Bug 1373640 implement async dns resolve api for webextensions, r=kmag
☠☠ backed out by 844379322831 ☠ ☠
authorShane Caraveo <scaraveo@mozilla.com>
Mon, 26 Feb 2018 11:01:09 -0600
changeset 405296 39a35e2f37f92172ebdeb7a19c47d8bc74dd7943
parent 405295 e95a90f9a0bbaadac8face578cc26a5943cd9673
child 405298 ce68000786e8644bcfcca2001ea45d48187bbfbe
push id100205
push usermixedpuppy@gmail.com
push dateMon, 26 Feb 2018 17:01:28 +0000
treeherdermozilla-inbound@39a35e2f37f9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag
bugs1373640
milestone60.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 1373640 implement async dns resolve api for webextensions, r=kmag
browser/locales/en-US/chrome/browser/browser.properties
toolkit/components/extensions/ext-dns.js
toolkit/components/extensions/ext-toolkit.json
toolkit/components/extensions/jar.mn
toolkit/components/extensions/schemas/dns.json
toolkit/components/extensions/schemas/jar.mn
toolkit/components/extensions/test/xpcshell/test_ext_dns.js
toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -96,16 +96,17 @@ webextPerms.optionalPermsDeny.label=Deny
 webextPerms.optionalPermsDeny.accessKey=D
 
 webextPerms.description.bookmarks=Read and modify bookmarks
 webextPerms.description.browserSettings=Read and modify browser settings
 webextPerms.description.browsingData=Clear recent browsing history, cookies, and related data
 webextPerms.description.clipboardRead=Get data from the clipboard
 webextPerms.description.clipboardWrite=Input data to the clipboard
 webextPerms.description.devtools=Extend developer tools to access your data in open tabs
+webextPerms.description.dns=Access IP address and hostname information
 webextPerms.description.downloads=Download files and read and modify the browser’s download history
 webextPerms.description.downloads.open=Open files downloaded to your computer
 webextPerms.description.find=Read the text of all open tabs
 webextPerms.description.geolocation=Access your location
 webextPerms.description.history=Access browsing history
 webextPerms.description.management=Monitor extension usage and manage themes
 # LOCALIZATION NOTE (webextPerms.description.nativeMessaging)
 # %S will be replaced with the name of the application
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/ext-dns.js
@@ -0,0 +1,61 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const dnssFlags = {
+  "allow_name_collisions": Ci.nsIDNSService.RESOLVE_ALLOW_NAME_COLLISION,
+  "bypass_cache": Ci.nsIDNSService.RESOLVE_BYPASS_CACHE,
+  "canonical_name": Ci.nsIDNSService.RESOLVE_CANONICAL_NAME,
+  "disable_ipv4": Ci.nsIDNSService.RESOLVE_DISABLE_IPV4,
+  "disable_ipv6": Ci.nsIDNSService.RESOLVE_DISABLE_IPV6,
+  "disable_trr": Ci.nsIDNSService.RESOLVE_DISABLE_TRR,
+  "offline": Ci.nsIDNSService.RESOLVE_OFFLINE,
+  "priority_low": Ci.nsIDNSService.RESOLVE_PRIORITY_LOW,
+  "priority_medium": Ci.nsIDNSService.RESOLVE_PRIORITY_MEDIUM,
+  "speculate": Ci.nsIDNSService.RESOLVE_SPECULATE,
+};
+
+function getErrorString(nsresult) {
+  let e = new Components.Exception("", nsresult);
+  return e.name;
+}
+
+this.dns = class extends ExtensionAPI {
+  getAPI(context) {
+    const dnss = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+    return {
+      dns: {
+        resolve: function(hostname, flags) {
+          let dnsFlags = flags.reduce((mask, flag) => mask | dnssFlags[flag], 0);
+
+          return new Promise((resolve, reject) => {
+            let request;
+            let response = {
+              addresses: [],
+            };
+            let listener = (inRequest, inRecord, inStatus) => {
+              if (inRequest === request) {
+                if (!Components.isSuccessCode(inStatus)) {
+                  return reject({message: getErrorString(inStatus)});
+                }
+                if (dnsFlags & Ci.nsIDNSService.RESOLVE_CANONICAL_NAME) {
+                  try {
+                    response.canonicalName = inRecord.canonicalName;
+                  } catch (e) {
+                    // no canonicalName
+                  }
+                }
+                response.isTRR = inRecord.IsTRR();
+                while (inRecord.hasMore()) {
+                  response.addresses.push(inRecord.getNextAddrAsString());
+                }
+                return resolve(response);
+              }
+            };
+            request = dnss.asyncResolve(hostname, dnsFlags, listener, null, {} /* defaultOriginAttributes */);
+          });
+        },
+      },
+    };
+  }
+};
--- a/toolkit/components/extensions/ext-toolkit.json
+++ b/toolkit/components/extensions/ext-toolkit.json
@@ -53,16 +53,24 @@
   "cookies": {
     "url": "chrome://extensions/content/ext-cookies.js",
     "schema": "chrome://extensions/content/schemas/cookies.json",
     "scopes": ["addon_parent"],
     "paths": [
       ["cookies"]
     ]
   },
