test_json.js (12100B)
1 const { json, getKnownElement, getKnownShadowRoot } = 2 ChromeUtils.importESModule("chrome://remote/content/marionette/json.sys.mjs"); 3 const { NodeCache } = ChromeUtils.importESModule( 4 "chrome://remote/content/shared/webdriver/NodeCache.sys.mjs" 5 ); 6 const { ShadowRoot, WebElement, WebReference } = ChromeUtils.importESModule( 7 "chrome://remote/content/marionette/web-reference.sys.mjs" 8 ); 9 10 const MemoryReporter = Cc["@mozilla.org/memory-reporter-manager;1"].getService( 11 Ci.nsIMemoryReporterManager 12 ); 13 14 function setupTest() { 15 const browser = Services.appShell.createWindowlessBrowser(false); 16 const nodeCache = new NodeCache(); 17 18 const videoEl = browser.document.createElement("video"); 19 browser.document.body.appendChild(videoEl); 20 21 const svgEl = browser.document.createElementNS( 22 "http://www.w3.org/2000/svg", 23 "rect" 24 ); 25 browser.document.body.appendChild(svgEl); 26 27 const shadowRoot = videoEl.openOrClosedShadowRoot; 28 29 const iframeEl = browser.document.createElement("iframe"); 30 browser.document.body.appendChild(iframeEl); 31 const childEl = iframeEl.contentDocument.createElement("div"); 32 33 return { 34 browser, 35 browsingContext: browser.browsingContext, 36 nodeCache, 37 childEl, 38 iframeEl, 39 seenNodeIds: new Map(), 40 shadowRoot, 41 svgEl, 42 videoEl, 43 }; 44 } 45 46 function assert_cloned_value(value, clonedValue, nodeCache, seenNodes = []) { 47 const { seenNodeIds, serializedValue } = json.clone(value, nodeCache); 48 49 deepEqual(serializedValue, clonedValue); 50 deepEqual([...seenNodeIds.values()], seenNodes); 51 } 52 53 add_task(function test_clone_generalTypes() { 54 const { nodeCache } = setupTest(); 55 56 // null 57 assert_cloned_value(undefined, null, nodeCache); 58 assert_cloned_value(null, null, nodeCache); 59 60 // primitives 61 assert_cloned_value(true, true, nodeCache); 62 assert_cloned_value(42, 42, nodeCache); 63 assert_cloned_value("foo", "foo", nodeCache); 64 65 // toJSON 66 assert_cloned_value( 67 { 68 toJSON() { 69 return "foo"; 70 }, 71 }, 72 "foo", 73 nodeCache 74 ); 75 }); 76 77 add_task(function test_clone_ShadowRoot() { 78 const { nodeCache, seenNodeIds, shadowRoot } = setupTest(); 79 80 const shadowRootRef = nodeCache.getOrCreateNodeReference( 81 shadowRoot, 82 seenNodeIds 83 ); 84 assert_cloned_value( 85 shadowRoot, 86 WebReference.from(shadowRoot, shadowRootRef).toJSON(), 87 nodeCache, 88 seenNodeIds 89 ); 90 }); 91 92 add_task(function test_clone_WebElement() { 93 const { videoEl, nodeCache, seenNodeIds, svgEl } = setupTest(); 94 95 const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds); 96 assert_cloned_value( 97 videoEl, 98 WebReference.from(videoEl, videoElRef).toJSON(), 99 nodeCache, 100 seenNodeIds 101 ); 102 103 // Check an element with a different namespace 104 const svgElRef = nodeCache.getOrCreateNodeReference(svgEl, seenNodeIds); 105 assert_cloned_value( 106 svgEl, 107 WebReference.from(svgEl, svgElRef).toJSON(), 108 nodeCache, 109 seenNodeIds 110 ); 111 }); 112 113 add_task(function test_clone_Sequences() { 114 const { videoEl, nodeCache, seenNodeIds } = setupTest(); 115 116 const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds); 117 118 const input = [ 119 null, 120 true, 121 [42], 122 videoEl, 123 { 124 toJSON() { 125 return "foo"; 126 }, 127 }, 128 { bar: "baz" }, 129 ]; 130 131 assert_cloned_value( 132 input, 133 [ 134 null, 135 true, 136 [42], 137 { [WebElement.Identifier]: videoElRef }, 138 "foo", 139 { bar: "baz" }, 140 ], 141 nodeCache, 142 seenNodeIds 143 ); 144 }); 145 146 add_task(function test_clone_objects() { 147 const { videoEl, nodeCache, seenNodeIds } = setupTest(); 148 149 const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds); 150 151 const input = { 152 null: null, 153 boolean: true, 154 array: [42], 155 element: videoEl, 156 toJSON: { 157 toJSON() { 158 return "foo"; 159 }, 160 }, 161 object: { bar: "baz" }, 162 }; 163 164 assert_cloned_value( 165 input, 166 { 167 null: null, 168 boolean: true, 169 array: [42], 170 element: { [WebElement.Identifier]: videoElRef }, 171 toJSON: "foo", 172 object: { bar: "baz" }, 173 }, 174 nodeCache, 175 seenNodeIds 176 ); 177 }); 178 179 add_task(function test_clone_ÑyclicReference() { 180 const { nodeCache } = setupTest(); 181 182 // object 183 Assert.throws(() => { 184 const obj = {}; 185 obj.reference = obj; 186 json.clone(obj, nodeCache); 187 }, /JavaScriptError/); 188 189 // array 190 Assert.throws(() => { 191 const array = []; 192 array.push(array); 193 json.clone(array, nodeCache); 194 }, /JavaScriptError/); 195 196 // array in object 197 Assert.throws(() => { 198 const array = []; 199 array.push(array); 200 json.clone({ array }, nodeCache); 201 }, /JavaScriptError/); 202 203 // object in array 204 Assert.throws(() => { 205 const obj = {}; 206 obj.reference = obj; 207 json.clone([obj], nodeCache); 208 }, /JavaScriptError/); 209 }); 210 211 add_task(function test_deserialize_generalTypes() { 212 const { browsingContext, nodeCache } = setupTest(); 213 214 // null 215 equal(json.deserialize(undefined, nodeCache, browsingContext), undefined); 216 equal(json.deserialize(null, nodeCache, browsingContext), null); 217 218 // primitives 219 equal(json.deserialize(true, nodeCache, browsingContext), true); 220 equal(json.deserialize(42, nodeCache, browsingContext), 42); 221 equal(json.deserialize("foo", nodeCache, browsingContext), "foo"); 222 }); 223 224 add_task(function test_deserialize_ShadowRoot() { 225 const { browsingContext, nodeCache, seenNodeIds, shadowRoot } = setupTest(); 226 const seenNodes = new Set(); 227 228 // Fails to resolve for unknown elements 229 const unknownShadowRootId = { [ShadowRoot.Identifier]: "foo" }; 230 Assert.throws(() => { 231 json.deserialize( 232 unknownShadowRootId, 233 nodeCache, 234 browsingContext, 235 seenNodes 236 ); 237 }, /NoSuchShadowRootError/); 238 239 const shadowRootRef = nodeCache.getOrCreateNodeReference( 240 shadowRoot, 241 seenNodeIds 242 ); 243 const shadowRootEl = { [ShadowRoot.Identifier]: shadowRootRef }; 244 245 // Fails to resolve for missing window reference 246 Assert.throws(() => json.deserialize(shadowRootEl, nodeCache), /TypeError/); 247 248 // Previously seen element is associated with original web element reference 249 seenNodes.add(shadowRootRef); 250 const root = json.deserialize( 251 shadowRootEl, 252 nodeCache, 253 browsingContext, 254 seenNodes 255 ); 256 deepEqual(root, shadowRoot); 257 deepEqual(root, nodeCache.getNode(browsingContext, shadowRootRef)); 258 }); 259 260 add_task(function test_deserialize_WebElement() { 261 const { browser, browsingContext, videoEl, nodeCache, seenNodeIds } = 262 setupTest(); 263 const seenNodes = new Set(); 264 265 // Fails to resolve for unknown elements 266 const unknownWebElId = { [WebElement.Identifier]: "foo" }; 267 Assert.throws(() => { 268 json.deserialize(unknownWebElId, nodeCache, browsingContext, seenNodes); 269 }, /NoSuchElementError/); 270 271 const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds); 272 const htmlWebEl = { [WebElement.Identifier]: videoElRef }; 273 274 // Fails to resolve for missing window reference 275 Assert.throws(() => json.deserialize(htmlWebEl, nodeCache), /TypeError/); 276 277 // Previously seen element is associated with original web element reference 278 seenNodes.add(videoElRef); 279 const el = json.deserialize(htmlWebEl, nodeCache, browsingContext, seenNodes); 280 deepEqual(el, videoEl); 281 deepEqual(el, nodeCache.getNode(browser.browsingContext, videoElRef)); 282 }); 283 284 add_task(function test_deserialize_Sequences() { 285 const { browsingContext, videoEl, nodeCache, seenNodeIds } = setupTest(); 286 const seenNodes = new Set(); 287 288 const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds); 289 seenNodes.add(videoElRef); 290 291 const input = [ 292 null, 293 true, 294 [42], 295 { [WebElement.Identifier]: videoElRef }, 296 { bar: "baz" }, 297 ]; 298 299 const actual = json.deserialize(input, nodeCache, browsingContext, seenNodes); 300 301 equal(actual[0], null); 302 equal(actual[1], true); 303 deepEqual(actual[2], [42]); 304 deepEqual(actual[3], videoEl); 305 deepEqual(actual[4], { bar: "baz" }); 306 }); 307 308 add_task(function test_deserialize_objects() { 309 const { browsingContext, videoEl, nodeCache, seenNodeIds } = setupTest(); 310 const seenNodes = new Set(); 311 312 const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds); 313 seenNodes.add(videoElRef); 314 315 const input = { 316 null: null, 317 boolean: true, 318 array: [42], 319 element: { [WebElement.Identifier]: videoElRef }, 320 object: { bar: "baz" }, 321 }; 322 323 const actual = json.deserialize(input, nodeCache, browsingContext, seenNodes); 324 325 equal(actual.null, null); 326 equal(actual.boolean, true); 327 deepEqual(actual.array, [42]); 328 deepEqual(actual.element, videoEl); 329 deepEqual(actual.object, { bar: "baz" }); 330 331 nodeCache.clear({ all: true }); 332 }); 333 334 add_task(async function test_getKnownElement() { 335 const { browser, nodeCache, seenNodeIds, shadowRoot, videoEl } = setupTest(); 336 const seenNodes = new Set(); 337 338 // Unknown element reference 339 Assert.throws(() => { 340 getKnownElement(browser.browsingContext, "foo", nodeCache, seenNodes); 341 }, /NoSuchElementError/); 342 343 // With a ShadowRoot reference 344 const shadowRootRef = nodeCache.getOrCreateNodeReference( 345 shadowRoot, 346 seenNodeIds 347 ); 348 seenNodes.add(shadowRootRef); 349 350 Assert.throws(() => { 351 getKnownElement( 352 browser.browsingContext, 353 shadowRootRef, 354 nodeCache, 355 seenNodes 356 ); 357 }, /NoSuchElementError/); 358 359 let detachedEl = browser.document.createElement("div"); 360 const detachedElRef = nodeCache.getOrCreateNodeReference( 361 detachedEl, 362 seenNodeIds 363 ); 364 seenNodes.add(detachedElRef); 365 366 // Element not connected to the DOM 367 Assert.throws(() => { 368 getKnownElement( 369 browser.browsingContext, 370 detachedElRef, 371 nodeCache, 372 seenNodes 373 ); 374 }, /StaleElementReferenceError/); 375 376 // Element garbage collected 377 detachedEl = null; 378 379 await new Promise(resolve => MemoryReporter.minimizeMemoryUsage(resolve)); 380 Assert.throws(() => { 381 getKnownElement( 382 browser.browsingContext, 383 detachedElRef, 384 nodeCache, 385 seenNodes 386 ); 387 }, /StaleElementReferenceError/); 388 389 // Known element reference 390 const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds); 391 seenNodes.add(videoElRef); 392 393 equal( 394 getKnownElement(browser.browsingContext, videoElRef, nodeCache, seenNodes), 395 videoEl 396 ); 397 }); 398 399 add_task(async function test_getKnownShadowRoot() { 400 const { browser, nodeCache, seenNodeIds, shadowRoot, videoEl } = setupTest(); 401 const seenNodes = new Set(); 402 403 const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds); 404 seenNodes.add(videoElRef); 405 406 // Unknown ShadowRoot reference 407 Assert.throws(() => { 408 getKnownShadowRoot(browser.browsingContext, "foo", nodeCache, seenNodes); 409 }, /NoSuchShadowRootError/); 410 411 // With a videoElement reference 412 Assert.throws(() => { 413 getKnownShadowRoot( 414 browser.browsingContext, 415 videoElRef, 416 nodeCache, 417 seenNodes 418 ); 419 }, /NoSuchShadowRootError/); 420 421 // Known ShadowRoot reference 422 const shadowRootRef = nodeCache.getOrCreateNodeReference( 423 shadowRoot, 424 seenNodeIds 425 ); 426 seenNodes.add(shadowRootRef); 427 428 equal( 429 getKnownShadowRoot( 430 browser.browsingContext, 431 shadowRootRef, 432 nodeCache, 433 seenNodes 434 ), 435 shadowRoot 436 ); 437 438 // Detached ShadowRoot host 439 let el = browser.document.createElement("div"); 440 let detachedShadowRoot = el.attachShadow({ mode: "open" }); 441 detachedShadowRoot.innerHTML = "<input></input>"; 442 443 const detachedShadowRootRef = nodeCache.getOrCreateNodeReference( 444 detachedShadowRoot, 445 seenNodeIds 446 ); 447 seenNodes.add(detachedShadowRootRef); 448 449 // ... not connected to the DOM 450 Assert.throws(() => { 451 getKnownShadowRoot( 452 browser.browsingContext, 453 detachedShadowRootRef, 454 nodeCache, 455 seenNodes 456 ); 457 }, /DetachedShadowRootError/); 458 459 // ... host and shadow root garbage collected 460 el = null; 461 detachedShadowRoot = null; 462 463 await new Promise(resolve => MemoryReporter.minimizeMemoryUsage(resolve)); 464 Assert.throws(() => { 465 getKnownShadowRoot( 466 browser.browsingContext, 467 detachedShadowRootRef, 468 nodeCache, 469 seenNodes 470 ); 471 }, /DetachedShadowRootError/); 472 });