web-reference.sys.mjs (8671B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 const lazy = {}; 6 7 ChromeUtils.defineESModuleGetters(lazy, { 8 assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs", 9 dom: "chrome://remote/content/shared/DOM.sys.mjs", 10 error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", 11 generateUUID: "chrome://remote/content/shared/UUID.sys.mjs", 12 pprint: "chrome://remote/content/shared/Format.sys.mjs", 13 }); 14 15 /** 16 * A web reference is an abstraction used to identify an element when 17 * it is transported via the protocol, between remote- and local ends. 18 * 19 * In Marionette this abstraction can represent DOM elements, 20 * WindowProxies, and XUL elements. 21 */ 22 export class WebReference { 23 /** 24 * @param {string} uuid 25 * Identifier that must be unique across all browsing contexts 26 * for the contract to be upheld. 27 */ 28 constructor(uuid) { 29 this.uuid = lazy.assert.string( 30 uuid, 31 lazy.pprint`Expected "uuid" to be a string, got ${uuid}` 32 ); 33 } 34 35 /** 36 * Performs an equality check between this web element and 37 * <var>other</var>. 38 * 39 * @param {WebReference} other 40 * Web element to compare with this. 41 * 42 * @returns {boolean} 43 * True if this and <var>other</var> are the same. False 44 * otherwise. 45 */ 46 is(other) { 47 return other instanceof WebReference && this.uuid === other.uuid; 48 } 49 50 toString() { 51 return `[object ${this.constructor.name} uuid=${this.uuid}]`; 52 } 53 54 /** 55 * Returns a new {@link WebReference} reference for a DOM or XUL element, 56 * <code>WindowProxy</code>, or <code>ShadowRoot</code>. 57 * 58 * @param {(Element|ShadowRoot|WindowProxy|MockXULElement)} node 59 * Node to construct a web element reference for. 60 * @param {string=} uuid 61 * Optional unique identifier of the WebReference if already known. 62 * If not defined a new unique identifier will be created. 63 * 64 * @returns {WebReference} 65 * Web reference for <var>node</var>. 66 * 67 * @throws {InvalidArgumentError} 68 * If <var>node</var> is neither a <code>WindowProxy</code>, 69 * DOM or XUL element, or <code>ShadowRoot</code>. 70 */ 71 static from(node, uuid) { 72 if (uuid === undefined) { 73 uuid = lazy.generateUUID(); 74 } 75 76 if (lazy.dom.isShadowRoot(node) && !lazy.dom.isInPrivilegedDocument(node)) { 77 // When we support Chrome Shadowroots we will need to 78 // do a check here of shadowroot.host being in a privileged document 79 // See Bug 1743541 80 return new ShadowRoot(uuid); 81 } else if (lazy.dom.isElement(node)) { 82 return new WebElement(uuid); 83 } else if (lazy.dom.isDOMWindow(node)) { 84 if (node.parent === node) { 85 return new WebWindow(uuid); 86 } 87 return new WebFrame(uuid); 88 } 89 90 throw new lazy.error.InvalidArgumentError( 91 "Expected DOM window/element " + lazy.pprint`or XUL element, got: ${node}` 92 ); 93 } 94 95 /** 96 * Unmarshals a JSON Object to one of {@link ShadowRoot}, {@link WebElement}, 97 * {@link WebFrame}, or {@link WebWindow}. 98 * 99 * @param {Record<string, string>} json 100 * Web reference, which is supposed to be a JSON Object 101 * where the key is one of the {@link WebReference} concrete 102 * classes' UUID identifiers. 103 * 104 * @returns {WebReference} 105 * Web reference for the JSON object. 106 * 107 * @throws {InvalidArgumentError} 108 * If <var>json</var> is not a web reference. 109 */ 110 static fromJSON(json) { 111 lazy.assert.object( 112 json, 113 lazy.pprint`Expected web reference to be an object, got ${json}` 114 ); 115 if (json instanceof WebReference) { 116 return json; 117 } 118 let keys = Object.keys(json); 119 120 for (let key of keys) { 121 switch (key) { 122 case ShadowRoot.Identifier: 123 return ShadowRoot.fromJSON(json); 124 125 case WebElement.Identifier: 126 return WebElement.fromJSON(json); 127 128 case WebFrame.Identifier: 129 return WebFrame.fromJSON(json); 130 131 case WebWindow.Identifier: 132 return WebWindow.fromJSON(json); 133 } 134 } 135 136 throw new lazy.error.InvalidArgumentError( 137 lazy.pprint`Expected web reference, got: ${json}` 138 ); 139 } 140 141 /** 142 * Checks if <var>obj<var> is a {@link WebReference} reference. 143 * 144 * @param {Record<string, string>} obj 145 * Object that represents a {@link WebReference}. 146 * 147 * @returns {boolean} 148 * True if <var>obj</var> is a {@link WebReference}, false otherwise. 149 */ 150 static isReference(obj) { 151 if (Object.prototype.toString.call(obj) != "[object Object]") { 152 return false; 153 } 154 155 if ( 156 ShadowRoot.Identifier in obj || 157 WebElement.Identifier in obj || 158 WebFrame.Identifier in obj || 159 WebWindow.Identifier in obj 160 ) { 161 return true; 162 } 163 return false; 164 } 165 } 166 167 /** 168 * Shadow Root elements are represented as shadow root references when they are 169 * transported over the wire protocol 170 */ 171 export class ShadowRoot extends WebReference { 172 toJSON() { 173 return { [ShadowRoot.Identifier]: this.uuid }; 174 } 175 176 static fromJSON(json) { 177 const { Identifier } = ShadowRoot; 178 179 if (!(Identifier in json)) { 180 throw new lazy.error.InvalidArgumentError( 181 lazy.pprint`Expected shadow root reference, got: ${json}` 182 ); 183 } 184 185 let uuid = json[Identifier]; 186 return new ShadowRoot(uuid); 187 } 188 189 /** 190 * Constructs a {@link ShadowRoot} from a string <var>uuid</var>. 191 * 192 * This whole function is a workaround for the fact that clients 193 * to Marionette occasionally pass <code>{id: <uuid>}</code> JSON 194 * Objects instead of shadow root representations. 195 * 196 * @param {string} uuid 197 * UUID to be associated with the web reference. 198 * 199 * @returns {ShadowRoot} 200 * The shadow root reference. 201 * 202 * @throws {InvalidArgumentError} 203 * If <var>uuid</var> is not a string. 204 */ 205 static fromUUID(uuid) { 206 lazy.assert.string( 207 uuid, 208 lazy.pprint`Expected "uuid" to be a string, got: ${uuid}` 209 ); 210 211 return new ShadowRoot(uuid); 212 } 213 } 214 215 ShadowRoot.Identifier = "shadow-6066-11e4-a52e-4f735466cecf"; 216 217 /** 218 * DOM elements are represented as web elements when they are 219 * transported over the wire protocol. 220 */ 221 export class WebElement extends WebReference { 222 toJSON() { 223 return { [WebElement.Identifier]: this.uuid }; 224 } 225 226 static fromJSON(json) { 227 const { Identifier } = WebElement; 228 229 if (!(Identifier in json)) { 230 throw new lazy.error.InvalidArgumentError( 231 lazy.pprint`Expected web element reference, got: ${json}` 232 ); 233 } 234 235 let uuid = json[Identifier]; 236 return new WebElement(uuid); 237 } 238 239 /** 240 * Constructs a {@link WebElement} from a string <var>uuid</var>. 241 * 242 * This whole function is a workaround for the fact that clients 243 * to Marionette occasionally pass <code>{id: <uuid>}</code> JSON 244 * Objects instead of web element representations. 245 * 246 * @param {string} uuid 247 * UUID to be associated with the web reference. 248 * 249 * @returns {WebElement} 250 * The web element reference. 251 * 252 * @throws {InvalidArgumentError} 253 * If <var>uuid</var> is not a string. 254 */ 255 static fromUUID(uuid) { 256 return new WebElement(uuid); 257 } 258 } 259 260 WebElement.Identifier = "element-6066-11e4-a52e-4f735466cecf"; 261 262 /** 263 * Nested browsing contexts, such as the <code>WindowProxy</code> 264 * associated with <tt><frame></tt> and <tt><iframe></tt>, 265 * are represented as web frames over the wire protocol. 266 */ 267 export class WebFrame extends WebReference { 268 toJSON() { 269 return { [WebFrame.Identifier]: this.uuid }; 270 } 271 272 static fromJSON(json) { 273 if (!(WebFrame.Identifier in json)) { 274 throw new lazy.error.InvalidArgumentError( 275 lazy.pprint`Expected web frame reference, got: ${json}` 276 ); 277 } 278 let uuid = json[WebFrame.Identifier]; 279 return new WebFrame(uuid); 280 } 281 } 282 283 WebFrame.Identifier = "frame-075b-4da1-b6ba-e579c2d3230a"; 284 285 /** 286 * Top-level browsing contexts, such as <code>WindowProxy</code> 287 * whose <code>opener</code> is null, are represented as web windows 288 * over the wire protocol. 289 */ 290 export class WebWindow extends WebReference { 291 toJSON() { 292 return { [WebWindow.Identifier]: this.uuid }; 293 } 294 295 static fromJSON(json) { 296 if (!(WebWindow.Identifier in json)) { 297 throw new lazy.error.InvalidArgumentError( 298 lazy.pprint`Expected web window reference, got: ${json}` 299 ); 300 } 301 let uuid = json[WebWindow.Identifier]; 302 return new WebWindow(uuid); 303 } 304 } 305 306 WebWindow.Identifier = "window-fcc6-11e5-b4f8-330a88ab9d7f";