|
| 1 | +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file |
| 2 | +// for details. All rights reserved. Use of this source code is governed by a |
| 3 | +// BSD-style license that can be found in the LICENSE file. |
| 4 | + |
| 5 | +import 'dart:js_interop'; |
| 6 | + |
| 7 | +import '../dom.dart' show HTMLIFrameElement, Location, Window; |
| 8 | + |
| 9 | +// The Dart runtime does not allow this to be typed as any better than `JSAny?`. |
| 10 | +extension type _CrossOriginWindow(JSAny? any) { |
| 11 | + external bool get closed; |
| 12 | + external int get length; |
| 13 | + // While you can set the location to a string value, this is the same as |
| 14 | + // `location.href`, so we only allow the getter to avoid a |
| 15 | + // `getter_not_subtype_setter_types` error. |
| 16 | + external JSAny? get location; |
| 17 | + external JSAny? get opener; |
| 18 | + external JSAny? get parent; |
| 19 | + external JSAny? get top; |
| 20 | + // `frames`, `self`, and `window` are all supported for cross-origin windows, |
| 21 | + // but simply return the calling window, so there's no use in supporting them |
| 22 | + // for interop. |
| 23 | + external void blur(); |
| 24 | + external void close(); |
| 25 | + external void focus(); |
| 26 | + external void postMessage( |
| 27 | + JSAny? message, [ |
| 28 | + JSAny optionsOrTargetOrigin, |
| 29 | + JSArray<JSObject> transfer, |
| 30 | + ]); |
| 31 | +} |
| 32 | + |
| 33 | +// The Dart runtime does not allow this to be typed as any better than `JSAny?`. |
| 34 | +extension type _CrossOriginLocation(JSAny? any) { |
| 35 | + external void replace(String url); |
| 36 | + external set href(String value); |
| 37 | +} |
| 38 | + |
| 39 | +/// A safe wrapper for a cross-origin window. |
| 40 | +/// |
| 41 | +/// Since cross-origin access is limited by the browser, the Dart runtime can't |
| 42 | +/// provide a type for or null-assert the cross-origin window. To safely |
| 43 | +/// interact with the cross-origin window, use this wrapper instead. |
| 44 | +/// |
| 45 | +/// The `dart:html` equivalent is `_DOMWindowCrossFrame`. |
| 46 | +/// |
| 47 | +/// Only includes allowed APIs from the W3 spec located here: |
| 48 | +/// https://html.spec.whatwg.org/multipage/nav-history-apis.html#crossoriginproperties-(-o-) |
| 49 | +/// Some browsers may provide more access. |
| 50 | +class CrossOriginWindow { |
| 51 | + CrossOriginWindow._(JSAny? o) : _window = _CrossOriginWindow(o); |
| 52 | + |
| 53 | + static CrossOriginWindow? _create(JSAny? o) { |
| 54 | + if (o == null) return null; |
| 55 | + return CrossOriginWindow._(o); |
| 56 | + } |
| 57 | + |
| 58 | + final _CrossOriginWindow _window; |
| 59 | + |
| 60 | + /// The [Window.closed] value of this cross-origin window. |
| 61 | + bool get closed => _window.closed; |
| 62 | + |
| 63 | + /// The [Window.length] value of this cross-origin window. |
| 64 | + int get length => _window.length; |
| 65 | + |
| 66 | + /// A [CrossOriginLocation] wrapper of the [Window.location] value of this |
| 67 | + /// cross-origin window. |
| 68 | + CrossOriginLocation? get location => |
| 69 | + CrossOriginLocation._create(_window.location); |
| 70 | + |
| 71 | + /// A [CrossOriginWindow] wrapper of the [Window.opener] value of this |
| 72 | + /// cross-origin window. |
| 73 | + CrossOriginWindow? get opener => _create(_window.opener); |
| 74 | + |
| 75 | + /// A [CrossOriginWindow] wrapper of the [Window.top] value of this |
| 76 | + /// cross-origin window. |
| 77 | + CrossOriginWindow? get parent => _create(_window.parent); |
| 78 | + |
| 79 | + /// A [CrossOriginWindow] wrapper of the [Window.parent] value of this |
| 80 | + /// cross-origin window. |
| 81 | + CrossOriginWindow? get top => _create(_window.top); |
| 82 | + |
| 83 | + /// Calls [Window.blur] on this cross-origin window. |
| 84 | + void blur() => _window.blur(); |
| 85 | + |
| 86 | + /// Calls [Window.close] on this cross-origin window. |
| 87 | + void close() => _window.close(); |
| 88 | + |
| 89 | + /// Calls [Window.focus] on this cross-origin window. |
| 90 | + void focus() => _window.focus(); |
| 91 | + |
| 92 | + /// Calls [Window.postMessage] on this cross-origin window with the given |
| 93 | + /// [message], [optionsOrTargetOrigin] if not `null`, and [transfer] if not |
| 94 | + /// `null`. |
| 95 | + void postMessage( |
| 96 | + JSAny? message, [ |
| 97 | + JSAny? optionsOrTargetOrigin, |
| 98 | + JSArray<JSObject>? transfer, |
| 99 | + ]) { |
| 100 | + if (optionsOrTargetOrigin == null) { |
| 101 | + _window.postMessage(message); |
| 102 | + } else if (transfer == null) { |
| 103 | + _window.postMessage(message, optionsOrTargetOrigin); |
| 104 | + } else { |
| 105 | + _window.postMessage(message, optionsOrTargetOrigin, transfer); |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + /// The unsafe window value that this wrapper wraps that should only ever be |
| 110 | + /// typed as <code>[JSAny]?</code>. |
| 111 | + /// |
| 112 | + /// > [!NOTE] |
| 113 | + /// > This is only intended to be passed to an interop member that expects a |
| 114 | + /// > <code>[JSAny]?</code>. Safety for any other operations is not |
| 115 | + /// > guaranteed. |
| 116 | + JSAny? get unsafeWindow => _window.any; |
| 117 | +} |
| 118 | + |
| 119 | +/// A safe wrapper for a cross-origin location obtained through a cross-origin |
| 120 | +/// window. |
| 121 | +/// |
| 122 | +/// Since cross-origin access is limited by the browser, the Dart runtime can't |
| 123 | +/// provide a type for or null-assert the cross-origin location. To safely |
| 124 | +/// interact with the cross-origin location, use this wrapper instead. |
| 125 | +/// |
| 126 | +/// The `dart:html` equivalent is `_LocationCrossFrame`. |
| 127 | +/// |
| 128 | +/// Only includes allowed APIs from the W3 spec located here: |
| 129 | +/// https://html.spec.whatwg.org/multipage/nav-history-apis.html#crossoriginproperties-(-o-) |
| 130 | +/// Some browsers may provide more access. |
| 131 | +class CrossOriginLocation { |
| 132 | + CrossOriginLocation._(JSAny? o) : _location = _CrossOriginLocation(o); |
| 133 | + |
| 134 | + static CrossOriginLocation? _create(JSAny? o) { |
| 135 | + if (o == null) return null; |
| 136 | + return CrossOriginLocation._(o); |
| 137 | + } |
| 138 | + |
| 139 | + final _CrossOriginLocation _location; |
| 140 | + |
| 141 | + /// Sets the [Location.href] value of this cross-origin location to [value]. |
| 142 | + set href(String value) => _location.href = value; |
| 143 | + |
| 144 | + /// Calls [Location.replace] on this cross-origin location with the given |
| 145 | + /// [url]. |
| 146 | + void replace(String url) => _location.replace(url); |
| 147 | + |
| 148 | + /// The unsafe location value that this wrapper wraps that should only ever be |
| 149 | + /// typed as <code>[JSAny]?</code>. |
| 150 | + /// |
| 151 | + /// > [!NOTE] |
| 152 | + /// > This is only intended to be passed to an interop member that expects a |
| 153 | + /// > <code>[JSAny]?</code>. Safety for any other operations is not |
| 154 | + /// > guaranteed. |
| 155 | + JSAny? get unsafeLocation => _location.any; |
| 156 | +} |
| 157 | + |
| 158 | +extension CrossOriginContentWindowExtension on HTMLIFrameElement { |
| 159 | + @JS('contentWindow') |
| 160 | + external JSAny? get _contentWindow; |
| 161 | + |
| 162 | + /// A [CrossOriginWindow] wrapper of the [HTMLIFrameElement.contentWindow] |
| 163 | + /// value of this `iframe`. |
| 164 | + CrossOriginWindow? get contentWindowCrossOrigin => |
| 165 | + CrossOriginWindow._create(_contentWindow); |
| 166 | +} |
| 167 | + |
| 168 | +/// Safe alternatives to common [Window] members that can return cross-origin |
| 169 | +/// windows. |
| 170 | +/// |
| 171 | +/// By default, the Dart web compilers are not sensitive to cross-origin |
| 172 | +/// objects, and therefore same-origin policy errors may be triggered when |
| 173 | +/// type-checking. Use these members instead to safely interact with such |
| 174 | +/// objects. |
| 175 | +extension CrossOriginWindowExtension on Window { |
| 176 | + @JS('open') |
| 177 | + external JSAny? _open(String url); |
| 178 | + |
| 179 | + /// A [CrossOriginWindow] wrapper of the value returned from calling |
| 180 | + /// [Window.open] with [url]. |
| 181 | + CrossOriginWindow? openCrossOrigin(String url) => |
| 182 | + CrossOriginWindow._create(_open(url)); |
| 183 | + @JS('opener') |
| 184 | + external JSAny? get _opener; |
| 185 | + |
| 186 | + /// A [CrossOriginWindow] wrapper of the [Window.opener] value of this |
| 187 | + /// cross-origin window. |
| 188 | + CrossOriginWindow? get openerCrossOrigin => |
| 189 | + CrossOriginWindow._create(_opener); |
| 190 | + @JS('parent') |
| 191 | + external JSAny? get _parent; |
| 192 | + |
| 193 | + /// A [CrossOriginWindow] wrapper of the [Window.parent] value of this |
| 194 | + /// cross-origin window. |
| 195 | + CrossOriginWindow? get parentCrossOrigin => |
| 196 | + CrossOriginWindow._create(_parent); |
| 197 | + @JS('top') |
| 198 | + external JSAny? get _top; |
| 199 | + |
| 200 | + /// A [CrossOriginWindow] wrapper of the [Window.top] value of this |
| 201 | + /// cross-origin window. |
| 202 | + CrossOriginWindow? get topCrossOrigin => CrossOriginWindow._create(_top); |
| 203 | +} |
0 commit comments