Bug 1451463: Make Quitter a WebExtension experiment. r=aswan
authorKris Maglione <maglione.k@gmail.com>
Tue, 03 Apr 2018 17:19:26 -0700
changeset 503867 639212b42347d02e5e3c414d83657137b3affb53
parent 503866 22ce80311bcfebe82ed7ed65a6831836a71313c7
child 503868 4f1e92f9db113b89425adff91c17c0e5ac153f9a
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1451463
milestone65.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 1451463: Make Quitter a WebExtension experiment. r=aswan Differential Revision: https://phabricator.services.mozilla.com/D12478
toolkit/components/extensions/WebExtensionPolicy.cpp
tools/quitter/.eslintrc.js
tools/quitter/background.js
tools/quitter/bootstrap.js
tools/quitter/chrome.manifest
tools/quitter/contentscript.js
tools/quitter/install.rdf
tools/quitter/jar.mn
tools/quitter/manifest.json
tools/quitter/moz.build
tools/quitter/parent.js
tools/quitter/quitter@mozilla.org.xpi
tools/quitter/schema.json
--- a/toolkit/components/extensions/WebExtensionPolicy.cpp
+++ b/toolkit/components/extensions/WebExtensionPolicy.cpp
@@ -612,17 +612,18 @@ MozDocumentMatcher::Matches(const DocInf
   if (!mMatchAboutBlank && aDoc.URL().InheritsPrincipal()) {
     return false;
   }
 
   // Top-level about:blank is a special case. We treat it as a match if
   // matchAboutBlank is true and it has the null principal. In all other
   // cases, we test the URL of the principal that it inherits.
   if (mMatchAboutBlank && aDoc.IsTopLevel() &&
-      aDoc.URL().Spec().EqualsLiteral("about:blank") &&
+      (aDoc.URL().Spec().EqualsLiteral("about:blank") ||
+       aDoc.URL().Scheme() == nsGkAtoms::data) &&
       aDoc.Principal() && aDoc.Principal()->GetIsNullPrincipal()) {
     return true;
   }
 
   if (mRestricted && mExtension->IsRestrictedDoc(aDoc)) {
     return false;
   }
 
new file mode 100644
--- /dev/null
+++ b/tools/quitter/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  globals: {
+    cloneInto: true,
+  }
+};
new file mode 100644
--- /dev/null
+++ b/tools/quitter/background.js
@@ -0,0 +1,9 @@
+"use strict";
+
+/* eslint-env webextensions */
+
+browser.runtime.onMessage.addListener(msg => {
+  if (msg === "quit") {
+    browser.quitter.quit();
+  }
+});
deleted file mode 100644
--- a/tools/quitter/bootstrap.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/* 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/. */
-
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-const CHILD_SCRIPT = "chrome://quitter/content/contentscript.js";
-
-const quitterObserver = {
-  init() {
-    Services.mm.addMessageListener("Quitter.Quit", this);
-    Services.mm.loadFrameScript(CHILD_SCRIPT, true);
-  },
-
-  uninit() {
-    Services.mm.removeMessageListener("Quitter.Quit", this);
-    Services.mm.removeDelayedFrameScript(CHILD_SCRIPT, true);
-  },
-
-  /**
-   * messageManager callback function
-   * This will get requests from our API in the window and process them in chrome for it
-   **/
-  receiveMessage(aMessage) {
-    switch (aMessage.name) {
-      case "Quitter.Quit":
-        Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
-        break;
-    }
-  },
-};
-
-function startup(data, reason) {
-  quitterObserver.init();
-}
-
-function shutdown(data, reason) {
-  quitterObserver.uninit();
-}
-
-function install(data, reason) {}
-function uninstall(data, reason) {}
deleted file mode 100644
--- a/tools/quitter/chrome.manifest
+++ /dev/null
@@ -1,1 +0,0 @@
-content quitter chrome/quitter/content/
--- a/tools/quitter/contentscript.js
+++ b/tools/quitter/contentscript.js
@@ -1,35 +1,13 @@
 /* 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/. */
 
-/* eslint-env mozilla/frame-script */
+"use strict";
 
-function Quitter() {
-}
+/* eslint-env webextensions */
 