+  "dns": {
+    "url": "chrome://extensions/content/ext-dns.js",
+    "schema": "chrome://extensions/content/schemas/dns.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["dns"]
+    ]
+  },
   "downloads": {
     "url": "chrome://extensions/content/ext-downloads.js",
     "schema": "chrome://extensions/content/schemas/downloads.json",
     "scopes": ["addon_parent"],
     "paths": [
       ["downloads"]
     ]
   },
--- a/toolkit/components/extensions/jar.mn
+++ b/toolkit/components/extensions/jar.mn
@@ -8,16 +8,17 @@ toolkit.jar:
     content/extensions/ext-alarms.js
     content/extensions/ext-backgroundPage.js
     content/extensions/ext-browser-content.js
     content/extensions/ext-browserSettings.js
     content/extensions/ext-contentScripts.js
     content/extensions/ext-contextualIdentities.js
     content/extensions/ext-clipboard.js
     content/extensions/ext-cookies.js
+    content/extensions/ext-dns.js
     content/extensions/ext-downloads.js
     content/extensions/ext-extension.js
     content/extensions/ext-i18n.js
 #ifndef ANDROID
     content/extensions/ext-identity.js
 #endif
     content/extensions/ext-idle.js
     content/extensions/ext-management.js
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/schemas/dns.json
@@ -0,0 +1,70 @@
+[
+  {
+    "namespace": "manifest",
+    "types": [
+      {
+        "$extend": "Permission",
+        "choices": [{
+          "type": "string",
+          "enum": [
+            "dns"
+          ]
+        }]
+      }
+    ]
+  },
+  {
+    "namespace": "dns",
+    "description": "Asynchronous DNS API",
+    "permissions": ["dns"],
+    "types": [
+      {
+        "id": "DNSRecord",
+        "type": "object",
+        "description": "An object encapsulating a DNS Record.",
+        "properties": {
+          "canonicalName": {
+            "type": "string",
+            "optional": true,
+            "description": "The canonical hostname for this record.  this value is empty if the record was not fetched with the 'canonical_name' flag."
+          },
+          "isTRR": {
+            "type": "string",
+            "description": "Record retreived with TRR."
+          },
+          "addresses": {
+            "type": "array",
+            "items": { "type": "string" }
+          }
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "resolve",
+        "type": "function",
+        "description": "Resolves a hostname to a DNS entry.",
+        "async": true,
+        "parameters": [
+          {
+            "name": "hostname",
+            "type": "string"
+          },
+          {
+            "name": "flags",
+            "optional": true,
+            "type": "array",
+            "default": [],
+            "items": {
+              "type": "string",
+              "enum": [
+                "bypass_cache", "canonical_name", "priority_medium", "priority_low", "speculate",
+                "disable_ipv6", "disable_ipv4", "offline", "allow_name_collisions", "disable_trr"
+              ]
+            }
+          }
+        ]
+      }
+    ]
+  }
+]
--- a/toolkit/components/extensions/schemas/jar.mn
+++ b/toolkit/components/extensions/schemas/jar.mn
@@ -5,16 +5,17 @@
 toolkit.jar:
 % content extensions %content/extensions/
     content/extensions/schemas/alarms.json
     content/extensions/schemas/browser_settings.json
     content/extensions/schemas/clipboard.json
     content/extensions/schemas/content_scripts.json
     content/extensions/schemas/contextual_identities.json
     content/extensions/schemas/cookies.json
+    content/extensions/schemas/dns.json
     content/extensions/schemas/downloads.json
     content/extensions/schemas/events.json
     content/extensions/schemas/experiments.json
     content/extensions/schemas/extension.json
     content/extensions/schemas/extension_types.json
     content/extensions/schemas/extension_protocol_handlers.json
     content/extensions/schemas/i18n.json
 #ifndef ANDROID
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_dns.js
@@ -0,0 +1,100 @@
+"use strict";
+
+function getExtension(background = undefined) {
+  let manifest = {
+    "permissions": [
+      "dns",
+    ],
+  };
+  return ExtensionTestUtils.loadExtension({
+    manifest,
+    background() {
+      browser.test.onMessage.addListener(async (msg, data) => {
+        browser.dns.resolve(data.hostname, data.flags).then(result => {
+          browser.test.sendMessage("resolved", result);
+        }).catch(e => {
+          browser.test.sendMessage("resolved", {message: e.message});
+        });
+      });
+      browser.test.sendMessage("ready");
+    },
+  });
+}
+
+const tests = [
+  {
+    request: {
+      hostname: "localhost",
+    },
+    expect: {
+      addresses: ["127.0.0.1", "::1"],
+    },
+  },
+  {
+    request: {
+      hostname: "localhost",
+      flags: ["offline"],
+    },
+    expect: {
+      addresses: ["127.0.0.1", "::1"],
+    },
+  },
+  {
+    request: {
+      hostname: "test.example",
+    },
+    expect: {
+      error: /UNKNOWN_HOST/,
+    },
+  },
+  {
+    request: {
+      hostname: "127.0.0.1",
+      flags: ["canonical_name"],
+    },
+    expect: {
+      canonicalName: "127.0.0.1",
+      addresses: ["127.0.0.1"],
+    },
+  },
+  {
+    request: {
+      hostname: "localhost",
+      flags: ["disable_ipv4"],
+    },
+    expect: {
+      addresses: ["::1"],
+    },
+  },
+  {
+    request: {
+      hostname: "localhost",
+      flags: ["disable_ipv6"],
+    },
+    expect: {
+      addresses: ["127.0.0.1"],
+    },
+  },
+];
+
+add_task(async function test_dns_resolve() {
+  let extension = getExtension();
+  await extension.startup();
+  await extension.awaitMessage("ready");
+
+  for (let test of tests) {
+    extension.sendMessage("resolve", test.request);
+    let result = await extension.awaitMessage("resolved");
+    if (test.expect.error) {
+      ok(test.expect.error.test(result.message), `expected error ${result.message}`);
+    } else {
+      equal(result.canonicalName, test.expect.canonicalName, "canonicalName match");
+      equal(result.addresses.length, test.expect.addresses.length, "expected number of addresses returned");
+      for (let addr of test.expect.addresses) {
+        ok(result.addresses.includes(addr), `expected ip match`);
+      }
+    }
+  }
+
+  await extension.unload();
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -15,16 +15,17 @@ skip-if = os == "android" # Android does
 skip-if = os == "android"
 [test_ext_browserSettings.js]
 [test_ext_browserSettings_homepage.js]
 skip-if = os == "android"
 [test_ext_cookieBehaviors.js]
 [test_ext_contextual_identities.js]
 skip-if = os == "android" # Containers are not exposed to android.
 [test_ext_debugging_utils.js]
+[test_ext_dns.js]
 [test_ext_downloads.js]
 [test_ext_downloads_download.js]
 skip-if = os == "android"
 [test_ext_downloads_misc.js]
 skip-if = os == "android" || (os=='linux' && bits==32) # linux32: bug 1324870
 [test_ext_downloads_private.js]
 skip-if = os == "android"
 [test_ext_downloads_search.js]