SpecialPowersChild.sys.mjs (68116B)
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 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 /* This code is loaded in every child process that is started by mochitest. 5 */ 6 7 import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs"; 8 9 const lazy = {}; 10 11 ChromeUtils.defineESModuleGetters(lazy, { 12 ContentTaskUtils: "resource://testing-common/ContentTaskUtils.sys.mjs", 13 MockColorPicker: "resource://testing-common/MockColorPicker.sys.mjs", 14 MockFilePicker: "resource://testing-common/MockFilePicker.sys.mjs", 15 MockPermissionPrompt: 16 "resource://testing-common/MockPermissionPrompt.sys.mjs", 17 MockPromptCollection: 18 "resource://testing-common/MockPromptCollection.sys.mjs", 19 MockSound: "resource://testing-common/MockSound.sys.mjs", 20 NetUtil: "resource://gre/modules/NetUtil.sys.mjs", 21 PerTestCoverageUtils: 22 "resource://testing-common/PerTestCoverageUtils.sys.mjs", 23 PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", 24 SpecialPowersSandbox: 25 "resource://testing-common/SpecialPowersSandbox.sys.mjs", 26 WrapPrivileged: "resource://testing-common/WrapPrivileged.sys.mjs", 27 }); 28 29 Cu.crashIfNotInAutomation(); 30 31 function bindDOMWindowUtils(aWindow) { 32 return aWindow && lazy.WrapPrivileged.wrap(aWindow.windowUtils, aWindow); 33 } 34 35 function defineSpecialPowers(sp) { 36 let window = sp.contentWindow; 37 window.SpecialPowers = sp; 38 if (window === window.wrappedJSObject) { 39 return; 40 } 41 // We can't use a generic |defineLazyGetter| because it does not 42 // allow customizing the re-definition behavior. 43 Object.defineProperty(window.wrappedJSObject, "SpecialPowers", { 44 get() { 45 let value = lazy.WrapPrivileged.wrap(sp, window); 46 // If we bind |window.wrappedJSObject| when defining the getter 47 // and use it here, it might become a dead wrapper. 48 // We have to retrieve |wrappedJSObject| again. 49 Object.defineProperty(window.wrappedJSObject, "SpecialPowers", { 50 configurable: true, 51 enumerable: true, 52 value, 53 writable: true, 54 }); 55 return value; 56 }, 57 configurable: true, 58 enumerable: true, 59 }); 60 } 61 62 // SPConsoleListener reflects nsIConsoleMessage objects into JS in a 63 // tidy, XPCOM-hiding way. Messages that are nsIScriptError objects 64 // have their properties exposed in detail. It also auto-unregisters 65 // itself when it receives a "sentinel" message. 66 function SPConsoleListener(callback, contentWindow) { 67 this.callback = callback; 68 this.contentWindow = contentWindow; 69 } 70 71 SPConsoleListener.prototype = { 72 // Overload the observe method for both nsIConsoleListener and nsIObserver. 73 // The topic will be null for nsIConsoleListener. 74 observe(msg) { 75 let m = { 76 message: msg.message, 77 errorMessage: null, 78 cssSelectors: null, 79 sourceName: null, 80 lineNumber: null, 81 columnNumber: null, 82 category: null, 83 windowID: null, 84 isScriptError: false, 85 isConsoleEvent: false, 86 isWarning: false, 87 }; 88 if (msg instanceof Ci.nsIScriptError) { 89 m.errorMessage = msg.errorMessage; 90 m.cssSelectors = msg.cssSelectors; 91 m.sourceName = msg.sourceName; 92 m.lineNumber = msg.lineNumber; 93 m.columnNumber = msg.columnNumber; 94 m.category = msg.category; 95 m.windowID = msg.outerWindowID; 96 m.innerWindowID = msg.innerWindowID; 97 m.isScriptError = true; 98 m.isWarning = (msg.flags & Ci.nsIScriptError.warningFlag) === 1; 99 } 100 101 Object.freeze(m); 102 103 // Run in a separate runnable since console listeners aren't 104 // supposed to touch content and this one might. 105 Services.tm.dispatchToMainThread(() => { 106 this.callback.call(undefined, Cu.cloneInto(m, this.contentWindow)); 107 }); 108 109 if (!m.isScriptError && !m.isConsoleEvent && m.message === "SENTINEL") { 110 Services.console.unregisterListener(this); 111 } 112 }, 113 114 QueryInterface: ChromeUtils.generateQI(["nsIConsoleListener", "nsIObserver"]), 115 }; 116 117 export class SpecialPowersChild extends JSWindowActorChild { 118 constructor() { 119 super(); 120 121 this._windowID = null; 122 123 this._encounteredCrashDumpFiles = []; 124 this._unexpectedCrashDumpFiles = {}; 125 this._crashDumpDir = null; 126 this._serviceWorkerRegistered = false; 127 this._serviceWorkerCleanUpRequests = new Map(); 128 Object.defineProperty(this, "Components", { 129 configurable: true, 130 enumerable: true, 131 value: Components, 132 }); 133 this._createFilesOnError = null; 134 this._createFilesOnSuccess = null; 135 136 this._messageListeners = new ExtensionUtils.DefaultMap(() => new Set()); 137 138 this._consoleListeners = []; 139 this._spawnTaskImports = {}; 140 this._encounteredCrashDumpFiles = []; 141 this._unexpectedCrashDumpFiles = {}; 142 this._crashDumpDir = null; 143 this._mfl = null; 144 this._asyncObservers = new WeakMap(); 145 this._xpcomabi = null; 146 this._os = null; 147 this._pu = null; 148 149 this._nextExtensionID = 0; 150 this._extensionListeners = null; 151 152 lazy.WrapPrivileged.disableAutoWrap( 153 this.unwrap, 154 this.isWrapper, 155 this.wrapCallback, 156 this.wrapCallbackObject, 157 this.setWrapped, 158 this.nondeterministicGetWeakMapKeys, 159 this.snapshotWindowWithOptions, 160 this.snapshotWindow, 161 this.snapshotRect 162 ); 163 } 164 165 observe() { 166 // Ignore the "{chrome/content}-document-global-created" event. It 167 // is only observed to force creation of the actor. 168 } 169 170 actorCreated() { 171 this.attachToWindow(); 172 } 173 174 attachToWindow() { 175 let window = this.contentWindow; 176 // We should not invoke the getter. 177 if (!("SpecialPowers" in window.wrappedJSObject)) { 178 this._windowID = window.windowGlobalChild.innerWindowId; 179 180 defineSpecialPowers(this); 181 } 182 } 183 184 get window() { 185 return this.contentWindow; 186 } 187 188 // Hack around devtools sometimes trying to JSON stringify us. 189 toJSON() { 190 return {}; 191 } 192 193 toString() { 194 return "[SpecialPowers]"; 195 } 196 sanityCheck() { 197 return "foo"; 198 } 199 200 _addMessageListener(msgname, listener) { 201 this._messageListeners.get(msgname).add(listener); 202 } 203 204 _removeMessageListener(msgname, listener) { 205 this._messageListeners.get(msgname).delete(listener); 206 } 207 208 receiveMessage(message) { 209 if (this._messageListeners.has(message.name)) { 210 for (let listener of this._messageListeners.get(message.name)) { 211 try { 212 if (typeof listener === "function") { 213 listener(message); 214 } else { 215 listener.receiveMessage(message); 216 } 217 } catch (e) { 218 console.error(e); 219 } 220 } 221 } 222 223 switch (message.name) { 224 case "SPProcessCrashService": 225 if (message.json.type == "crash-observed") { 226 for (let e of message.json.dumpIDs) { 227 this._encounteredCrashDumpFiles.push(e.id + "." + e.extension); 228 } 229 } 230 break; 231 232 case "SPServiceWorkerRegistered": 233 this._serviceWorkerRegistered = message.data.registered; 234 break; 235 236 case "SpecialPowers.FilesCreated": 237 var createdHandler = this._createFilesOnSuccess; 238 this._createFilesOnSuccess = null; 239 this._createFilesOnError = null; 240 if (createdHandler) { 241 createdHandler(Cu.cloneInto(message.data, this.contentWindow)); 242 } 243 break; 244 245 case "SpecialPowers.FilesError": 246 var errorHandler = this._createFilesOnError; 247 this._createFilesOnSuccess = null; 248 this._createFilesOnError = null; 249 if (errorHandler) { 250 errorHandler(message.data); 251 } 252 break; 253 254 case "Spawn": { 255 let { task, args, caller, taskId, imports } = message.data; 256 return this._spawnTask(task, args, caller, taskId, imports); 257 } 258 259 case "EnsureFocus": { 260 // Ensure that the focus is in this child document. Returns a browsing 261 // context of a child frame if a subframe should be focused or undefined 262 // otherwise. 263 264 // If a subframe node is focused, then the focus will actually 265 // be within that subframe's document. If blurSubframe is true, 266 // then blur the subframe so that this parent document is focused 267 // instead. If blurSubframe is false, then return the browsing 268 // context for that subframe. The parent process will then call back 269 // into this same code but in the process for that subframe. 270 let focusedNode = this.document.activeElement; 271 let subframeFocused = 272 ChromeUtils.getClassName(focusedNode) == "HTMLIFrameElement" || 273 ChromeUtils.getClassName(focusedNode) == "HTMLFrameElement" || 274 ChromeUtils.getClassName(focusedNode) == "XULFrameElement"; 275 if (subframeFocused) { 276 if (message.data.blurSubframe) { 277 Services.focus.clearFocus(this.contentWindow); 278 } else { 279 if (!this.document.hasFocus()) { 280 this.contentWindow.focus(); 281 } 282 return Promise.resolve(focusedNode.browsingContext); 283 } 284 } 285 286 // A subframe is not focused, so if this document is 287 // not focused, focus it and wait for the focus event. 288 if (!this.document.hasFocus()) { 289 return new Promise(resolve => { 290 this.document.addEventListener( 291 "focus", 292 () => { 293 resolve(); 294 }, 295 { 296 capture: true, 297 once: true, 298 } 299 ); 300 this.contentWindow.focus(); 301 }); 302 } 303 break; 304 } 305 306 case "Assert": 307 { 308 // Handles info & Assert reports from SpecialPowersSandbox.sys.mjs. 309 if ("info" in message.data) { 310 (this.xpcshellScope || this.SimpleTest).info(message.data.info); 311 break; 312 } 313 314 // An assertion has been done in a mochitest chrome script 315 let { name, passed, stack, diag, expectFail } = message.data; 316 317 if (this.xpcshellScope) { 318 this.xpcshellScope.do_report_result(passed, name, stack); 319 } else if (this.SimpleTest) { 320 let expected = expectFail ? "fail" : "pass"; 321 this.SimpleTest.record(passed, name, diag, stack, expected); 322 } else { 323 // Well, this is unexpected. 324 dump(name + "\n"); 325 } 326 } 327 break; 328 } 329 return undefined; 330 } 331 332 registerProcessCrashObservers() { 333 this.sendAsyncMessage("SPProcessCrashService", { op: "register-observer" }); 334 } 335 336 unregisterProcessCrashObservers() { 337 this.sendAsyncMessage("SPProcessCrashService", { 338 op: "unregister-observer", 339 }); 340 } 341 342 /* 343 * Privileged object wrapping API 344 * 345 * Usage: 346 * var wrapper = SpecialPowers.wrap(obj); 347 * wrapper.privilegedMethod(); wrapper.privilegedProperty; 348 * obj === SpecialPowers.unwrap(wrapper); 349 * 350 * These functions provide transparent access to privileged objects using 351 * various pieces of deep SpiderMagic. Conceptually, a wrapper is just an 352 * object containing a reference to the underlying object, where all method 353 * calls and property accesses are transparently performed with the System 354 * Principal. Moreover, objects obtained from the wrapper (including properties 355 * and method return values) are wrapped automatically. Thus, after a single 356 * call to SpecialPowers.wrap(), the wrapper layer is transitively maintained. 357 * 358 * Known Issues: 359 * 360 * - The wrapping function does not preserve identity, so 361 * SpecialPowers.wrap(foo) !== SpecialPowers.wrap(foo). See bug 718543. 362 * 363 * - The wrapper cannot see expando properties on unprivileged DOM objects. 364 * That is to say, the wrapper uses Xray delegation. 365 * 366 * - The wrapper sometimes guesses certain ES5 attributes for returned 367 * properties. This is explained in a comment in the wrapper code above, 368 * and shouldn't be a problem. 369 */ 370 wrap(obj) { 371 return obj; 372 } 373 unwrap(obj) { 374 return lazy.WrapPrivileged.unwrap(obj); 375 } 376 isWrapper(val) { 377 return lazy.WrapPrivileged.isWrapper(val); 378 } 379 380 unwrapIfWrapped(obj) { 381 return lazy.WrapPrivileged.isWrapper(obj) 382 ? lazy.WrapPrivileged.unwrap(obj) 383 : obj; 384 } 385 386 /* 387 * Wrap objects on a specified global. 388 */ 389 wrapFor(obj, win) { 390 return lazy.WrapPrivileged.wrap(obj, win); 391 } 392 393 /* 394 * When content needs to pass a callback or a callback object to an API 395 * accessed over SpecialPowers, that API may sometimes receive arguments for 396 * whom it is forbidden to create a wrapper in content scopes. As such, we 397 * need a layer to wrap the values in SpecialPowers wrappers before they ever 398 * reach content. 399 */ 400 wrapCallback(func) { 401 return lazy.WrapPrivileged.wrapCallback(func, this.contentWindow); 402 } 403 wrapCallbackObject(obj) { 404 return lazy.WrapPrivileged.wrapCallbackObject(obj, this.contentWindow); 405 } 406 407 /* 408 * Used for assigning a property to a SpecialPowers wrapper, without unwrapping 409 * the value that is assigned. 410 */ 411 setWrapped(obj, prop, val) { 412 if (!lazy.WrapPrivileged.isWrapper(obj)) { 413 throw new Error( 414 "You only need to use this for SpecialPowers wrapped objects" 415 ); 416 } 417 418 obj = lazy.WrapPrivileged.unwrap(obj); 419 return Reflect.set(obj, prop, val); 420 } 421 422 /* 423 * Create blank privileged objects to use as out-params for privileged functions. 424 */ 425 createBlankObject() { 426 return {}; 427 } 428 429 /* 430 * Because SpecialPowers wrappers don't preserve identity, comparing with == 431 * can be hazardous. Sometimes we can just unwrap to compare, but sometimes 432 * wrapping the underlying object into a content scope is forbidden. This 433 * function strips any wrappers if they exist and compare the underlying 434 * values. 435 */ 436 compare(a, b) { 437 return lazy.WrapPrivileged.unwrap(a) === lazy.WrapPrivileged.unwrap(b); 438 } 439 440 get MockFilePicker() { 441 return lazy.MockFilePicker; 442 } 443 444 get MockColorPicker() { 445 return lazy.MockColorPicker; 446 } 447 448 get MockPromptCollection() { 449 return lazy.MockPromptCollection; 450 } 451 452 get MockPermissionPrompt() { 453 return lazy.MockPermissionPrompt; 454 } 455 456 get MockSound() { 457 return lazy.MockSound; 458 } 459 460 quit() { 461 this.sendAsyncMessage("SpecialPowers.Quit", {}); 462 } 463 464 // fileRequests is an array of file requests. Each file request is an object. 465 // A request must have a field |name|, which gives the base of the name of the 466 // file to be created in the profile directory. If the request has a |data| field 467 // then that data will be written to the file. 468 createFiles(fileRequests, onCreation, onError) { 469 return this.sendQuery("SpecialPowers.CreateFiles", fileRequests).then( 470 files => onCreation(Cu.cloneInto(files, this.contentWindow)), 471 onError 472 ); 473 } 474 475 // Remove the files that were created using |SpecialPowers.createFiles()|. 476 // This will be automatically called by |SimpleTest.finish()|. 477 removeFiles() { 478 this.sendAsyncMessage("SpecialPowers.RemoveFiles", {}); 479 } 480 481 executeAfterFlushingMessageQueue(aCallback) { 482 return this.sendQuery("Ping").then(aCallback); 483 } 484 485 async registeredServiceWorkers(aForceCheck) { 486 // Please see the comment in SpecialPowersParent.sys.mjs above 487 // this._serviceWorkerListener's assignment for what this returns. 488 if (this._serviceWorkerRegistered || aForceCheck) { 489 // This test registered at least one service worker. Send a synchronous 490 // call to the parent to make sure that it called unregister on all of its 491 // service workers. 492 let { workers } = await this.sendQuery("SPCheckServiceWorkers"); 493 return workers; 494 } 495 496 return []; 497 } 498 499 _readUrlAsString(aUrl) { 500 // Fetch script content as we can't use scriptloader's loadSubScript 501 // to evaluate http:// urls... 502 var scriptableStream = Cc[ 503 "@mozilla.org/scriptableinputstream;1" 504 ].getService(Ci.nsIScriptableInputStream); 505 506 var channel = lazy.NetUtil.newChannel({ 507 uri: aUrl, 508 loadUsingSystemPrincipal: true, 509 }); 510 var input = channel.open(); 511 scriptableStream.init(input); 512 513 var str; 514 var buffer = []; 515 516 while ((str = scriptableStream.read(4096))) { 517 buffer.push(str); 518 } 519 520 var output = buffer.join(""); 521 522 scriptableStream.close(); 523 input.close(); 524 525 var status; 526 if (channel instanceof Ci.nsIHttpChannel) { 527 status = channel.responseStatus; 528 } 529 530 if (status == 404) { 531 throw new Error( 532 `Error while executing chrome script '${aUrl}':\n` + 533 "The script doesn't exist. Ensure you have registered it in " + 534 "'support-files' in your mochitest.toml." 535 ); 536 } 537 538 return output; 539 } 540 541 loadChromeScript(urlOrFunction, sandboxOptions) { 542 // Create a unique id for this chrome script 543 let id = Services.uuid.generateUUID().toString(); 544 545 // Tells chrome code to evaluate this chrome script 546 let scriptArgs = { id, sandboxOptions }; 547 if (typeof urlOrFunction == "function") { 548 scriptArgs.function = { 549 body: "(" + urlOrFunction.toString() + ")();", 550 name: urlOrFunction.name, 551 }; 552 } else { 553 // Note: We need to do this in the child since, even though 554 // `_readUrlAsString` pretends to be synchronous, its channel 555 // winds up spinning the event loop when loading HTTP URLs. That 556 // leads to unexpected out-of-order operations if the child sends 557 // a message immediately after loading the script. 558 scriptArgs.function = { 559 body: this._readUrlAsString(urlOrFunction), 560 }; 561 scriptArgs.url = urlOrFunction; 562 } 563 this.sendAsyncMessage("SPLoadChromeScript", scriptArgs); 564 565 // Returns a MessageManager like API in order to be 566 // able to communicate with this chrome script 567 let listeners = []; 568 let chromeScript = { 569 addMessageListener: (name, listener) => { 570 listeners.push({ name, listener }); 571 }, 572 573 promiseOneMessage: name => 574 new Promise(resolve => { 575 chromeScript.addMessageListener(name, function listener(message) { 576 chromeScript.removeMessageListener(name, listener); 577 resolve(message); 578 }); 579 }), 580 581 removeMessageListener: (name, listener) => { 582 listeners = listeners.filter( 583 o => o.name != name || o.listener != listener 584 ); 585 }, 586 587 sendAsyncMessage: (name, message) => { 588 this.sendAsyncMessage("SPChromeScriptMessage", { id, name, message }); 589 }, 590 591 sendQuery: (name, message) => { 592 return this.sendQuery("SPChromeScriptMessage", { id, name, message }); 593 }, 594 595 destroy: () => { 596 listeners = []; 597 this._removeMessageListener("SPChromeScriptMessage", chromeScript); 598 }, 599 600 receiveMessage: aMessage => { 601 let messageId = aMessage.json.id; 602 let name = aMessage.json.name; 603 let message = aMessage.json.message; 604 if (this.contentWindow) { 605 message = new StructuredCloneHolder( 606 `SpecialPowers/receiveMessage/${name}`, 607 null, 608 message 609 ).deserialize(this.contentWindow); 610 } 611 // Ignore message from other chrome script 612 if (messageId != id) { 613 return null; 614 } 615 616 let result; 617 if (aMessage.name == "SPChromeScriptMessage") { 618 for (let listener of listeners.filter(o => o.name == name)) { 619 result = listener.listener(message); 620 } 621 } 622 return result; 623 }, 624 }; 625 this._addMessageListener("SPChromeScriptMessage", chromeScript); 626 627 return chromeScript; 628 } 629 630 get Services() { 631 return Services; 632 } 633 634 /* 635 * Convenient shortcuts to the standard Components abbreviations. 636 */ 637 get Cc() { 638 return Cc; 639 } 640 get Ci() { 641 return Ci; 642 } 643 get Cu() { 644 return Cu; 645 } 646 get Cr() { 647 return Cr; 648 } 649 650 get ChromeUtils() { 651 return ChromeUtils; 652 } 653 654 get isHeadless() { 655 return Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo).isHeadless; 656 } 657 658 get addProfilerMarker() { 659 return ChromeUtils.addProfilerMarker; 660 } 661 662 get DOMWindowUtils() { 663 return this.contentWindow.windowUtils; 664 } 665 666 getDOMWindowUtils(aWindow) { 667 if (aWindow == this.contentWindow) { 668 return aWindow.windowUtils; 669 } 670 671 return bindDOMWindowUtils(Cu.unwaiveXrays(aWindow)); 672 } 673 674 async toggleMuteState(aMuted, aWindow) { 675 let actor = aWindow 676 ? aWindow.windowGlobalChild.getActor("SpecialPowers") 677 : this; 678 return actor.sendQuery("SPToggleMuteAudio", { mute: aMuted }); 679 } 680 681 /* 682 * A method to get a DOMParser that can't parse XUL. 683 */ 684 getNoXULDOMParser() { 685 // If we create it with a system subject principal (so it gets a 686 // nullprincipal), it won't be able to parse XUL by default. 687 return new DOMParser(); 688 } 689 690 get InspectorUtils() { 691 return InspectorUtils; 692 } 693 694 get PromiseDebugging() { 695 return PromiseDebugging; 696 } 697 698 async waitForCrashes(aExpectingProcessCrash) { 699 if (!aExpectingProcessCrash) { 700 return; 701 } 702 703 var crashIds = this._encounteredCrashDumpFiles 704 .filter(filename => { 705 return filename.length === 40 && filename.endsWith(".dmp"); 706 }) 707 .map(id => { 708 return id.slice(0, -4); // Strip the .dmp extension to get the ID 709 }); 710 711 await this.sendQuery("SPProcessCrashManagerWait", { 712 crashIds, 713 }); 714 } 715 716 async removeExpectedCrashDumpFiles(aExpectingProcessCrash) { 717 var success = true; 718 if (aExpectingProcessCrash) { 719 var message = { 720 op: "delete-crash-dump-files", 721 filenames: this._encounteredCrashDumpFiles, 722 }; 723 if (!(await this.sendQuery("SPProcessCrashService", message))) { 724 success = false; 725 } 726 } 727 this._encounteredCrashDumpFiles.length = 0; 728 return success; 729 } 730 731 async findUnexpectedCrashDumpFiles() { 732 var self = this; 733 var message = { 734 op: "find-crash-dump-files", 735 crashDumpFilesToIgnore: this._unexpectedCrashDumpFiles, 736 }; 737 var crashDumpFiles = await this.sendQuery("SPProcessCrashService", message); 738 crashDumpFiles.forEach(function (aFilename) { 739 self._unexpectedCrashDumpFiles[aFilename] = true; 740 }); 741 // The value is an Array of strings. Export into the scope of the window to 742 // allow the caller to read its value without wrapper. Callers of 743 // findUnexpectedCrashDumpFiles will automatically get a wrapper; call 744 // SpecialPowers.unwrap() on its return value to access the raw value that 745 // we are returning here (see bug 2007587 for context). 746 crashDumpFiles = Cu.cloneInto(crashDumpFiles, this.contentWindow); 747 return crashDumpFiles; 748 } 749 750 removePendingCrashDumpFiles() { 751 var message = { 752 op: "delete-pending-crash-dump-files", 753 }; 754 return this.sendQuery("SPProcessCrashService", message); 755 } 756 757 _setTimeout(callback, delay = 0) { 758 // for mochitest-browser 759 if (typeof this.chromeWindow != "undefined") { 760 this.chromeWindow.setTimeout(callback, delay); 761 } 762 // for mochitest-plain 763 else { 764 this.contentWindow.setTimeout(callback, delay); 765 } 766 } 767 768 promiseTimeout(delay) { 769 return new Promise(resolve => { 770 this._setTimeout(resolve, delay); 771 }); 772 } 773 774 _delayCallbackTwice(callback) { 775 let delayedCallback = () => { 776 let delayAgain = aCallback => { 777 // Using this._setTimeout doesn't work here 778 // It causes failures in mochtests that use 779 // multiple pushPrefEnv calls 780 // For chrome/browser-chrome mochitests 781 this._setTimeout(aCallback); 782 }; 783 delayAgain(delayAgain.bind(this, callback)); 784 }; 785 return delayedCallback; 786 } 787 788 /* apply permissions to the system and when the test case is finished (SimpleTest.finish()) 789 we will revert the permission back to the original. 790 791 inPermissions is an array of objects where each object has a type, action, context, ex: 792 [{'type': 'SystemXHR', 'allow': 1, 'context': document}, 793 {'type': 'SystemXHR', 'allow': Ci.nsIPermissionManager.PROMPT_ACTION, 'context': document}] 794 795 Allow can be a boolean value of true/false or ALLOW_ACTION/DENY_ACTION/PROMPT_ACTION/UNKNOWN_ACTION 796 */ 797 async pushPermissions(inPermissions, callback) { 798 let permissions = []; 799 for (let perm of inPermissions) { 800 let principal = this._getPrincipalFromArg(perm.context); 801 permissions.push({ 802 ...perm, 803 context: null, 804 principal, 805 }); 806 } 807 808 await this.sendQuery("PushPermissions", permissions).then(callback); 809 await this.promiseTimeout(0); 810 } 811 812 async popPermissions(callback = null) { 813 await this.sendQuery("PopPermissions").then(callback); 814 await this.promiseTimeout(0); 815 } 816 817 async flushPermissions(callback = null) { 818 await this.sendQuery("FlushPermissions").then(callback); 819 await this.promiseTimeout(0); 820 } 821 822 /* 823 * This function should be used when specialpowers is in content process but 824 * it want to get the notification from chrome space. 825 * 826 * This function will call Services.obs.addObserver in SpecialPowersParent 827 * (that is in chrome process) and forward the data received to SpecialPowers 828 * via messageManager. 829 * You can use this._addMessageListener("specialpowers-YOUR_TOPIC") to fire 830 * the callback. 831 * 832 * To get the expected data, you should modify 833 * SpecialPowersParent.prototype._registerObservers.observe. Or the message 834 * you received from messageManager will only contain 'aData' from Service.obs. 835 */ 836 registerObservers(topic) { 837 var msg = { 838 op: "add", 839 observerTopic: topic, 840 }; 841 return this.sendQuery("SPObserverService", msg); 842 } 843 844 async pushPrefEnv(inPrefs, callback = null) { 845 let { requiresRefresh } = await this.sendQuery("PushPrefEnv", inPrefs); 846 if (callback) { 847 await callback(); 848 } 849 if (requiresRefresh) { 850 await this._promiseEarlyRefresh(); 851 } 852 } 853 854 async popPrefEnv(callback = null) { 855 let { popped, requiresRefresh } = await this.sendQuery("PopPrefEnv"); 856 if (callback) { 857 await callback(popped); 858 } 859 if (requiresRefresh) { 860 await this._promiseEarlyRefresh(); 861 } 862 } 863 864 async flushPrefEnv(callback = null) { 865 let { requiresRefresh } = await this.sendQuery("FlushPrefEnv"); 866 if (callback) { 867 await callback(); 868 } 869 if (requiresRefresh) { 870 await this._promiseEarlyRefresh(); 871 } 872 } 873 874 /* 875 Collect a snapshot of all preferences in Firefox (i.e. about:prefs). 876 From this, store the results within specialpowers for later reference. 877 */ 878 async getBaselinePrefs(callback = null) { 879 await this.sendQuery("getBaselinePrefs"); 880 if (callback) { 881 await callback(); 882 } 883 } 884 885 /* 886 This uses the stored prefs from getBaselinePrefs, collects a new snapshot 887 of preferences, then compares the new vs the baseline. If there are differences 888 they are recorded and returned as an array of preferences, in addition 889 all the changed preferences are reset to the value found in the baseline. 890 891 ignorePrefs: array of strings which are preferences. If they end in *, 892 we do a partial match 893 */ 894 async comparePrefsToBaseline(ignorePrefs, callback = null) { 895 let retVal = await this.sendQuery("comparePrefsToBaseline", ignorePrefs); 896 if (callback) { 897 callback(retVal); 898 } 899 return retVal; 900 } 901 902 _promiseEarlyRefresh() { 903 return new Promise(r => { 904 // for mochitest-browser 905 if (typeof this.chromeWindow != "undefined") { 906 this.chromeWindow.requestAnimationFrame(r); 907 } 908 // for mochitest-plain 909 else { 910 this.contentWindow.requestAnimationFrame(r); 911 } 912 }); 913 } 914 915 _addObserverProxy(notification) { 916 if (notification in this._proxiedObservers) { 917 this._addMessageListener( 918 notification, 919 this._proxiedObservers[notification] 920 ); 921 } 922 } 923 _removeObserverProxy(notification) { 924 if (notification in this._proxiedObservers) { 925 this._removeMessageListener( 926 notification, 927 this._proxiedObservers[notification] 928 ); 929 } 930 } 931 932 addObserver(obs, notification, weak) { 933 // Make sure the parent side exists, or we won't get any notifications. 934 this.sendAsyncMessage("Wakeup"); 935 936 this._addObserverProxy(notification); 937 obs = Cu.waiveXrays(obs); 938 if ( 939 typeof obs == "object" && 940 obs.observe.name != "SpecialPowersCallbackWrapper" 941 ) { 942 obs.observe = lazy.WrapPrivileged.wrapCallback( 943 Cu.unwaiveXrays(obs.observe), 944 this.contentWindow 945 ); 946 } 947 Services.obs.addObserver(obs, notification, weak); 948 } 949 removeObserver(obs, notification) { 950 this._removeObserverProxy(notification); 951 Services.obs.removeObserver(Cu.waiveXrays(obs), notification); 952 } 953 notifyObservers(subject, topic, data) { 954 Services.obs.notifyObservers(subject, topic, data); 955 } 956 957 /** 958 * An async observer is useful if you're listening for a 959 * notification that normally is only used by C++ code or chrome 960 * code (so it runs in the SystemGroup), but we need to know about 961 * it for a test (which runs as web content). If we used 962 * addObserver, we would assert when trying to enter web content 963 * from a runnabled labeled by the SystemGroup. An async observer 964 * avoids this problem. 965 */ 966 addAsyncObserver(obs, notification, weak) { 967 obs = Cu.waiveXrays(obs); 968 if ( 969 typeof obs == "object" && 970 obs.observe.name != "SpecialPowersCallbackWrapper" 971 ) { 972 obs.observe = lazy.WrapPrivileged.wrapCallback( 973 Cu.unwaiveXrays(obs.observe), 974 this.contentWindow 975 ); 976 } 977 let asyncObs = (...args) => { 978 Services.tm.dispatchToMainThread(() => { 979 if (typeof obs == "function") { 980 obs(...args); 981 } else { 982 obs.observe.call(undefined, ...args); 983 } 984 }); 985 }; 986 this._asyncObservers.set(obs, asyncObs); 987 Services.obs.addObserver(asyncObs, notification, weak); 988 } 989 removeAsyncObserver(obs, notification) { 990 let asyncObs = this._asyncObservers.get(Cu.waiveXrays(obs)); 991 Services.obs.removeObserver(asyncObs, notification); 992 } 993 994 can_QI(obj) { 995 return obj.QueryInterface !== undefined; 996 } 997 do_QueryInterface(obj, iface) { 998 return obj.QueryInterface(Ci[iface]); 999 } 1000 1001 call_Instanceof(obj1, obj2) { 1002 obj1 = lazy.WrapPrivileged.unwrap(obj1); 1003 obj2 = lazy.WrapPrivileged.unwrap(obj2); 1004 return obj1 instanceof obj2; 1005 } 1006 1007 // Returns a privileged getter from an object. GetOwnPropertyDescriptor does 1008 // not work here because xray wrappers don't properly implement it. 1009 // 1010 // This terribleness is used by dom/base/test/test_object.html because 1011 // <object> and <embed> tags will spawn plugins if their prototype is touched, 1012 // so we need to get and cache the getter of |hasRunningPlugin| if we want to 1013 // call it without paradoxically spawning the plugin. 1014 do_lookupGetter(obj, name) { 1015 return Object.prototype.__lookupGetter__.call(obj, name); 1016 } 1017 1018 // Mimic the get*Pref API 1019 getBoolPref(...args) { 1020 return Services.prefs.getBoolPref(...args); 1021 } 1022 getIntPref(...args) { 1023 return Services.prefs.getIntPref(...args); 1024 } 1025 getCharPref(...args) { 1026 return Services.prefs.getCharPref(...args); 1027 } 1028 getComplexValue(prefName, iid) { 1029 return Services.prefs.getComplexValue(prefName, iid); 1030 } 1031 getStringPref(...args) { 1032 return Services.prefs.getStringPref(...args); 1033 } 1034 1035 getParentBoolPref(prefName, defaultValue) { 1036 return this._getParentPref(prefName, "BOOL", { defaultValue }); 1037 } 1038 getParentIntPref(prefName, defaultValue) { 1039 return this._getParentPref(prefName, "INT", { defaultValue }); 1040 } 1041 getParentCharPref(prefName, defaultValue) { 1042 return this._getParentPref(prefName, "CHAR", { defaultValue }); 1043 } 1044 getParentStringPref(prefName, defaultValue) { 1045 return this._getParentPref(prefName, "STRING", { defaultValue }); 1046 } 1047 1048 // Mimic the set*Pref API 1049 setBoolPref(prefName, value) { 1050 return this._setPref(prefName, "BOOL", value); 1051 } 1052 setIntPref(prefName, value) { 1053 return this._setPref(prefName, "INT", value); 1054 } 1055 setCharPref(prefName, value) { 1056 return this._setPref(prefName, "CHAR", value); 1057 } 1058 setComplexValue(prefName, iid, value) { 1059 return this._setPref(prefName, "COMPLEX", value, iid); 1060 } 1061 setStringPref(prefName, value) { 1062 return this._setPref(prefName, "STRING", value); 1063 } 1064 1065 // Mimic the clearUserPref API 1066 clearUserPref(prefName) { 1067 let msg = { 1068 op: "clear", 1069 prefName, 1070 prefType: "", 1071 }; 1072 return this.sendQuery("SPPrefService", msg); 1073 } 1074 1075 // Private pref functions to communicate to chrome 1076 async _getParentPref(prefName, prefType, { defaultValue, iid }) { 1077 let msg = { 1078 op: "get", 1079 prefName, 1080 prefType, 1081 iid, // Only used with complex prefs 1082 defaultValue, // Optional default value 1083 }; 1084 let val = await this.sendQuery("SPPrefService", msg); 1085 if (val == null) { 1086 throw new Error(`Error getting pref '${prefName}'`); 1087 } 1088 return val; 1089 } 1090 _getPref(prefName, prefType) { 1091 switch (prefType) { 1092 case "BOOL": 1093 return Services.prefs.getBoolPref(prefName); 1094 case "INT": 1095 return Services.prefs.getIntPref(prefName); 1096 case "CHAR": 1097 return Services.prefs.getCharPref(prefName); 1098 case "STRING": 1099 return Services.prefs.getStringPref(prefName); 1100 } 1101 return undefined; 1102 } 1103 _setPref(prefName, prefType, prefValue, iid) { 1104 let msg = { 1105 op: "set", 1106 prefName, 1107 prefType, 1108 iid, // Only used with complex prefs 1109 prefValue, 1110 }; 1111 return this.sendQuery("SPPrefService", msg); 1112 } 1113 1114 _getMUDV(window) { 1115 return window.docShell.docViewer; 1116 } 1117 // XXX: these APIs really ought to be removed, they're not e10s-safe. 1118 // (also they're pretty Firefox-specific) 1119 _getTopChromeWindow(window) { 1120 return window.browsingContext.topChromeWindow; 1121 } 1122 _getAutoCompletePopup(window) { 1123 return this._getTopChromeWindow(window).document.getElementById( 1124 "PopupAutoComplete" 1125 ); 1126 } 1127 addAutoCompletePopupEventListener(window, eventname, listener) { 1128 this._getAutoCompletePopup(window).addEventListener(eventname, listener); 1129 } 1130 removeAutoCompletePopupEventListener(window, eventname, listener) { 1131 this._getAutoCompletePopup(window).removeEventListener(eventname, listener); 1132 } 1133 getFormFillController() { 1134 return Cc["@mozilla.org/satchel/form-fill-controller;1"].getService( 1135 Ci.nsIFormFillController 1136 ); 1137 } 1138 isBackButtonEnabled(window) { 1139 return !this._getTopChromeWindow(window) 1140 .document.getElementById("Browser:Back") 1141 .hasAttribute("disabled"); 1142 } 1143 // XXX end of problematic APIs 1144 1145 addChromeEventListener(type, listener, capture, allowUntrusted) { 1146 this.docShell.chromeEventHandler.addEventListener( 1147 type, 1148 listener, 1149 capture, 1150 allowUntrusted 1151 ); 1152 } 1153 removeChromeEventListener(type, listener, capture) { 1154 this.docShell.chromeEventHandler.removeEventListener( 1155 type, 1156 listener, 1157 capture 1158 ); 1159 } 1160 1161 async generateMediaControlKeyTestEvent(event) { 1162 await this.sendQuery("SPGenerateMediaControlKeyTestEvent", { event }); 1163 } 1164 1165 // Note: each call to registerConsoleListener MUST be paired with a 1166 // call to postConsoleSentinel; when the callback receives the 1167 // sentinel it will unregister itself (_after_ calling the 1168 // callback). SimpleTest.expectConsoleMessages does this for you. 1169 // If you register more than one console listener, a call to 1170 // postConsoleSentinel will zap all of them. 1171 registerConsoleListener(callback) { 1172 let listener = new SPConsoleListener(callback, this.contentWindow); 1173 Services.console.registerListener(listener); 1174 } 1175 postConsoleSentinel() { 1176 Services.console.logStringMessage("SENTINEL"); 1177 } 1178 resetConsole() { 1179 Services.console.reset(); 1180 } 1181 1182 getFullZoom(window) { 1183 return BrowsingContext.getFromWindow(window).fullZoom; 1184 } 1185 1186 getDeviceFullZoom(window) { 1187 return this._getMUDV(window).deviceFullZoomForTest; 1188 } 1189 setFullZoom(window, zoom) { 1190 BrowsingContext.getFromWindow(window).fullZoom = zoom; 1191 } 1192 getTextZoom(window) { 1193 return BrowsingContext.getFromWindow(window).textZoom; 1194 } 1195 setTextZoom(window, zoom) { 1196 BrowsingContext.getFromWindow(window).textZoom = zoom; 1197 } 1198 1199 emulateMedium(window, mediaType) { 1200 BrowsingContext.getFromWindow(window).top.mediumOverride = mediaType; 1201 } 1202 1203 stopEmulatingMedium(window) { 1204 BrowsingContext.getFromWindow(window).top.mediumOverride = ""; 1205 } 1206 1207 // Takes a snapshot of the given window and returns a <canvas> 1208 // containing the image. When the window is same-process, the canvas 1209 // is returned synchronously. When it is out-of-process (or when a 1210 // BrowsingContext or FrameLoaderOwner is passed instead of a Window), 1211 // a promise which resolves to such a canvas is returned instead. 1212 snapshotWindowWithOptions(content, rect, bgcolor, options) { 1213 function getImageData(rect, bgcolor, options) { 1214 let el = content.document.createElementNS( 1215 "http://www.w3.org/1999/xhtml", 1216 "canvas" 1217 ); 1218 if (rect === undefined) { 1219 rect = { 1220 top: content.scrollY, 1221 left: content.scrollX, 1222 width: content.innerWidth, 1223 height: content.innerHeight, 1224 }; 1225 } 1226 if (bgcolor === undefined) { 1227 bgcolor = "rgb(255,255,255)"; 1228 } 1229 if (options === undefined) { 1230 options = {}; 1231 } 1232 1233 el.width = rect.width; 1234 el.height = rect.height; 1235 let ctx = el.getContext("2d"); 1236 1237 let flags = 0; 1238 for (let option in options) { 1239 flags |= options[option] && ctx[option]; 1240 } 1241 1242 ctx.drawWindow( 1243 content, 1244 rect.left, 1245 rect.top, 1246 rect.width, 1247 rect.height, 1248 bgcolor, 1249 flags 1250 ); 1251 1252 return ctx.getImageData(0, 0, el.width, el.height); 1253 } 1254 1255 let toCanvas = imageData => { 1256 let el = this.document.createElementNS( 1257 "http://www.w3.org/1999/xhtml", 1258 "canvas" 1259 ); 1260 el.width = imageData.width; 1261 el.height = imageData.height; 1262 1263 if (ImageData.isInstance(imageData)) { 1264 let ctx = el.getContext("2d"); 1265 ctx.putImageData(imageData, 0, 0); 1266 } 1267 1268 return el; 1269 }; 1270 1271 if (!Cu.isRemoteProxy(content) && Window.isInstance(content)) { 1272 // Hack around tests that try to snapshot 0 width or height 1273 // elements. 1274 if (rect && !(rect.width && rect.height)) { 1275 return toCanvas(rect); 1276 } 1277 1278 // This is an in-process window. Snapshot it synchronously. 1279 return toCanvas(getImageData(rect, bgcolor, options)); 1280 } 1281 1282 // This is a remote window or frame. Snapshot it asynchronously and 1283 // return a promise for the result. Alas, consumers expect us to 1284 // return a <canvas> element rather than an ImageData object, so we 1285 // need to convert the result from the remote snapshot to a local 1286 // canvas. 1287 let promise = this.spawn( 1288 content, 1289 [rect, bgcolor, options], 1290 getImageData 1291 ).then(toCanvas); 1292 if (Cu.isXrayWrapper(this.contentWindow)) { 1293 return new this.contentWindow.Promise((resolve, reject) => { 1294 promise.then(resolve, reject); 1295 }); 1296 } 1297 return promise; 1298 } 1299 1300 snapshotWindow(win, withCaret, rect, bgcolor) { 1301 return this.snapshotWindowWithOptions(win, rect, bgcolor, { 1302 DRAWWINDOW_DRAW_CARET: withCaret, 1303 }); 1304 } 1305 1306 snapshotRect(win, rect, bgcolor) { 1307 return this.snapshotWindowWithOptions(win, rect, bgcolor); 1308 } 1309 1310 gc() { 1311 this.contentWindow.windowUtils.garbageCollect(); 1312 } 1313 1314 forceGC() { 1315 Cu.forceGC(); 1316 } 1317 1318 forceShrinkingGC() { 1319 Cu.forceShrinkingGC(); 1320 } 1321 1322 forceCC() { 1323 Cu.forceCC(); 1324 } 1325 1326 finishCC() { 1327 Cu.finishCC(); 1328 } 1329 1330 ccSlice(budget) { 1331 Cu.ccSlice(budget); 1332 } 1333 1334 // Due to various dependencies between JS objects and C++ objects, an ordinary 1335 // forceGC doesn't necessarily clear all unused objects, thus the GC and CC 1336 // needs to run several times and when no other JS is running. 1337 // The current number of iterations has been determined according to massive 1338 // cross platform testing. 1339 exactGC(callback) { 1340 let count = 0; 1341 1342 function genGCCallback(cb) { 1343 return function () { 1344 Cu.forceCC(); 1345 if (++count < 3) { 1346 Cu.schedulePreciseGC(genGCCallback(cb)); 1347 } else if (cb) { 1348 cb(); 1349 } 1350 }; 1351 } 1352 1353 Cu.schedulePreciseGC(genGCCallback(callback)); 1354 } 1355 1356 nondeterministicGetWeakMapKeys(m) { 1357 let keys = ChromeUtils.nondeterministicGetWeakMapKeys(m); 1358 if (!keys) { 1359 return undefined; 1360 } 1361 return this.contentWindow.Array.from(keys); 1362 } 1363 1364 getMemoryReports() { 1365 try { 1366 Cc["@mozilla.org/memory-reporter-manager;1"] 1367 .getService(Ci.nsIMemoryReporterManager) 1368 .getReports( 1369 () => {}, 1370 null, 1371 () => {}, 1372 null, 1373 false 1374 ); 1375 } catch (e) {} 1376 } 1377 1378 setGCZeal(zeal) { 1379 Cu.setGCZeal(zeal); 1380 } 1381 1382 isMainProcess() { 1383 try { 1384 return ( 1385 Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT 1386 ); 1387 } catch (e) {} 1388 return true; 1389 } 1390 1391 get XPCOMABI() { 1392 if (this._xpcomabi != null) { 1393 return this._xpcomabi; 1394 } 1395 1396 var xulRuntime = Services.appinfo.QueryInterface(Ci.nsIXULRuntime); 1397 1398 this._xpcomabi = xulRuntime.XPCOMABI; 1399 return this._xpcomabi; 1400 } 1401 1402 // The optional aWin parameter allows the caller to specify a given window in 1403 // whose scope the runnable should be dispatched. If aFun throws, the 1404 // exception will be reported to aWin. 1405 executeSoon(aFun, aWin) { 1406 // Create the runnable in the scope of aWin to avoid running into COWs. 1407 var runnable = {}; 1408 if (aWin) { 1409 runnable = Cu.createObjectIn(aWin); 1410 } 1411 runnable.run = aFun; 1412 Cu.dispatch(runnable, aWin); 1413 } 1414 1415 get OS() { 1416 if (this._os != null) { 1417 return this._os; 1418 } 1419 1420 this._os = Services.appinfo.OS; 1421 return this._os; 1422 } 1423 1424 get useRemoteSubframes() { 1425 return this.docShell.nsILoadContext.useRemoteSubframes; 1426 } 1427 1428 ISOLATION_STRATEGY = { 1429 IsolateNothing: 0, 1430 IsolateEverything: 1, 1431 IsolateHighValue: 2, 1432 }; 1433 1434 effectiveIsolationStrategy() { 1435 // If remote subframes are disabled, we always use the IsolateNothing strategy. 1436 if (!this.useRemoteSubframes) { 1437 return this.ISOLATION_STRATEGY.IsolateNothing; 1438 } 1439 return this.getIntPref("fission.webContentIsolationStrategy"); 1440 } 1441 1442 // helper method to check if the event is consumed by either default group's 1443 // event listener or system group's event listener. 1444 defaultPreventedInAnyGroup(event) { 1445 // FYI: Event.defaultPrevented returns false in content context if the 1446 // event is consumed only by system group's event listeners. 1447 return event.defaultPrevented; 1448 } 1449 1450 addCategoryEntry(category, entry, value, persists, replace) { 1451 Services.catMan.addCategoryEntry(category, entry, value, persists, replace); 1452 } 1453 1454 deleteCategoryEntry(category, entry, persists) { 1455 Services.catMan.deleteCategoryEntry(category, entry, persists); 1456 } 1457 openDialog(win, args) { 1458 return win.openDialog.apply(win, args); 1459 } 1460 // This is a blocking call which creates and spins a native event loop 1461 spinEventLoop(win) { 1462 // simply do a sync XHR back to our windows location. 1463 var syncXHR = new win.XMLHttpRequest(); 1464 syncXHR.open("GET", win.location, false); 1465 syncXHR.send(); 1466 } 1467 1468 // :jdm gets credit for this. ex: getPrivilegedProps(window, 'location.href'); 1469 getPrivilegedProps(obj, props) { 1470 var parts = props.split("."); 1471 for (var i = 0; i < parts.length; i++) { 1472 var p = parts[i]; 1473 if (obj[p] != undefined) { 1474 obj = obj[p]; 1475 } else { 1476 return null; 1477 } 1478 } 1479 return obj; 1480 } 1481 1482 _browsingContextForTarget(target) { 1483 if (BrowsingContext.isInstance(target)) { 1484 return target; 1485 } 1486 if (Element.isInstance(target)) { 1487 return target.browsingContext; 1488 } 1489 1490 return BrowsingContext.getFromWindow(target); 1491 } 1492 1493 getBrowsingContextID(target) { 1494 return this._browsingContextForTarget(target).id; 1495 } 1496 1497 *getGroupTopLevelWindows(target) { 1498 let { group } = this._browsingContextForTarget(target); 1499 for (let bc of group.getToplevels()) { 1500 yield bc.window; 1501 } 1502 } 1503 1504 /** 1505 * Runs a task in the context of the given frame, and returns a 1506 * promise which resolves to the return value of that task. 1507 * 1508 * The given frame may be in-process or out-of-process. Either way, 1509 * the task will run asynchronously, in a sandbox with access to the 1510 * frame's content window via its `content` global. Any arguments 1511 * passed will be copied via structured clone, as will its return 1512 * value. 1513 * 1514 * The sandbox also has access to an Assert object, as provided by 1515 * Assert.sys.mjs. Any assertion methods called before the task resolves 1516 * will be relayed back to the test environment of the caller. 1517 * Assertions triggered after a task returns may be relayed back if 1518 * setAsDefaultAssertHandler() has been called, until this SpecialPowers 1519 * instance is destroyed. 1520 * 1521 * If your assertions need to outlive this SpecialPowers instance, 1522 * use SpecialPowersForProcess from SpecialPowersProcessActor.sys.mjs, 1523 * which lives until the specified child process terminates. 1524 * 1525 * @param {BrowsingContext or FrameLoaderOwner or WindowProxy} target 1526 * The target in which to run the task. This may be any element 1527 * which implements the FrameLoaderOwner interface (including 1528 * HTML <iframe> elements and XUL <browser> elements) or a 1529 * WindowProxy (either in-process or remote). 1530 * @param {Array<any>} args 1531 * An array of arguments to pass to the task. All arguments 1532 * must be structured clone compatible, and will be cloned 1533 * before being passed to the task. 1534 * @param {function} task 1535 * The function to run in the context of the target. The 1536 * function will be stringified and re-evaluated in the context 1537 * of the target's content window. It may return any structured 1538 * clone compatible value, or a Promise which resolves to the 1539 * same, which will be returned to the caller. 1540 * 1541 * @returns {Promise<any>} 1542 * A promise which resolves to the return value of the task, or 1543 * which rejects if the task raises an exception. As this is 1544 * being written, the rejection value will always be undefined 1545 * in the cases where the task throws an error, though that may 1546 * change in the future. 1547 */ 1548 spawn(target, args, task) { 1549 let browsingContext = this._browsingContextForTarget(target); 1550 1551 return this.sendQuery("Spawn", { 1552 browsingContext, 1553 args, 1554 task: String(task), 1555 caller: Cu.getFunctionSourceLocation(task), 1556 hasHarness: 1557 typeof this.SimpleTest === "object" || 1558 typeof this.xpcshellScope === "object", 1559 imports: this._spawnTaskImports, 1560 }); 1561 } 1562 1563 /** 1564 * Like `spawn`, but spawns a chrome task in the parent process, 1565 * instead. The task additionally has access to `windowGlobalParent` 1566 * and `browsingContext` globals corresponding to the window from 1567 * which the task was spawned. 1568 */ 1569 spawnChrome(args, task) { 1570 return this.sendQuery("SpawnChrome", { 1571 args, 1572 task: String(task), 1573 caller: Cu.getFunctionSourceLocation(task), 1574 imports: this._spawnTaskImports, 1575 }); 1576 } 1577 1578 snapshotContext(target, rect, background, resetScrollPosition = false) { 1579 let browsingContext = this._browsingContextForTarget(target); 1580 1581 return this.sendQuery("Snapshot", { 1582 browsingContext, 1583 rect, 1584 background, 1585 resetScrollPosition, 1586 }).then(imageData => { 1587 return this.contentWindow.createImageBitmap(imageData); 1588 }); 1589 } 1590 1591 getSecurityState(target) { 1592 let browsingContext = this._browsingContextForTarget(target); 1593 1594 return this.sendQuery("SecurityState", { 1595 browsingContext, 1596 }); 1597 } 1598 1599 _spawnTask(task, args, caller, taskId, imports) { 1600 let sb = new lazy.SpecialPowersSandbox( 1601 null, 1602 data => { 1603 this.sendAsyncMessage("ProxiedAssert", { taskId, data }); 1604 }, 1605 { imports } 1606 ); 1607 1608 // If more variables are made available, don't forget to update 1609 // tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-content-task-globals.js. 1610 sb.sandbox.SpecialPowers = this; 1611 sb.sandbox.ContentTaskUtils = lazy.ContentTaskUtils; 1612 for (let [global, prop] of Object.entries({ 1613 content: "contentWindow", 1614 docShell: "docShell", 1615 })) { 1616 Object.defineProperty(sb.sandbox, global, { 1617 get: () => { 1618 return this[prop]; 1619 }, 1620 enumerable: true, 1621 }); 1622 } 1623 1624 return sb.execute(task, args, caller); 1625 } 1626 1627 /** 1628 * Automatically imports the given symbol from the given sys.mjs for any 1629 * task spawned by this SpecialPowers instance. 1630 */ 1631 addTaskImport(symbol, url) { 1632 this._spawnTaskImports[symbol] = url; 1633 } 1634 1635 get SimpleTest() { 1636 return this._SimpleTest || this.contentWindow.wrappedJSObject.SimpleTest; 1637 } 1638 set SimpleTest(val) { 1639 this._SimpleTest = val; 1640 } 1641 1642 get xpcshellScope() { 1643 return this._xpcshellScope; 1644 } 1645 set xpcshellScope(val) { 1646 this._xpcshellScope = val; 1647 } 1648 1649 async evictAllDocumentViewers() { 1650 if (Services.appinfo.sessionHistoryInParent) { 1651 await this.sendQuery("EvictAllDocumentViewers"); 1652 } else { 1653 this.browsingContext.top.childSessionHistory.legacySHistory.evictAllDocumentViewers(); 1654 } 1655 } 1656 1657 /** 1658 * Sets this actor as the default assertion result handler for tasks 1659 * which originate in a window without a test harness. 1660 */ 1661 setAsDefaultAssertHandler() { 1662 this.sendAsyncMessage("SetAsDefaultAssertHandler"); 1663 } 1664 1665 getFocusedElementForWindow(targetWindow, aDeep) { 1666 var outParam = {}; 1667 Services.focus.getFocusedElementForWindow(targetWindow, aDeep, outParam); 1668 return outParam.value; 1669 } 1670 1671 get focusManager() { 1672 return Services.focus; 1673 } 1674 1675 activeWindow() { 1676 return Services.focus.activeWindow; 1677 } 1678 1679 focusedWindow() { 1680 return Services.focus.focusedWindow; 1681 } 1682 1683 clearFocus(aWindow) { 1684 Services.focus.clearFocus(aWindow); 1685 } 1686 1687 focus(aWindow) { 1688 // This is called inside TestRunner._makeIframe without aWindow, because of assertions in oop mochitests 1689 // With aWindow, it is called in SimpleTest.waitForFocus to allow popup window opener focus switching 1690 if (aWindow) { 1691 aWindow.focus(); 1692 } 1693 1694 try { 1695 let actor = aWindow 1696 ? aWindow.windowGlobalChild.getActor("SpecialPowers") 1697 : this; 1698 actor.sendAsyncMessage("SpecialPowers.Focus", {}); 1699 } catch (e) { 1700 console.error(e); 1701 } 1702 } 1703 1704 ensureFocus(aBrowsingContext, aBlurSubframe) { 1705 return this.sendQuery("EnsureFocus", { 1706 browsingContext: aBrowsingContext, 1707 blurSubframe: aBlurSubframe, 1708 }); 1709 } 1710 1711 getClipboardData(flavor, whichClipboard) { 1712 if (whichClipboard === undefined) { 1713 whichClipboard = Services.clipboard.kGlobalClipboard; 1714 } 1715 1716 var xferable = Cc["@mozilla.org/widget/transferable;1"].createInstance( 1717 Ci.nsITransferable 1718 ); 1719 xferable.init(this.docShell); 1720 xferable.addDataFlavor(flavor); 1721 Services.clipboard.getData( 1722 xferable, 1723 whichClipboard, 1724 this.browsingContext.currentWindowContext 1725 ); 1726 var data = {}; 1727 try { 1728 xferable.getTransferData(flavor, data); 1729 } catch (e) {} 1730 data = data.value || null; 1731 if (data == null) { 1732 return ""; 1733 } 1734 1735 return data.QueryInterface(Ci.nsISupportsString).data; 1736 } 1737 1738 clipboardCopyString(str) { 1739 Cc["@mozilla.org/widget/clipboardhelper;1"] 1740 .getService(Ci.nsIClipboardHelper) 1741 .copyString(str); 1742 } 1743 1744 cleanupAllClipboard() { 1745 // copied from widget/tests/clipboard_helper.js 1746 // there is a write there I didn't want to copy 1747 const clipboard = Services.clipboard; 1748 const clipboardTypes = [ 1749 clipboard.kGlobalClipboard, 1750 clipboard.kSelectionClipboard, 1751 clipboard.kFindClipboard, 1752 clipboard.kSelectionCache, 1753 ]; 1754 1755 clipboardTypes.forEach(function (type) { 1756 if (clipboard.isClipboardTypeSupported(type)) { 1757 clipboard.emptyClipboard(type); 1758 } 1759 }); 1760 } 1761 1762 supportsSelectionClipboard() { 1763 return Services.clipboard.isClipboardTypeSupported( 1764 Services.clipboard.kSelectionClipboard 1765 ); 1766 } 1767 1768 swapFactoryRegistration(cid, contractID, newFactory) { 1769 newFactory = Cu.waiveXrays(newFactory); 1770 1771 var componentRegistrar = Components.manager.QueryInterface( 1772 Ci.nsIComponentRegistrar 1773 ); 1774 1775 var currentCID = componentRegistrar.contractIDToCID(contractID); 1776 var currentFactory = Components.manager.getClassObject( 1777 Cc[contractID], 1778 Ci.nsIFactory 1779 ); 1780 if (cid) { 1781 componentRegistrar.unregisterFactory(currentCID, currentFactory); 1782 } else { 1783 cid = Services.uuid.generateUUID(); 1784 } 1785 1786 // Restore the original factory. 1787 componentRegistrar.registerFactory(cid, "", contractID, newFactory); 1788 return { originalCID: currentCID }; 1789 } 1790 1791 _getElement(aWindow, id) { 1792 return typeof id == "string" ? aWindow.document.getElementById(id) : id; 1793 } 1794 1795 dispatchEvent(aWindow, target, event) { 1796 var el = this._getElement(aWindow, target); 1797 return el.dispatchEvent(event); 1798 } 1799 1800 get isDebugBuild() { 1801 return Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2) 1802 .isDebugBuild; 1803 } 1804 assertionCount() { 1805 var debugsvc = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2); 1806 return debugsvc.assertionCount; 1807 } 1808 1809 /** 1810 * @param arg one of the following: 1811 * - A URI string. 1812 * - A document node. 1813 * - A dictionary including a URL (`url`) and origin attributes (`attr`). 1814 */ 1815 _getPrincipalFromArg(arg) { 1816 arg = lazy.WrapPrivileged.unwrap(Cu.unwaiveXrays(arg)); 1817 1818 if (arg.nodePrincipal) { 1819 // It's a document. 1820 return arg.nodePrincipal; 1821 } 1822 1823 let secMan = Services.scriptSecurityManager; 1824 if (typeof arg == "string") { 1825 // It's a URL. 1826 let uri = Services.io.newURI(arg); 1827 return secMan.createContentPrincipal(uri, {}); 1828 } 1829 1830 let uri = Services.io.newURI(arg.url); 1831 let attrs = arg.originAttributes || {}; 1832 return secMan.createContentPrincipal(uri, attrs); 1833 } 1834 1835 async addPermission(type, allow, arg, expireType, expireTime) { 1836 let principal = this._getPrincipalFromArg(arg); 1837 if (principal.isSystemPrincipal) { 1838 return; // nothing to do 1839 } 1840 1841 let permission = allow; 1842 if (typeof permission === "boolean") { 1843 permission = 1844 Ci.nsIPermissionManager[allow ? "ALLOW_ACTION" : "DENY_ACTION"]; 1845 } 1846 1847 var msg = { 1848 op: "add", 1849 type, 1850 permission, 1851 principal, 1852 expireType: typeof expireType === "number" ? expireType : 0, 1853 expireTime: typeof expireTime === "number" ? expireTime : 0, 1854 }; 1855 1856 await this.sendQuery("SPPermissionManager", msg); 1857 } 1858 1859 /** 1860 * @param type see nsIPermissionsManager::testPermissionFromPrincipal. 1861 * @param arg one of the following: 1862 * - A URI string. 1863 * - A document node. 1864 * - A dictionary including a URL (`url`) and origin attributes (`attr`). 1865 */ 1866 async removePermission(type, arg) { 1867 let principal = this._getPrincipalFromArg(arg); 1868 if (principal.isSystemPrincipal) { 1869 return; // nothing to do 1870 } 1871 1872 var msg = { 1873 op: "remove", 1874 type, 1875 principal, 1876 }; 1877 1878 await this.sendQuery("SPPermissionManager", msg); 1879 } 1880 1881 async hasPermission(type, arg) { 1882 let principal = this._getPrincipalFromArg(arg); 1883 if (principal.isSystemPrincipal) { 1884 return true; // system principals have all permissions 1885 } 1886 1887 var msg = { 1888 op: "has", 1889 type, 1890 principal, 1891 }; 1892 1893 return this.sendQuery("SPPermissionManager", msg); 1894 } 1895 1896 async testPermission(type, value, arg) { 1897 let principal = this._getPrincipalFromArg(arg); 1898 if (principal.isSystemPrincipal) { 1899 return true; // system principals have all permissions 1900 } 1901 1902 var msg = { 1903 op: "test", 1904 type, 1905 value, 1906 principal, 1907 }; 1908 return this.sendQuery("SPPermissionManager", msg); 1909 } 1910 1911 isContentWindowPrivate(win) { 1912 return lazy.PrivateBrowsingUtils.isContentWindowPrivate(win); 1913 } 1914 1915 async notifyObserversInParentProcess(subject, topic, data) { 1916 if (subject) { 1917 throw new Error("Can't send subject to another process!"); 1918 } 1919 if (this.isMainProcess()) { 1920 this.notifyObservers(subject, topic, data); 1921 return; 1922 } 1923 var msg = { 1924 op: "notify", 1925 observerTopic: topic, 1926 observerData: data, 1927 }; 1928 await this.sendQuery("SPObserverService", msg); 1929 } 1930 1931 removeAllServiceWorkerData() { 1932 return this.sendQuery("SPRemoveAllServiceWorkers", {}); 1933 } 1934 1935 removeServiceWorkerDataForExampleDomain() { 1936 return this.sendQuery("SPRemoveServiceWorkerDataForExampleDomain", {}); 1937 } 1938 1939 cleanUpSTSData(origin) { 1940 return this.sendQuery("SPCleanUpSTSData", { origin }); 1941 } 1942 1943 async requestDumpCoverageCounters() { 1944 // We want to avoid a roundtrip between child and parent. 1945 if (!lazy.PerTestCoverageUtils.enabled) { 1946 return; 1947 } 1948 1949 await this.sendQuery("SPRequestDumpCoverageCounters", {}); 1950 } 1951 1952 async requestResetCoverageCounters() { 1953 // We want to avoid a roundtrip between child and parent. 1954 if (!lazy.PerTestCoverageUtils.enabled) { 1955 return; 1956 } 1957 await this.sendQuery("SPRequestResetCoverageCounters", {}); 1958 } 1959 1960 loadExtension(ext, handler) { 1961 if (this._extensionListeners == null) { 1962 this._extensionListeners = new Set(); 1963 1964 this._addMessageListener("SPExtensionMessage", msg => { 1965 for (let listener of this._extensionListeners) { 1966 try { 1967 listener(msg); 1968 } catch (e) { 1969 console.error(e); 1970 } 1971 } 1972 }); 1973 } 1974 1975 // Note, this is not the addon is as used by the AddonManager etc, 1976 // this is just an identifier used for specialpowers messaging 1977 // between this content process and the chrome process. 1978 let id = this._nextExtensionID++; 1979 1980 handler = Cu.waiveXrays(handler); 1981 ext = Cu.waiveXrays(ext); 1982 1983 let sp = this; 1984 let state = "uninitialized"; 1985 let extension = { 1986 get state() { 1987 return state; 1988 }, 1989 1990 startup() { 1991 state = "pending"; 1992 return sp.sendQuery("SPStartupExtension", { id }).then( 1993 () => { 1994 state = "running"; 1995 }, 1996 () => { 1997 state = "failed"; 1998 sp._extensionListeners.delete(listener); 1999 return Promise.reject("startup failed"); 2000 } 2001 ); 2002 }, 2003 2004 unload() { 2005 state = "unloading"; 2006 return sp.sendQuery("SPUnloadExtension", { id }).finally(() => { 2007 sp._extensionListeners.delete(listener); 2008 state = "unloaded"; 2009 }); 2010 }, 2011 2012 sendMessage(...args) { 2013 sp.sendAsyncMessage("SPExtensionMessage", { id, args }); 2014 }, 2015 2016 grantActiveTab(tabId) { 2017 sp.sendAsyncMessage("SPExtensionGrantActiveTab", { id, tabId }); 2018 }, 2019 2020 terminateBackground(...args) { 2021 return sp.sendQuery("SPExtensionTerminateBackground", { id, args }); 2022 }, 2023 2024 wakeupBackground() { 2025 return sp.sendQuery("SPExtensionWakeupBackground", { id }); 2026 }, 2027 }; 2028 2029 this.sendAsyncMessage("SPLoadExtension", { ext, id }); 2030 2031 let listener = msg => { 2032 if (msg.data.id == id) { 2033 if (msg.data.type == "extensionSetId") { 2034 extension.id = msg.data.args[0]; 2035 extension.uuid = msg.data.args[1]; 2036 } else if (msg.data.type in handler) { 2037 handler[msg.data.type]( 2038 ...Cu.cloneInto(msg.data.args, this.contentWindow) 2039 ); 2040 } else { 2041 dump(`Unexpected: ${msg.data.type}\n`); 2042 } 2043 } 2044 }; 2045 2046 this._extensionListeners.add(listener); 2047 return extension; 2048 } 2049 2050 invalidateExtensionStorageCache() { 2051 this.notifyObserversInParentProcess( 2052 null, 2053 "extension-invalidate-storage-cache", 2054 "" 2055 ); 2056 } 2057 2058 allowMedia(window, enable) { 2059 window.docShell.allowMedia = enable; 2060 } 2061 2062 createChromeCache(name, url) { 2063 let principal = this._getPrincipalFromArg(url); 2064 return new this.contentWindow.CacheStorage(name, principal); 2065 } 2066 2067 loadChannelAndReturnStatus(url, loadUsingSystemPrincipal) { 2068 const BinaryInputStream = Components.Constructor( 2069 "@mozilla.org/binaryinputstream;1", 2070 "nsIBinaryInputStream", 2071 "setInputStream" 2072 ); 2073 2074 return new Promise(function (resolve) { 2075 let listener = { 2076 httpStatus: 0, 2077 2078 onStartRequest(request) { 2079 request.QueryInterface(Ci.nsIHttpChannel); 2080 this.httpStatus = request.responseStatus; 2081 }, 2082 2083 onDataAvailable(request, stream, offset, count) { 2084 new BinaryInputStream(stream).readByteArray(count); 2085 }, 2086 2087 onStopRequest(request, status) { 2088 /* testing here that the redirect was not followed. If it was followed 2089 we would see a http status of 200 and status of NS_OK */ 2090 2091 let httpStatus = this.httpStatus; 2092 resolve({ status, httpStatus }); 2093 }, 2094 }; 2095 let uri = lazy.NetUtil.newURI(url); 2096 let channel = lazy.NetUtil.newChannel({ uri, loadUsingSystemPrincipal }); 2097 2098 channel.loadFlags |= Ci.nsIChannel.LOAD_DOCUMENT_URI; 2099 channel.QueryInterface(Ci.nsIHttpChannelInternal); 2100 channel.documentURI = uri; 2101 channel.asyncOpen(listener); 2102 }); 2103 } 2104 2105 get ParserUtils() { 2106 if (this._pu != null) { 2107 return this._pu; 2108 } 2109 2110 let pu = Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils); 2111 // We need to create and return our own wrapper. 2112 this._pu = { 2113 sanitize(src, flags) { 2114 return pu.sanitize(src, flags); 2115 }, 2116 convertToPlainText(src, flags, wrapCol) { 2117 return pu.convertToPlainText(src, flags, wrapCol); 2118 }, 2119 parseFragment(fragment, flags, isXML, baseURL, element) { 2120 let baseURI = baseURL ? lazy.NetUtil.newURI(baseURL) : null; 2121 return pu.parseFragment( 2122 lazy.WrapPrivileged.unwrap(fragment), 2123 flags, 2124 isXML, 2125 baseURI, 2126 lazy.WrapPrivileged.unwrap(element) 2127 ); 2128 }, 2129 }; 2130 return this._pu; 2131 } 2132 2133 createDOMWalker(node, showAnonymousContent) { 2134 node = lazy.WrapPrivileged.unwrap(node); 2135 let walker = Cc["@mozilla.org/inspector/deep-tree-walker;1"].createInstance( 2136 Ci.inIDeepTreeWalker 2137 ); 2138 walker.showAnonymousContent = showAnonymousContent; 2139 walker.init(node.ownerDocument, NodeFilter.SHOW_ALL); 2140 walker.currentNode = node; 2141 let contentWindow = this.contentWindow; 2142 return { 2143 get firstChild() { 2144 return lazy.WrapPrivileged.wrap(walker.firstChild(), contentWindow); 2145 }, 2146 get lastChild() { 2147 return lazy.WrapPrivileged.wrap(walker.lastChild(), contentWindow); 2148 }, 2149 }; 2150 } 2151 2152 /** 2153 * Which commands are available can be determined by checking which commands 2154 * are registered. See \ref 2155 * nsIControllerCommandTable.registerCommand(in String, in nsIControllerCommand). 2156 */ 2157 doCommand(window, cmd, param) { 2158 switch (cmd) { 2159 case "cmd_align": 2160 case "cmd_backgroundColor": 2161 case "cmd_fontColor": 2162 case "cmd_fontFace": 2163 case "cmd_fontSize": 2164 case "cmd_highlight": 2165 case "cmd_insertImageNoUI": 2166 case "cmd_insertLinkNoUI": 2167 case "cmd_paragraphState": { 2168 const params = Cu.createCommandParams(); 2169 params.setStringValue("state_attribute", param); 2170 return window.docShell.doCommandWithParams(cmd, params); 2171 } 2172 case "cmd_pasteTransferable": { 2173 const params = Cu.createCommandParams(); 2174 params.setISupportsValue("transferable", param); 2175 return window.docShell.doCommandWithParams(cmd, params); 2176 } 2177 default: 2178 return window.docShell.doCommand(cmd); 2179 } 2180 } 2181 2182 isCommandEnabled(window, cmd) { 2183 return window.docShell.isCommandEnabled(cmd); 2184 } 2185 2186 /** 2187 * See \ref nsIDocumentViewerEdit.setCommandNode(in Node). 2188 */ 2189 setCommandNode(window, node) { 2190 return window.docShell.docViewer 2191 .QueryInterface(Ci.nsIDocumentViewerEdit) 2192 .setCommandNode(node); 2193 } 2194 2195 /* Bug 1339006 Runnables of nsIURIClassifier.classify may be labeled by 2196 * SystemGroup, but some test cases may run as web content. That would assert 2197 * when trying to enter web content from a runnable labeled by the 2198 * SystemGroup. To avoid that, we run classify from SpecialPowers which is 2199 * chrome-privileged and allowed to run inside SystemGroup 2200 */ 2201 2202 doUrlClassify(principal, callback) { 2203 let classifierService = Cc[ 2204 "@mozilla.org/url-classifier/dbservice;1" 2205 ].getService(Ci.nsIURIClassifier); 2206 2207 let wrapCallback = (...args) => { 2208 Services.tm.dispatchToMainThread(() => { 2209 if (typeof callback == "function") { 2210 callback(...args); 2211 } else { 2212 callback.onClassifyComplete.call(undefined, ...args); 2213 } 2214 }); 2215 }; 2216 2217 return classifierService.classify( 2218 lazy.WrapPrivileged.unwrap(principal), 2219 wrapCallback 2220 ); 2221 } 2222 2223 // TODO: Bug 1353701 - Supports custom event target for labelling. 2224 doUrlClassifyLocal(uri, tables, callback) { 2225 let classifierService = Cc[ 2226 "@mozilla.org/url-classifier/dbservice;1" 2227 ].getService(Ci.nsIURIClassifier); 2228 2229 let wrapCallback = results => { 2230 Services.tm.dispatchToMainThread(() => { 2231 if (typeof callback == "function") { 2232 callback(lazy.WrapPrivileged.wrap(results, this.contentWindow)); 2233 } else { 2234 callback.onClassifyComplete.call( 2235 undefined, 2236 lazy.WrapPrivileged.wrap(results, this.contentWindow) 2237 ); 2238 } 2239 }); 2240 }; 2241 2242 let feature = classifierService.createFeatureWithTables( 2243 "test", 2244 tables.split(","), 2245 [] 2246 ); 2247 return classifierService.asyncClassifyLocalWithFeatures( 2248 lazy.WrapPrivileged.unwrap(uri), 2249 [feature], 2250 Ci.nsIUrlClassifierFeature.blocklist, 2251 wrapCallback 2252 ); 2253 } 2254 2255 /* Content processes asynchronously receive child-to-parent transformations 2256 * when they are launched. Until they are received, screen coordinates 2257 * reported to JS are wrong. This is generally ok. It behaves as if the 2258 * user repositioned the window. But if we want to test screen coordinates, 2259 * we need to wait for the updated data. 2260 */ 2261 contentTransformsReceived(win) { 2262 while (win) { 2263 try { 2264 return win.docShell.browserChild.contentTransformsReceived(); 2265 } catch (ex) { 2266 // browserChild getter throws on non-e10s rather than returning null. 2267 } 2268 if (win == win.parent) { 2269 break; 2270 } 2271 win = win.parent; 2272 } 2273 return Promise.resolve(); 2274 } 2275 } 2276 2277 SpecialPowersChild.prototype._proxiedObservers = { 2278 "specialpowers-http-notify-request": function (aMessage) { 2279 let uri = aMessage.json.uri; 2280 Services.obs.notifyObservers( 2281 null, 2282 "specialpowers-http-notify-request", 2283 uri 2284 ); 2285 }, 2286 2287 "specialpowers-service-worker-shutdown": function () { 2288 Services.obs.notifyObservers(null, "specialpowers-service-worker-shutdown"); 2289 }, 2290 2291 "specialpowers-csp-on-violate-policy": function (aMessage) { 2292 let subject = null; 2293 2294 try { 2295 subject = Services.io.newURI(aMessage.data.subject); 2296 } catch (ex) { 2297 // if it's not a valid URI it must be an nsISupportsCString 2298 subject = Cc["@mozilla.org/supports-cstring;1"].createInstance( 2299 Ci.nsISupportsCString 2300 ); 2301 subject.data = aMessage.data.subject; 2302 } 2303 Services.obs.notifyObservers( 2304 subject, 2305 "specialpowers-csp-on-violate-policy", 2306 aMessage.data.data 2307 ); 2308 }, 2309 2310 "specialpowers-xfo-on-violate-policy": function (aMessage) { 2311 let subject = Services.io.newURI(aMessage.data.subject); 2312 Services.obs.notifyObservers( 2313 subject, 2314 "specialpowers-xfo-on-violate-policy", 2315 aMessage.data.data 2316 ); 2317 }, 2318 };