Part 2: Bug 1295807 - Implement the Proxy API. r?kmag draft
authorMatthew Wein <mwein@mozilla.com>
Fri, 03 Mar 2017 02:16:01 +0000
changeset 494240 64e30dcdfbdde052aa5f7bc5b118a4308001f39b
parent 494239 9dfc0d929b739eb44761842a2aac1ed19c810ef8
child 548057 fdd8965447eff871775ba68fbdc252d6250e1e30
push id47986
push usermwein@mozilla.com
push dateMon, 06 Mar 2017 22:07:05 +0000
reviewerskmag
bugs1295807
milestone54.0a1
Part 2: Bug 1295807 - Implement the Proxy API. r?kmag MozReview-Commit-ID: KCoak15Mic8
toolkit/components/extensions/ext-proxy.js
toolkit/components/extensions/extensions-toolkit.manifest
toolkit/components/extensions/jar.mn
toolkit/components/extensions/schemas/jar.mn
toolkit/components/extensions/schemas/proxy.json
toolkit/components/extensions/test/mochitest/mochitest-common.ini
toolkit/components/extensions/test/mochitest/test_ext_proxy.html
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/ext-proxy.js
@@ -0,0 +1,62 @@
+/* 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/. */
+
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+
+"use strict";
+
+var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "ProxyScriptContext",
+                                  "resource://gre/modules/ProxyScriptContext.jsm");
+
+var {
+  SingletonEventManager,
+} = ExtensionUtils;
+
+// WeakMap[Extension -> ProxyScriptContext]
+let proxyScriptContextMap = new WeakMap();
+
+/* eslint-disable mozilla/balanced-listeners */
+extensions.on("shutdown", (type, extension) => {
+  let proxyScriptContext = proxyScriptContextMap.get(extension);
+  if (proxyScriptContext) {
+    proxyScriptContext.unload();
+    proxyScriptContextMap.delete(extension);
+  }
+});
+/* eslint-enable mozilla/balanced-listeners */
+
+extensions.registerSchemaAPI("proxy", "addon_parent", context => {
+  let {extension} = context;
+  return {
+    proxy: {
+      registerProxyScript: (url) => {
+        // Unload the current proxy script if one is loaded.
+        if (proxyScriptContextMap.has(extension)) {
+          proxyScriptContextMap.get(extension).unload();
+          proxyScriptContextMap.delete(extension);
+        }
+
+        let proxyScriptContext = new ProxyScriptContext(extension, url);
+        if (proxyScriptContext.load()) {
+          proxyScriptContextMap.set(extension, proxyScriptContext);
+        }
+      },
+
+      onProxyError: new SingletonEventManager(context, "proxy.onProxyError", fire => {
+        let listener = (name, error) => {
+          fire.async(error);
+        };
+        extension.on("proxy-error", listener);
+        return () => {
+          extension.off("proxy-error", listener);
+        };
+      }).api(),
+    },
+  };
+});
--- a/toolkit/components/extensions/extensions-toolkit.manifest
+++ b/toolkit/components/extensions/extensions-toolkit.manifest
@@ -7,16 +7,17 @@ category webextension-scripts downloads 
 category webextension-scripts extension chrome://extensions/content/ext-extension.js
 category webextension-scripts geolocation chrome://extensions/content/ext-geolocation.js
 category webextension-scripts handlers chrome://extensions/content/ext-protocolHandlers.js
 category webextension-scripts i18n chrome://extensions/content/ext-i18n.js
 category webextension-scripts idle chrome://extensions/content/ext-idle.js
 category webextension-scripts management chrome://extensions/content/ext-management.js
 category webextension-scripts notifications chrome://extensions/content/ext-notifications.js
 category webextension-scripts privacy chrome://extensions/content/ext-privacy.js