-Quitter.prototype = {
-  toString() { return "[Quitter]"; },
-  quit() { sendSyncMessage("Quitter.Quit", {}); },
+const Quitter = {
+  quit() { browser.runtime.sendMessage("quit"); },
 };
 
-// This is a frame script, so it may be running in a content process.
-// In any event, it is targeted at a specific "tab", so we listen for
-// the DOMWindowCreated event to be notified about content windows
-// being created in this context.
-
-function QuitterManager() {
-  addEventListener("DOMWindowCreated", this, false);
-}
-
-QuitterManager.prototype = {
-  handleEvent: function handleEvent(aEvent) {
-    var quitter = new Quitter(window);
-    var window = aEvent.target.defaultView;
-    window.wrappedJSObject.Quitter = Cu.cloneInto({
-      toString: quitter.toString.bind(quitter),
-      quit: quitter.quit.bind(quitter),
-    }, window, {cloneFunctions: true});
-  },
-};
-
-var quittermanager = new QuitterManager();
+window.wrappedJSObject.Quitter = cloneInto(Quitter, window, {cloneFunctions: true});
deleted file mode 100644
--- a/tools/quitter/install.rdf
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0"?>
-
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-
-  <Description about="urn:mozilla:install-manifest">
-    <em:id>quitter@mozilla.org</em:id>
-    <em:version>2018.03.19</em:version>
-    <em:type>2</em:type>
-    <em:bootstrap>true</em:bootstrap>
-
-    <!-- Target Application this extension can install into,
-         with minimum and maximum supported versions. -->
-    <em:targetApplication>
-      <Description>
-        <!-- Firefox -->
-        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
-        <em:minVersion>45</em:minVersion>
-        <em:maxVersion>*</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-    <em:targetApplication>
-      <Description>
-        <!-- Fennec -->
-        <em:id>{aa3c5121-dab2-40e2-81ca-7ea25febc110}</em:id>
-        <em:minVersion>45</em:minVersion>
-        <em:maxVersion>*</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-
-    <!-- Front End MetaData -->
-    <em:name>Quitter</em:name>
-    <em:description>Adds a quit method that content pages can use to quit the application.</em:description>
-    <em:creator>Mozilla</em:creator>
-  </Description>
-</RDF>
deleted file mode 100644
--- a/tools/quitter/jar.mn
+++ /dev/null
@@ -1,3 +0,0 @@
-quitter.jar:
-% content quitter %content/
-  content/contentscript.js (contentscript.js)
new file mode 100644
--- /dev/null
+++ b/tools/quitter/manifest.json
@@ -0,0 +1,34 @@
+{
+  "manifest_version": 2,
+
+  "applications": {
+    "gecko": {"id": "quitter@mozilla.org"}
+  },
+
+  "name": "Quitter",
+  "description": "Quit",
+  "version": "2018.04.03",
+  "author": "Mozilla",
+
+  "background": {
+    "scripts": ["background.js"]
+  },
+
+  "content_scripts": [{
+    "js": ["contentscript.js"],
+    "run_at": "document_start",
+    "match_about_blank": true,
+    "matches": ["<all_urls>"]
+  }],
+
+  "experiment_apis": {
+    "quitter": {
+      "schema": "schema.json",
+      "parent": {
+        "scopes": ["addon_parent"],
+        "script": "parent.js",
+        "paths": [["quitter", "quit"]]
+      }
+    }
+  }
+}
--- a/tools/quitter/moz.build
+++ b/tools/quitter/moz.build
@@ -1,17 +1,17 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 XPI_NAME = 'quitter'
 
-JAR_MANIFESTS += ['jar.mn']
-
 USE_EXTENSION_MANIFEST = True
 
 FINAL_TARGET_FILES += [
-    'bootstrap.js',
-    'chrome.manifest',
-    'install.rdf',
+    'background.js',
+    'contentscript.js',
+    'manifest.json',
+    'parent.js',
+    'schema.json',
 ]
new file mode 100644
--- /dev/null
+++ b/tools/quitter/parent.js
@@ -0,0 +1,17 @@
+"use strict";
+
+/* globals ExtensionAPI */
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+this.quitter = class extends ExtensionAPI {
+  getAPI(context) {
+    return {
+      quitter: {
+        quit() {
+          Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
+        },
+      },
+    };
+  }
+};
index f4e7d81117c12ff6c00d54cf191b398765e68ab5..acf9c87e90803540c2ce99e3ca5d4e1d2d8070bc
GIT binary patch
literal 6164
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
GL;4pEgu*!h
new file mode 100644
--- /dev/null
+++ b/tools/quitter/schema.json
@@ -0,0 +1,13 @@
+[
+  {
+    "namespace": "quitter",
+    "functions": [
+      {
+        "name": "quit",
+        "type": "function",
+        "async": true,
+        "parameters": []
+      }
+    ]
+  }
+]