Bug 1561705: Part 5b - Merge SpecialPowersAPIParent.jsm and SpecialPowersParent.jsm. r=mccr8
authorKris Maglione <maglione.k@gmail.com>
Wed, 14 Aug 2019 16:41:41 -0700
changeset 488959 72315ad35cbfe2afce8a22ee518bb5cfae1b2d73
parent 488958 caa59e50a7b4db66d26564055e7437a85f535a2b
child 488960 f222dd497eb63df32feb4bba85ed54f966cee296
push id113932
push usermaglione.k@gmail.com
push dateTue, 20 Aug 2019 20:03:27 +0000
treeherdermozilla-inbound@1f836a6215ab [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmccr8
bugs1561705
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1561705: Part 5b - Merge SpecialPowersAPIParent.jsm and SpecialPowersParent.jsm. r=mccr8 Differential Revision: https://phabricator.services.mozilla.com/D42182
testing/specialpowers/content/SpecialPowersAPIParent.jsm
testing/specialpowers/content/SpecialPowersParent.jsm
testing/specialpowers/moz.build
rename from testing/specialpowers/content/SpecialPowersAPIParent.jsm
rename to testing/specialpowers/content/SpecialPowersParent.jsm
--- a/testing/specialpowers/content/SpecialPowersAPIParent.jsm
+++ b/testing/specialpowers/content/SpecialPowersParent.jsm
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-var EXPORTED_SYMBOLS = ["SpecialPowersAPIParent", "SpecialPowersError"];
+var EXPORTED_SYMBOLS = ["SpecialPowersParent"];
 
 var { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   ExtensionData: "resource://gre/modules/Extension.jsm",
@@ -114,40 +114,131 @@ function doPrefEnvOp(fn) {
     inPrefEnvOp = false;
   }
 }
 
 // Supplies the unique IDs for tasks created by SpecialPowers.spawn(),
 // used to bounce assertion messages back down to the correct child.
 let nextTaskID = 1;
 
-class SpecialPowersAPIParent extends JSWindowActorParent {
+class SpecialPowersParent extends JSWindowActorParent {
   constructor() {
     super();
+
+    this._messageManager = Services.mm;
+    this._serviceWorkerListener = null;
+
+    this._observer = this.observe.bind(this);
+
+    this.didDestroy = this.uninit.bind(this);
+
+    this._registerObservers = {
+      _self: this,
+      _topics: [],
+      _add(topic) {
+        if (!this._topics.includes(topic)) {
+          this._topics.push(topic);
+          Services.obs.addObserver(this, topic);
+        }
+      },
+      observe(aSubject, aTopic, aData) {
+        var msg = { aData };
+        switch (aTopic) {
+          case "perm-changed":
+            var permission = aSubject.QueryInterface(Ci.nsIPermission);
+
+            // specialPowersAPI will consume this value, and it is used as a
+            // fake permission, but only type will be used.
+            //
+            // We need to ensure that it looks the same as a real permission,
+            // so we fake these properties.
+            msg.permission = {
+              principal: {
+                originAttributes: {},
+              },
+              type: permission.type,
+            };
+          // fall through
+          default:
+            this._self.sendAsyncMessage("specialpowers-" + aTopic, msg);
+        }
+      },
+    };
+
+    this.init();
+
     this._crashDumpDir = null;
     this._processCrashObserversRegistered = false;
     this._chromeScriptListeners = [];
     this._extensions = new Map();
     this._taskActors = new Map();
   }
 
-  _observe(aSubject, aTopic, aData) {
+  init() {
+    Services.obs.addObserver(this._observer, "http-on-modify-request");
+
+    // We would like to check that tests don't leave service workers around
+    // after they finish, but that information lives in the parent process.
+    // Ideally, we'd be able to tell the child processes whenever service
+    // workers are registered or unregistered so they would know at all times,
+    // but service worker lifetimes are complicated enough to make that
+    // difficult. For the time being, let the child process know when a test
+    // registers a service worker so it can ask, synchronously, at the end if
+    // the service worker had unregister called on it.
+    let swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
+      Ci.nsIServiceWorkerManager
+    );
+    let self = this;
+    this._serviceWorkerListener = {
+      onRegister() {
+        self.onRegister();
+      },
+
+      onUnregister() {
+        // no-op
+      },
+    };
+    swm.addListener(this._serviceWorkerListener);
+  }
+
+  uninit() {
+    var obs = Services.obs;
+    obs.removeObserver(this._observer, "http-on-modify-request");
+    this._registerObservers._topics.splice(0).forEach(element => {
+      obs.removeObserver(this._registerObservers, element);
+    });
+    this._removeProcessCrashObservers();
+
+    let swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
+      Ci.nsIServiceWorkerManager
+    );
+    swm.removeListener(this._serviceWorkerListener);
+  }
+
+  observe(aSubject, aTopic, aData) {
     function addDumpIDToMessage(propertyName) {
       try {
         var id = aSubject.getPropertyAsAString(propertyName);
       } catch (ex) {
         id = null;
       }
       if (id) {
         message.dumpIDs.push({ id, extension: "dmp" });
         message.dumpIDs.push({ id, extension: "extra" });
       }
     }
 
     switch (aTopic) {
+      case "http-on-modify-request":
+        if (aSubject instanceof Ci.nsIChannel) {
+          let uri = aSubject.URI.spec;
+          this.sendAsyncMessage("specialpowers-http-notify-request", { uri });
+        }
+        break;
+
       case "plugin-crashed":
       case "ipc:content-shutdown":
         var message = { type: "crash-observed", dumpIDs: [] };
         aSubject = aSubject.QueryInterface(Ci.nsIPropertyBag2);
         if (aTopic == "plugin-crashed") {
           addDumpIDToMessage("pluginDumpID");
           addDumpIDToMessage("browserDumpID");
 
@@ -249,16 +340,40 @@ class SpecialPowersAPIParent extends JSW
           file.remove(false);
           removed = true;
         }
       }
     }
     return removed;
   }
 
+  _addProcessCrashObservers() {
+    if (this._processCrashObserversRegistered) {
+      return;
+    }
+
+    Services.obs.addObserver(this._observer, "plugin-crashed");
+    Services.obs.addObserver(this._observer, "ipc:content-shutdown");
+    this._processCrashObserversRegistered = true;
+  }
+
+  _removeProcessCrashObservers() {
+    if (!this._processCrashObserversRegistered) {
+      return;
+    }
+
+    Services.obs.removeObserver(this._observer, "plugin-crashed");
+    Services.obs.removeObserver(this._observer, "ipc:content-shutdown");
+    this._processCrashObserversRegistered = false;
+  }
+
+  onRegister() {
+    this.sendAsyncMessage("SPServiceWorkerRegistered", { registered: true });
+  }
+
   _getURI(url) {
     return Services.io.newURI(url);
   }
   _notifyCategoryAndObservers(subject, topic, data) {
     const serviceMarker = "service,";
 
     // First create observers from the category manager.
 
@@ -465,16 +580,89 @@ class SpecialPowersAPIParent extends JSW
   // eslint-disable-next-line complexity
   receiveMessage(aMessage) {
     // We explicitly return values in the below code so that this function
     // doesn't trigger a flurry of warnings about "does not always return
     // a value".
     switch (aMessage.name) {
       case "SPToggleMuteAudio":
         return this._toggleMuteAudio(aMessage.data.mute);
+
+      case "Ping":
+        return undefined;
+
+      case "SpecialPowers.Quit":
+        Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
+        return undefined;
+
+      case "SpecialPowers.Focus":
+        this.manager.rootFrameLoader.ownerElement.focus();
+        return undefined;
+
+      case "SpecialPowers.CreateFiles":
+        return (async () => {
+          let filePaths = [];
+          if (!this._createdFiles) {
+            this._createdFiles = [];
+          }
+          let createdFiles = this._createdFiles;
+
+          let promises = [];
+          aMessage.data.forEach(function(request) {
+            const filePerms = 0o666;
+            let testFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
+            if (request.name) {
+              testFile.appendRelativePath(request.name);
+            } else {
+              testFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, filePerms);
+            }
+            let outStream = Cc[
+              "@mozilla.org/network/file-output-stream;1"
+            ].createInstance(Ci.nsIFileOutputStream);
+            outStream.init(
+              testFile,
+              0x02 | 0x08 | 0x20, // PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE
+              filePerms,
+              0
+            );
+            if (request.data) {
+              outStream.write(request.data, request.data.length);
+            }
+            outStream.close();
+            promises.push(
+              File.createFromFileName(testFile.path, request.options).then(
+                function(file) {
+                  filePaths.push(file);
+                }
+              )
+            );
+            createdFiles.push(testFile);
+          });
+
+          await Promise.all(promises);
+          return filePaths;
+        })().catch(e => {
+          Cu.reportError(e);
+          return Promise.reject(String(e));
+        });
+
+      case "SpecialPowers.RemoveFiles":
+        if (this._createdFiles) {
+          this._createdFiles.forEach(function(testFile) {
+            try {
+              testFile.remove(false);
+            } catch (e) {}
+          });
+          this._createdFiles = null;
+        }
+        return undefined;
+
+      case "Wakeup":
+        return undefined;
+
       case "PushPrefEnv":
         return this.pushPrefEnv(aMessage.data);
 
       case "PopPrefEnv":
         return this.popPrefEnv();
 
       case "FlushPrefEnv":
         return this.flushPrefEnv();
@@ -904,23 +1092,17 @@ class SpecialPowersAPIParent extends JSW
       case "SPRemoveAllServiceWorkers": {
         return ServiceWorkerCleanUp.removeAll();
       }
 
       case "SPRemoveServiceWorkerDataForExampleDomain": {
         return ServiceWorkerCleanUp.removeFromHost("example.com");
       }
 
-      case "Wakeup":
-        return undefined;
-
       default:
         throw new SpecialPowersError(
           `Unrecognized Special Powers API: ${aMessage.name}`
         );
     }
-
-    // We throw an exception before reaching this explicit return because
-    // we should never be arriving here anyway.
-    throw new SpecialPowersError("Unreached code"); // eslint-disable-line no-unreachable
-    return undefined;
+    // This should be unreachable. If it ever becomes reachable, ESLint
+    // will produce an error about inconsistent return values.
   }
 }
--- a/testing/specialpowers/moz.build
+++ b/testing/specialpowers/moz.build
@@ -14,17 +14,16 @@ FINAL_TARGET_FILES += [
     'schema.json',
 ]
 
 FINAL_TARGET_FILES.content += [
     '../modules/Assert.jsm',
     'content/MockColorPicker.jsm',
     'content/MockFilePicker.jsm',
     'content/MockPermissionPrompt.jsm',
-    'content/SpecialPowersAPIParent.jsm',
     'content/SpecialPowersChild.jsm',
     'content/SpecialPowersParent.jsm',
     'content/SpecialPowersSandbox.jsm',
     'content/WrapPrivileged.jsm',
 ]
 
 with Files("**"):
     BUG_COMPONENT = ("Testing", "Mochitest")