+category webextension-scripts proxy chrome://extensions/content/ext-proxy.js
 category webextension-scripts runtime chrome://extensions/content/ext-runtime.js
 category webextension-scripts storage chrome://extensions/content/ext-storage.js
 category webextension-scripts theme chrome://extensions/content/ext-theme.js
 category webextension-scripts topSites chrome://extensions/content/ext-topSites.js
 category webextension-scripts webNavigation chrome://extensions/content/ext-webNavigation.js
 category webextension-scripts webRequest chrome://extensions/content/ext-webRequest.js
 
 # scripts specific for content process.
@@ -57,16 +58,17 @@ category webextension-schemas i18n chrom
 #ifndef ANDROID
 category webextension-schemas identity chrome://extensions/content/schemas/identity.json
 #endif
 category webextension-schemas idle chrome://extensions/content/schemas/idle.json
 category webextension-schemas management chrome://extensions/content/schemas/management.json
 category webextension-schemas native_host_manifest chrome://extensions/content/schemas/native_host_manifest.json
 category webextension-schemas notifications chrome://extensions/content/schemas/notifications.json
 category webextension-schemas privacy chrome://extensions/content/schemas/privacy.json
+category webextension-schemas proxy chrome://extensions/content/schemas/proxy.json
 category webextension-schemas runtime chrome://extensions/content/schemas/runtime.json
 category webextension-schemas storage chrome://extensions/content/schemas/storage.json
 category webextension-schemas test chrome://extensions/content/schemas/test.json
 category webextension-schemas theme chrome://extensions/content/schemas/theme.json
 category webextension-schemas top_sites chrome://extensions/content/schemas/top_sites.json
 category webextension-schemas types chrome://extensions/content/schemas/types.json
 category webextension-schemas web_navigation chrome://extensions/content/schemas/web_navigation.json
 category webextension-schemas web_request chrome://extensions/content/schemas/web_request.json
--- a/toolkit/components/extensions/jar.mn
+++ b/toolkit/components/extensions/jar.mn
@@ -13,16 +13,17 @@ toolkit.jar:
     content/extensions/ext-extension.js
     content/extensions/ext-geolocation.js
     content/extensions/ext-i18n.js
     content/extensions/ext-idle.js
     content/extensions/ext-management.js
     content/extensions/ext-notifications.js
     content/extensions/ext-privacy.js
     content/extensions/ext-protocolHandlers.js
+    content/extensions/ext-proxy.js
     content/extensions/ext-runtime.js
     content/extensions/ext-storage.js
     content/extensions/ext-theme.js
     content/extensions/ext-topSites.js
     content/extensions/ext-webRequest.js
     content/extensions/ext-webNavigation.js
     # Below is a separate group using the naming convention ext-c-*.js that run
     # in the child process.
--- a/toolkit/components/extensions/schemas/jar.mn
+++ b/toolkit/components/extensions/schemas/jar.mn
@@ -17,16 +17,17 @@ toolkit.jar:
 #ifndef ANDROID
     content/extensions/schemas/identity.json
 #endif
     content/extensions/schemas/idle.json
     content/extensions/schemas/management.json
     content/extensions/schemas/manifest.json
     content/extensions/schemas/native_host_manifest.json
     content/extensions/schemas/notifications.json
