Bug 1373640 implement async dns resolve api for webextensions, r=kmag

MozReview-Commit-ID: Bzfr2x6Vmx2

--HG--
extra : rebase_source : cfde60f191c470e483a95b7e668a7ac9e5ba5af3
This commit is contained in:
Shane Caraveo 2018-02-27 19:35:01 -06:00
Родитель 88d4860b5d
Коммит 3891cc84f5
8 изменённых файлов: 267 добавлений и 0 удалений

Просмотреть файл

@ -101,6 +101,7 @@ webextPerms.description.browsingData=Clear recent browsing history, cookies, and
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 browsers download history
webextPerms.description.downloads.open=Open files downloaded to your computer
webextPerms.description.find=Read the text of all open tabs

Просмотреть файл

@ -0,0 +1,70 @@
/* -*- 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()) {
let addr = inRecord.getNextAddrAsString();
// Sometimes there are duplicate records with the same ip.
if (!response.addresses.includes(addr)) {
response.addresses.push(addr);
}
}
return resolve(response);
}
};
try {
request = dnss.asyncResolve(hostname, dnsFlags, listener, null, {} /* defaultOriginAttributes */);
} catch (e) {
// handle exceptions such as offline mode.
return reject({message: e.name});
}
});
},
},
};
}
};

Просмотреть файл

@ -58,6 +58,14 @@
["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",

Просмотреть файл

@ -13,6 +13,7 @@ toolkit.jar:
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

Просмотреть файл

@ -0,0 +1,82 @@
[
{
"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" }
}
}
},
{
"id": "ResolveFlags",
"type": "array",
"items": {
"type": "string",
"enum": [
"allow_name_collisions",
"bypass_cache",
"canonical_name",
"disable_ipv4",
"disable_ipv6",
"disable_trr",
"offline",
"priority_low",
"priority_medium",
"speculate"
]
}
}
],
"functions": [
{
"name": "resolve",
"type": "function",
"description": "Resolves a hostname to a DNS record.",
"async": true,
"parameters": [
{
"name": "hostname",
"type": "string"
},
{
"name": "flags",
"optional": true,
"default": [],
"$ref": "ResolveFlags"
}
]
}
]
}
]

Просмотреть файл

@ -10,6 +10,7 @@ toolkit.jar:
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

Просмотреть файл

@ -0,0 +1,103 @@
"use strict";
// Some test machines and android are not returing ipv6, turn it
// off to get consistent test results.
Services.prefs.setBoolPref("network.dns.disableIPv6", true);
function getExtension(background = undefined) {
let manifest = {
"permissions": [
"dns",
],
};
return ExtensionTestUtils.loadExtension({
manifest,
background() {
browser.test.onMessage.addListener(async (msg, data) => {
browser.test.log(`=== dns resolve test ${JSON.stringify(data)}`);
browser.dns.resolve(data.hostname, data.flags).then(result => {
browser.test.log(`=== dns resolve result ${JSON.stringify(result)}`);
browser.test.sendMessage("resolved", result);
}).catch(e => {
browser.test.log(`=== dns resolve error ${e.message}`);
browser.test.sendMessage("resolved", {message: e.message});
});
});
browser.test.sendMessage("ready");
},
});
}
const tests = [
{
request: {
hostname: "localhost",
},
expect: {
addresses: ["127.0.0.1"], // ipv6 disabled , "::1"
},
},
{
request: {
hostname: "localhost",
flags: ["offline"],
},
expect: {
addresses: ["127.0.0.1"], // ipv6 disabled , "::1"
},
},
{
request: {
hostname: "test.example",
},
expect: {
// android will error with offline
error: /NS_ERROR_UNKNOWN_HOST|NS_ERROR_OFFLINE/,
},
},
{
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_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");
// It seems there are platform differences happening that make this
// testing difficult. We're going to rely on other existing dns tests to validate
// the dns service itself works and only validate that we're getting generally
// expected results in the webext api.
ok(result.addresses.length >= test.expect.addresses.length, "expected number of addresses returned");
if (test.expect.addresses.length > 0 && result.addresses.length > 0) {
ok(result.addresses.includes(test.expect.addresses[0]), "got expected ip address");
}
}
}
await extension.unload();
});

Просмотреть файл

@ -20,6 +20,7 @@ skip-if = os == "android"
[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"