+    content/extensions/schemas/proxy.json
     content/extensions/schemas/privacy.json
     content/extensions/schemas/runtime.json
     content/extensions/schemas/storage.json
     content/extensions/schemas/test.json
     content/extensions/schemas/theme.json
     content/extensions/schemas/top_sites.json
     content/extensions/schemas/types.json
     content/extensions/schemas/web_navigation.json
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/schemas/proxy.json
@@ -0,0 +1,49 @@
+[
+  {
+    "namespace": "manifest",
+    "types": [
+      {
+        "$extend": "Permission",
+        "choices": [{
+          "type": "string",
+          "enum": [
+            "proxy"
+          ]
+        }]
+      }
+    ]
+  },
+  {
+    "namespace": "proxy",
+    "description": "Use the browser.proxy API to register proxy scripts in Firefox. Proxy scripts in Firefox are proxy auto-config files with extra contextual information and support for additional return types.",
+    "permissions": ["proxy"],
+    "functions": [
+      {
+        "name": "registerProxyScript",
+        "type": "function",
+        "description": "Registers the proxy script for the extension.",
+        "async": true,
+        "parameters": [
+          {
+            "name": "url",
+            "type": "string",
+            "format": "strictRelativeUrl"
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onProxyError",
+        "type": "function",
+        "description": "Notifies about proxy script errors.",
+        "parameters": [
+          {
+            "name": "error",
+            "type": "object"
+          }
+        ]
+      }
+    ]
+  }
+]
\ No newline at end of file
--- a/toolkit/components/extensions/test/mochitest/mochitest-common.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest-common.ini
@@ -69,16 +69,17 @@ skip-if = os == 'android' # Android does
 [test_ext_exclude_include_globs.html]
 [test_ext_external_messaging.html]
 [test_ext_i18n_css.html]
 [test_ext_generate.html]
 [test_ext_geolocation.html]
 skip-if = os == 'android' # Android support Bug 1336194
 [test_ext_notifications.html]
 [test_ext_permission_xhr.html]
+[test_ext_proxy.html]
 [test_ext_runtime_connect.html]
 [test_ext_runtime_connect_twoway.html]
 [test_ext_runtime_connect2.html]
 [test_ext_runtime_disconnect.html]
 [test_ext_runtime_id.html]
 [test_ext_sandbox_var.html]
 [test_ext_sendmessage_reply.html]
 [test_ext_sendmessage_reply2.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_proxy.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Tests for the proxy API</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="text/javascript">
+/* eslint no-unused-vars: ["error", {"args": "none", "varsIgnorePattern": "^(FindProxyForURL)$"}] */
+
+"use strict";
+
+function* testProxyScript(script, expected) {
+  let extension = ExtensionTestUtils.loadExtension({
+    background() {
+      browser.proxy.onProxyError.addListener(error => {
+        browser.test.sendMessage("proxy-error-received", error);
+      });
+
+      browser.proxy.registerProxyScript("proxy_script.js");
+    },
+    manifest: {
+      "permissions": ["proxy"],
+    },
+    files: {
+      "proxy_script.js": String(script).replace(/^.*?\{([^]*)\}$/, "$1"),
+    },
+  });
+
+  yield extension.startup();
+
+  let win = window.open("http://example.com/");
+  let error = yield extension.awaitMessage("proxy-error-received");
+  is(error.message, expected.message, "Correct error message received");
+  win.close();
+
+  if (expected.errorInfo) {
+    ok(error.fileName.includes("proxy_script.js"), "Error should include file name");
+    is(error.lineNumber, 3, "Error should include line number");
+    ok(error.stack.includes("proxy_script.js:3:9"), "Error should include stack trace");
+  }
+
+  yield extension.unload();
+}
+
+add_task(function* test_invalid_FindProxyForURL_type() {
+  yield testProxyScript(
+    () => { }, {
+      message: "The proxy script must define FindProxyForURL as a function",
+    });
+
+  yield testProxyScript(
+    () => {
+      var FindProxyForURL = 5; // eslint-disable-line mozilla/var-only-at-top-level
+    }, {
+      message: "The proxy script must define FindProxyForURL as a function",
+    });
+});
+
+add_task(function* test_invalid_FindProxyForURL_return_type() {
+  yield testProxyScript(
+    () => {
+      function FindProxyForURL() {
+        return 5;
+      }
+    }, {
+      message: "FindProxyForURL: Return type must be a string",
+    });
+
+  yield testProxyScript(
+    () => {
+      function FindProxyForURL() {
+        return "INVALID";
+      }
+    }, {
+      message: "FindProxyForURL: Invalid Proxy Rule: INVALID",
+    });
+});
+
+add_task(function* test_runtime_error_in_FindProxyForURL() {
+  yield testProxyScript(
+    () => {
+      function FindProxyForURL() {
+        return not_defined; // eslint-disable-line no-undef
+      }
+    }, {
+      message: "not_defined is not defined",
+      errorInfo: true,
+    });
+});
+
+</script>
+
+</body>
+</html>