summaryrefslogtreecommitdiffstats
path: root/docshell/test/unit
diff options
context:
space:
mode:
Diffstat (limited to 'docshell/test/unit')
-rw-r--r--docshell/test/unit/AllowJavascriptChild.sys.mjs41
-rw-r--r--docshell/test/unit/AllowJavascriptParent.sys.mjs28
-rw-r--r--docshell/test/unit/data/engine.xml10
-rw-r--r--docshell/test/unit/data/enginePost.xml10
-rw-r--r--docshell/test/unit/data/enginePrivate.xml10
-rw-r--r--docshell/test/unit/head_docshell.js106
-rw-r--r--docshell/test/unit/test_URIFixup.js123
-rw-r--r--docshell/test/unit/test_URIFixup_check_host.js183
-rw-r--r--docshell/test/unit/test_URIFixup_external_protocol_fallback.js106
-rw-r--r--docshell/test/unit/test_URIFixup_forced.js159
-rw-r--r--docshell/test/unit/test_URIFixup_info.js1075
-rw-r--r--docshell/test/unit/test_URIFixup_search.js143
-rw-r--r--docshell/test/unit/test_allowJavascript.js291
-rw-r--r--docshell/test/unit/test_browsing_context_structured_clone.js70
-rw-r--r--docshell/test/unit/test_bug442584.js35
-rw-r--r--docshell/test/unit/test_pb_notification.js18
-rw-r--r--docshell/test/unit/test_privacy_transition.js21
-rw-r--r--docshell/test/unit/test_subframe_stop_after_parent_error.js146
-rw-r--r--docshell/test/unit/xpcshell.ini35
19 files changed, 2610 insertions, 0 deletions
diff --git a/docshell/test/unit/AllowJavascriptChild.sys.mjs b/docshell/test/unit/AllowJavascriptChild.sys.mjs
new file mode 100644
index 0000000000..a7c3fa6172
--- /dev/null
+++ b/docshell/test/unit/AllowJavascriptChild.sys.mjs
@@ -0,0 +1,41 @@
+export class AllowJavascriptChild extends JSWindowActorChild {
+ async receiveMessage(msg) {
+ switch (msg.name) {
+ case "CheckScriptsAllowed":
+ return this.checkScriptsAllowed();
+ case "CheckFiredLoadEvent":
+ return this.contentWindow.wrappedJSObject.gFiredOnload;
+ case "CreateIframe":
+ return this.createIframe(msg.data.url);
+ }
+ return null;
+ }
+
+ handleEvent(event) {
+ if (event.type === "load") {
+ this.sendAsyncMessage("LoadFired");
+ }
+ }
+
+ checkScriptsAllowed() {
+ let win = this.contentWindow;
+
+ win.wrappedJSObject.gFiredOnclick = false;
+ win.document.body.click();
+ return win.wrappedJSObject.gFiredOnclick;
+ }
+
+ async createIframe(url) {
+ let doc = this.contentWindow.document;
+
+ let iframe = doc.createElement("iframe");
+ iframe.src = url;
+ doc.body.appendChild(iframe);
+
+ await new Promise(resolve => {
+ iframe.addEventListener("load", resolve, { once: true });
+ });
+
+ return iframe.browsingContext;
+ }
+}
diff --git a/docshell/test/unit/AllowJavascriptParent.sys.mjs b/docshell/test/unit/AllowJavascriptParent.sys.mjs
new file mode 100644
index 0000000000..5631fcdb09
--- /dev/null
+++ b/docshell/test/unit/AllowJavascriptParent.sys.mjs
@@ -0,0 +1,28 @@
+let loadPromises = new WeakMap();
+
+export class AllowJavascriptParent extends JSWindowActorParent {
+ async receiveMessage(msg) {
+ switch (msg.name) {
+ case "LoadFired":
+ let bc = this.browsingContext;
+ let deferred = loadPromises.get(bc);
+ if (deferred) {
+ loadPromises.delete(bc);
+ deferred.resolve(this);
+ }
+ break;
+ }
+ }
+
+ static promiseLoad(bc) {
+ let deferred = loadPromises.get(bc);
+ if (!deferred) {
+ deferred = {};
+ deferred.promise = new Promise(resolve => {
+ deferred.resolve = resolve;
+ });
+ loadPromises.set(bc, deferred);
+ }
+ return deferred.promise;
+ }
+}
diff --git a/docshell/test/unit/data/engine.xml b/docshell/test/unit/data/engine.xml
new file mode 100644
index 0000000000..3a2bd85c1b
--- /dev/null
+++ b/docshell/test/unit/data/engine.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>test_urifixup_search_engine</ShortName>
+<Description>test_urifixup_search_engine</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Url type="text/html" method="GET" template="https://www.example.org/">
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<SearchForm>https://www.example.org/</SearchForm>
+</SearchPlugin>
diff --git a/docshell/test/unit/data/enginePost.xml b/docshell/test/unit/data/enginePost.xml
new file mode 100644
index 0000000000..14775b6f0a
--- /dev/null
+++ b/docshell/test/unit/data/enginePost.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>test_urifixup_search_engine_post</ShortName>
+<Description>test_urifixup_search_engine_post</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Url type="text/html" method="POST" template="https://www.example.org/">
+ <Param name="q" value="{searchTerms}"/>
+</Url>
+<SearchForm>https://www.example.org/</SearchForm>
+</SearchPlugin>
diff --git a/docshell/test/unit/data/enginePrivate.xml b/docshell/test/unit/data/enginePrivate.xml
new file mode 100644
index 0000000000..7d87de98fa
--- /dev/null
+++ b/docshell/test/unit/data/enginePrivate.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>test_urifixup_search_engine_private</ShortName>
+<Description>test_urifixup_search_engine_private</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Url type="text/html" method="GET" template="https://www.example.org/">
+ <Param name="private" value="{searchTerms}"/>
+</Url>
+<SearchForm>https://www.example.org/</SearchForm>
+</SearchPlugin>
diff --git a/docshell/test/unit/head_docshell.js b/docshell/test/unit/head_docshell.js
new file mode 100644
index 0000000000..5b0369089e
--- /dev/null
+++ b/docshell/test/unit/head_docshell.js
@@ -0,0 +1,106 @@
+/* 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/. */
+
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ AddonTestUtils: "resource://testing-common/AddonTestUtils.sys.mjs",
+ SearchTestUtils: "resource://testing-common/SearchTestUtils.sys.mjs",
+ SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs",
+ TestUtils: "resource://testing-common/TestUtils.sys.mjs",
+});
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ HttpServer: "resource://testing-common/httpd.js",
+ NetUtil: "resource://gre/modules/NetUtil.jsm",
+});
+
+var profileDir = do_get_profile();
+
+const kSearchEngineID = "test_urifixup_search_engine";
+const kSearchEngineURL = "https://www.example.org/?search={searchTerms}";
+const kPrivateSearchEngineID = "test_urifixup_search_engine_private";
+const kPrivateSearchEngineURL =
+ "https://www.example.org/?private={searchTerms}";
+const kPostSearchEngineID = "test_urifixup_search_engine_post";
+const kPostSearchEngineURL = "https://www.example.org/";
+const kPostSearchEngineData = "q={searchTerms}";
+
+const SEARCH_CONFIG = [
+ {
+ appliesTo: [
+ {
+ included: {
+ everywhere: true,
+ },
+ },
+ ],
+ default: "yes",
+ webExtension: {
+ id: "fixup_search@search.mozilla.org",
+ },
+ },
+];
+
+async function setupSearchService() {
+ SearchTestUtils.init(this);
+
+ AddonTestUtils.init(this);
+ AddonTestUtils.overrideCertDB();
+ AddonTestUtils.createAppInfo(
+ "xpcshell@tests.mozilla.org",
+ "XPCShell",
+ "1",
+ "42"
+ );
+
+ await SearchTestUtils.useTestEngines(".", null, SEARCH_CONFIG);
+ await AddonTestUtils.promiseStartupManager();
+ await Services.search.init();
+}
+
+/**
+ * After useHttpServer() is called, this string contains the URL of the "data"
+ * directory, including the final slash.
+ */
+var gDataUrl;
+
+/**
+ * Initializes the HTTP server and ensures that it is terminated when tests end.
+ *
+ * @param {string} dir
+ * The test sub-directory to use for the engines.
+ * @returns {HttpServer}
+ * The HttpServer object in case further customization is needed.
+ */
+function useHttpServer(dir = "data") {
+ let httpServer = new HttpServer();
+ httpServer.start(-1);
+ httpServer.registerDirectory("/", do_get_cwd());
+ gDataUrl = `http://localhost:${httpServer.identity.primaryPort}/${dir}/`;
+ registerCleanupFunction(async function cleanup_httpServer() {
+ await new Promise(resolve => {
+ httpServer.stop(resolve);
+ });
+ });
+ return httpServer;
+}
+
+async function addTestEngines() {
+ useHttpServer();
+ // This is a hack, ideally we should be setting up a configuration with
+ // built-in engines, but the `chrome_settings_overrides` section that
+ // WebExtensions need is only defined for browser/
+ await SearchTestUtils.promiseNewSearchEngine({
+ url: `${gDataUrl}/engine.xml`,
+ });
+ await SearchTestUtils.promiseNewSearchEngine({
+ url: `${gDataUrl}/enginePrivate.xml`,
+ });
+ await SearchTestUtils.promiseNewSearchEngine({
+ url: `${gDataUrl}/enginePost.xml`,
+ });
+}
diff --git a/docshell/test/unit/test_URIFixup.js b/docshell/test/unit/test_URIFixup.js
new file mode 100644
index 0000000000..7967933b56
--- /dev/null
+++ b/docshell/test/unit/test_URIFixup.js
@@ -0,0 +1,123 @@
+var pref = "browser.fixup.typo.scheme";
+
+var data = [
+ {
+ // ttp -> http.
+ wrong: "ttp://www.example.com/",
+ fixed: "http://www.example.com/",
+ },
+ {
+ // htp -> http.
+ wrong: "htp://www.example.com/",
+ fixed: "http://www.example.com/",
+ },
+ {
+ // ttps -> https.
+ wrong: "ttps://www.example.com/",
+ fixed: "https://www.example.com/",
+ },
+ {
+ // tps -> https.
+ wrong: "tps://www.example.com/",
+ fixed: "https://www.example.com/",
+ },
+ {
+ // ps -> https.
+ wrong: "ps://www.example.com/",
+ fixed: "https://www.example.com/",
+ },
+ {
+ // htps -> https.
+ wrong: "htps://www.example.com/",
+ fixed: "https://www.example.com/",
+ },
+ {
+ // ile -> file.
+ wrong: "ile:///this/is/a/test.html",
+ fixed: "file:///this/is/a/test.html",
+ },
+ {
+ // le -> file.
+ wrong: "le:///this/is/a/test.html",
+ fixed: "file:///this/is/a/test.html",
+ },
+ {
+ // Replace ';' with ':'.
+ wrong: "http;//www.example.com/",
+ fixed: "http://www.example.com/",
+ noPrefValue: "http://http;//www.example.com/",
+ },
+ {
+ // Missing ':'.
+ wrong: "https//www.example.com/",
+ fixed: "https://www.example.com/",
+ noPrefValue: "http://https//www.example.com/",
+ },
+ {
+ // Missing ':' for file scheme.
+ wrong: "file///this/is/a/test.html",
+ fixed: "file:///this/is/a/test.html",
+ noPrefValue: "http://file///this/is/a/test.html",
+ },
+ {
+ // Valid should not be changed.
+ wrong: "https://example.com/this/is/a/test.html",
+ fixed: "https://example.com/this/is/a/test.html",
+ },
+ {
+ // Unmatched should not be changed.
+ wrong: "whatever://this/is/a/test.html",
+ fixed: "whatever://this/is/a/test.html",
+ },
+];
+
+var len = data.length;
+
+add_task(async function setup() {
+ await setupSearchService();
+ // Now we've initialised the search service, we force remove the engines
+ // it has, so they don't interfere with this test.
+ // Search engine integration is tested in test_URIFixup_search.js.
+ Services.search.wrappedJSObject._engines.clear();
+});
+
+// Make sure we fix what needs fixing when there is no pref set.
+add_task(function test_unset_pref_fixes_typos() {
+ Services.prefs.clearUserPref(pref);
+ for (let i = 0; i < len; ++i) {
+ let item = data[i];
+ let { preferredURI } = Services.uriFixup.getFixupURIInfo(
+ item.wrong,
+ Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS
+ );
+ Assert.equal(preferredURI.spec, item.fixed);
+ }
+});
+
+// Make sure we don't do anything when the pref is explicitly
+// set to false.
+add_task(function test_false_pref_keeps_typos() {
+ Services.prefs.setBoolPref(pref, false);
+ for (let i = 0; i < len; ++i) {
+ let item = data[i];
+ let { preferredURI } = Services.uriFixup.getFixupURIInfo(
+ item.wrong,
+ Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS
+ );
+ Assert.equal(preferredURI.spec, item.noPrefValue || item.wrong);
+ }
+});
+
+// Finally, make sure we still fix what needs fixing if the pref is
+// explicitly set to true.
+add_task(function test_true_pref_fixes_typos() {
+ Services.prefs.setBoolPref(pref, true);
+ for (let i = 0; i < len; ++i) {
+ let item = data[i];
+ let { preferredURI } = Services.uriFixup.getFixupURIInfo(
+ item.wrong,
+ Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS
+ );
+ Assert.equal(preferredURI.spec, item.fixed);
+ }
+});
diff --git a/docshell/test/unit/test_URIFixup_check_host.js b/docshell/test/unit/test_URIFixup_check_host.js
new file mode 100644
index 0000000000..132d74ca9b
--- /dev/null
+++ b/docshell/test/unit/test_URIFixup_check_host.js
@@ -0,0 +1,183 @@
+/* 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";
+
+const lazy = {};
+
+// Ensure DNS lookups don't hit the server
+XPCOMUtils.defineLazyServiceGetter(
+ lazy,
+ "gDNSOverride",
+ "@mozilla.org/network/native-dns-override;1",
+ "nsINativeDNSResolverOverride"
+);
+
+add_task(async function setup() {
+ Services.prefs.setStringPref("browser.fixup.alternate.prefix", "www.");
+ Services.prefs.setStringPref("browser.fixup.alternate.suffix", ".com");
+ Services.prefs.setStringPref("browser.fixup.alternate.protocol", "https");
+ Services.prefs.setBoolPref(
+ "browser.urlbar.dnsResolveFullyQualifiedNames",
+ true
+ );
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.fixup.alternate.prefix");
+ Services.prefs.clearUserPref("browser.fixup.alternate.suffix");
+ Services.prefs.clearUserPref("browser.fixup.alternate.protocol");
+ Services.prefs.clearUserPref(
+ "browser.urlbar.dnsResolveFullyQualifiedNames"
+ );
+ });
+});
+
+// TODO: Export Listener from test_dns_override instead of duping
+class Listener {
+ constructor() {
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+ }
+
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ this.resolve([inRequest, inRecord, inStatus]);
+ }
+
+ async firstAddress() {
+ let all = await this.addresses();
+ if (all.length) {
+ return all[0];
+ }
+ return undefined;
+ }
+
+ async addresses() {
+ let [, inRecord] = await this.promise;
+ let addresses = [];
+ if (!inRecord) {
+ return addresses; // returns []
+ }
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ while (inRecord.hasMore()) {
+ addresses.push(inRecord.getNextAddrAsString());
+ }
+ return addresses;
+ }
+
+ then() {
+ return this.promise.then.apply(this.promise, arguments);
+ }
+}
+
+const FAKE_IP = "::1";
+const FAKE_INTRANET_IP = "::2";
+const ORIGIN_ATTRIBUTE = {};
+
+add_task(async function test_uri_with_force_fixup() {
+ let listener = new Listener();
+ let { fixedURI } = Services.uriFixup.forceHttpFixup("http://www.example.com");
+
+ lazy.gDNSOverride.addIPOverride(fixedURI.displayHost, FAKE_IP);
+
+ Services.uriFixup.checkHost(fixedURI, listener, ORIGIN_ATTRIBUTE);
+ Assert.equal(
+ await listener.firstAddress(),
+ FAKE_IP,
+ "Should've received fake IP"
+ );
+
+ lazy.gDNSOverride.clearHostOverride(fixedURI.displayHost);
+ Services.dns.clearCache(false);
+});
+
+add_task(async function test_uri_with_get_fixup() {
+ let listener = new Listener();
+ let uri = Services.io.newURI("http://www.example.com");
+
+ lazy.gDNSOverride.addIPOverride(uri.displayHost, FAKE_IP);
+
+ Services.uriFixup.checkHost(uri, listener, ORIGIN_ATTRIBUTE);
+ Assert.equal(
+ await listener.firstAddress(),
+ FAKE_IP,
+ "Should've received fake IP"
+ );
+
+ lazy.gDNSOverride.clearHostOverride(uri.displayHost);
+ Services.dns.clearCache(false);
+});
+
+add_task(async function test_intranet_like_uri() {
+ let listener = new Listener();
+ let uri = Services.io.newURI("http://someintranet");
+
+ lazy.gDNSOverride.addIPOverride(uri.displayHost, FAKE_IP);
+ // Hosts without periods should end with a period to make them FQN
+ lazy.gDNSOverride.addIPOverride(uri.displayHost + ".", FAKE_INTRANET_IP);
+
+ Services.uriFixup.checkHost(uri, listener, ORIGIN_ATTRIBUTE);
+ Assert.deepEqual(
+ await listener.addresses(),
+ FAKE_INTRANET_IP,
+ "Should've received fake intranet IP"
+ );
+
+ lazy.gDNSOverride.clearHostOverride(uri.displayHost);
+ lazy.gDNSOverride.clearHostOverride(uri.displayHost + ".");
+ Services.dns.clearCache(false);
+});
+
+add_task(async function test_intranet_like_uri_without_fixup() {
+ let listener = new Listener();
+ let uri = Services.io.newURI("http://someintranet");
+ Services.prefs.setBoolPref(
+ "browser.urlbar.dnsResolveFullyQualifiedNames",
+ false
+ );
+
+ lazy.gDNSOverride.addIPOverride(uri.displayHost, FAKE_IP);
+ // Hosts without periods should end with a period to make them FQN
+ lazy.gDNSOverride.addIPOverride(uri.displayHost + ".", FAKE_INTRANET_IP);
+
+ Services.uriFixup.checkHost(uri, listener, ORIGIN_ATTRIBUTE);
+ Assert.deepEqual(
+ await listener.addresses(),
+ FAKE_IP,
+ "Should've received non-fixed up IP"
+ );
+
+ lazy.gDNSOverride.clearHostOverride(uri.displayHost);
+ lazy.gDNSOverride.clearHostOverride(uri.displayHost + ".");
+ Services.dns.clearCache(false);
+});
+
+add_task(async function test_ip_address() {
+ let listener = new Listener();
+ let uri = Services.io.newURI("http://192.168.0.1");
+
+ // Testing IP address being passed into the method
+ // requires observing if the asyncResolve method gets called
+ let didResolve = false;
+ let topic = "uri-fixup-check-dns";
+ let observer = {
+ observe(aSubject, aTopicInner, aData) {
+ if (aTopicInner == topic) {
+ didResolve = true;
+ }
+ },
+ };
+ Services.obs.addObserver(observer, topic);
+ lazy.gDNSOverride.addIPOverride(uri.displayHost, FAKE_IP);
+
+ Services.uriFixup.checkHost(uri, listener, ORIGIN_ATTRIBUTE);
+ Assert.equal(
+ didResolve,
+ false,
+ "Passing an IP address should not conduct a host lookup"
+ );
+
+ lazy.gDNSOverride.clearHostOverride(uri.displayHost);
+ Services.dns.clearCache(false);
+ Services.obs.removeObserver(observer, topic);
+});
diff --git a/docshell/test/unit/test_URIFixup_external_protocol_fallback.js b/docshell/test/unit/test_URIFixup_external_protocol_fallback.js
new file mode 100644
index 0000000000..0846070b7a
--- /dev/null
+++ b/docshell/test/unit/test_URIFixup_external_protocol_fallback.js
@@ -0,0 +1,106 @@
+/* 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";
+
+// Test whether fallback mechanism is working if don't trust nsIExternalProtocolService.
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+const { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+add_task(async function setup() {
+ info(
+ "Prepare mock nsIExternalProtocolService whose externalProtocolHandlerExists returns always true"
+ );
+ const externalProtocolService = Cc[
+ "@mozilla.org/uriloader/external-protocol-service;1"
+ ].getService(Ci.nsIExternalProtocolService);
+ const mockId = MockRegistrar.register(
+ "@mozilla.org/uriloader/external-protocol-service;1",
+ {
+ getProtocolHandlerInfo: scheme =>
+ externalProtocolService.getProtocolHandlerInfo(scheme),
+ externalProtocolHandlerExists: () => true,
+ QueryInterface: ChromeUtils.generateQI(["nsIExternalProtocolService"]),
+ }
+ );
+ const mockExternalProtocolService = Cc[
+ "@mozilla.org/uriloader/external-protocol-service;1"
+ ].getService(Ci.nsIExternalProtocolService);
+ Assert.ok(
+ mockExternalProtocolService.externalProtocolHandlerExists("__invalid__"),
+ "Mock service is working"
+ );
+
+ info("Register new dummy protocol");
+ const dummyProtocolHandlerInfo =
+ externalProtocolService.getProtocolHandlerInfo("dummy");
+ const handlerService = Cc[
+ "@mozilla.org/uriloader/handler-service;1"
+ ].getService(Ci.nsIHandlerService);
+ handlerService.store(dummyProtocolHandlerInfo);
+
+ info("Prepare test search engine");
+ await setupSearchService();
+ await addTestEngines();
+ await Services.search.setDefault(
+ Services.search.getEngineByName(kSearchEngineID),
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+
+ registerCleanupFunction(() => {
+ handlerService.remove(dummyProtocolHandlerInfo);
+ MockRegistrar.unregister(mockId);
+ });
+});
+
+add_task(function basic() {
+ const testData = [
+ {
+ input: "mailto:test@example.com",
+ expected:
+ isSupportedInHandlerService("mailto") ||
+ // Thunderbird IS a mailto handler, it doesn't have handlers.
+ AppConstants.MOZ_APP_NAME == "thunderbird"
+ ? "mailto:test@example.com"
+ : "http://mailto:test@example.com/",
+ },
+ {
+ input: "keyword:search",
+ expected: "https://www.example.org/?search=keyword%3Asearch",
+ },
+ {
+ input: "dummy:protocol",
+ expected: "dummy:protocol",
+ },
+ ];
+
+ for (const { input, expected } of testData) {
+ assertFixup(input, expected);
+ }
+});
+
+function assertFixup(input, expected) {
+ const { preferredURI } = Services.uriFixup.getFixupURIInfo(
+ input,
+ Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS
+ );
+ Assert.equal(preferredURI.spec, expected);
+}
+
+function isSupportedInHandlerService(scheme) {
+ const externalProtocolService = Cc[
+ "@mozilla.org/uriloader/external-protocol-service;1"
+ ].getService(Ci.nsIExternalProtocolService);
+ const handlerService = Cc[
+ "@mozilla.org/uriloader/handler-service;1"
+ ].getService(Ci.nsIHandlerService);
+ return handlerService.exists(
+ externalProtocolService.getProtocolHandlerInfo(scheme)
+ );
+}
diff --git a/docshell/test/unit/test_URIFixup_forced.js b/docshell/test/unit/test_URIFixup_forced.js
new file mode 100644
index 0000000000..1d4dfd94a7
--- /dev/null
+++ b/docshell/test/unit/test_URIFixup_forced.js
@@ -0,0 +1,159 @@
+/* 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 data = [
+ {
+ // singleword.
+ wrong: "http://example/",
+ fixed: "https://www.example.com/",
+ },
+ {
+ // upgrade protocol.
+ wrong: "http://www.example.com/",
+ fixed: "https://www.example.com/",
+ noAlternateURI: true,
+ },
+ {
+ // no difference.
+ wrong: "https://www.example.com/",
+ fixed: "https://www.example.com/",
+ noProtocolFixup: true,
+ noAlternateURI: true,
+ },
+ {
+ // don't add prefix when there's more than one dot.
+ wrong: "http://www.example.abc.def/",
+ fixed: "https://www.example.abc.def/",
+ noAlternateURI: true,
+ },
+ {
+ // http -> https.
+ wrong: "http://www.example/",
+ fixed: "https://www.example/",
+ noAlternateURI: true,
+ },
+ {
+ // domain.com -> https://www.domain.com.
+ wrong: "http://example.com/",
+ fixed: "https://www.example.com/",
+ },
+ {
+ // example/example... -> https://www.example.com/example/
+ wrong: "http://example/example/",
+ fixed: "https://www.example.com/example/",
+ },
+ {
+ // example/example/s#q -> www.example.com/example/s#q.
+ wrong: "http://example/example/s#q",
+ fixed: "https://www.example.com/example/s#q",
+ },
+ {
+ wrong: "http://モジラ.org",
+ fixed: "https://www.xn--yck6dwa.org/",
+ },
+ {
+ wrong: "http://モジラ",
+ fixed: "https://www.xn--yck6dwa.com/",
+ },
+ {
+ wrong: "http://xn--yck6dwa",
+ fixed: "https://www.xn--yck6dwa.com/",
+ },
+ {
+ wrong: "https://モジラ.org",
+ fixed: "https://www.xn--yck6dwa.org/",
+ noProtocolFixup: true,
+ },
+ {
+ wrong: "https://モジラ",
+ fixed: "https://www.xn--yck6dwa.com/",
+ noProtocolFixup: true,
+ },
+ {
+ wrong: "https://xn--yck6dwa",
+ fixed: "https://www.xn--yck6dwa.com/",
+ noProtocolFixup: true,
+ },
+ {
+ // Host is https -> fixup typos of protocol
+ wrong: "htp://https://mozilla.org",
+ fixed: "http://https//mozilla.org",
+ noAlternateURI: true,
+ },
+ {
+ // Host is http -> fixup typos of protocol
+ wrong: "ttp://http://mozilla.org",
+ fixed: "http://http//mozilla.org",
+ noAlternateURI: true,
+ },
+ {
+ // Host is localhost -> fixup typos of protocol
+ wrong: "htps://localhost://mozilla.org",
+ fixed: "https://localhost//mozilla.org",
+ noAlternateURI: true,
+ },
+ {
+ // view-source is not http/https -> error
+ wrong: "view-source:http://example/example/example/example",
+ reject: true,
+ comment: "Scheme should be either http or https",
+ },
+ {
+ // file is not http/https -> error
+ wrong: "file://http://example/example/example/example",
+ reject: true,
+ comment: "Scheme should be either http or https",
+ },
+ {
+ // Protocol is missing -> error
+ wrong: "example.com",
+ reject: true,
+ comment: "Scheme should be either http or https",
+ },
+ {
+ // Empty input -> error
+ wrong: "",
+ reject: true,
+ comment: "Should pass a non-null uri",
+ },
+];
+
+add_task(async function setup() {
+ Services.prefs.setStringPref("browser.fixup.alternate.prefix", "www.");
+ Services.prefs.setStringPref("browser.fixup.alternate.suffix", ".com");
+ Services.prefs.setStringPref("browser.fixup.alternate.protocol", "https");
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.fixup.alternate.prefix");
+ Services.prefs.clearUserPref("browser.fixup.alternate.suffix");
+ Services.prefs.clearUserPref("browser.fixup.alternate.protocol");
+ });
+});
+
+add_task(function test_default_https_pref() {
+ for (let item of data) {
+ if (item.reject) {
+ Assert.throws(
+ () => Services.uriFixup.forceHttpFixup(item.wrong),
+ /NS_ERROR_FAILURE/,
+ item.comment
+ );
+ } else {
+ let { fixupChangedProtocol, fixupCreatedAlternateURI, fixedURI } =
+ Services.uriFixup.forceHttpFixup(item.wrong);
+ Assert.equal(fixedURI.spec, item.fixed, "Specs should be the same");
+ Assert.equal(
+ fixupChangedProtocol,
+ !item.noProtocolFixup,
+ `fixupChangedProtocol should be ${!item.noAlternateURI}`
+ );
+ Assert.equal(
+ fixupCreatedAlternateURI,
+ !item.noAlternateURI,
+ `fixupCreatedAlternateURI should be ${!item.limitedFixup}`
+ );
+ }
+ }
+});
diff --git a/docshell/test/unit/test_URIFixup_info.js b/docshell/test/unit/test_URIFixup_info.js
new file mode 100644
index 0000000000..89514cb00c
--- /dev/null
+++ b/docshell/test/unit/test_URIFixup_info.js
@@ -0,0 +1,1075 @@
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+const kForceDNSLookup = "browser.fixup.dns_first_for_single_words";
+const kFixupEnabled = "browser.fixup.alternate.enabled";
+
+// TODO(bug 1522134), this test should also use
+// combinations of the following flags.
+var flagInputs = [
+ Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP,
+ Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP |
+ Services.uriFixup.FIXUP_FLAG_PRIVATE_CONTEXT,
+ Services.uriFixup.FIXUP_FLAGS_MAKE_ALTERNATE_URI,
+ Services.uriFixup.FIXUP_FLAG_FORCE_ALTERNATE_URI,
+ Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS,
+ // This should not really generate a search, but it does, see Bug 1588118.
+ Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
+ Services.uriFixup.FIXUP_FLAG_PRIVATE_CONTEXT,
+];
+
+/*
+ The following properties are supported for these test cases:
+ {
+ input: "", // Input string, required
+ fixedURI: "", // Expected fixedURI
+ alternateURI: "", // Expected alternateURI
+ keywordLookup: false, // Whether a keyword lookup is expected
+ protocolChange: false, // Whether a protocol change is expected
+ inWhitelist: false, // Whether the input host is in the whitelist
+ affectedByDNSForSingleWordHosts: false, // Whether the input host could be a host, but is normally assumed to be a keyword query
+ }
+*/
+var testcases = [
+ {
+ input: "about:home",
+ fixedURI: "about:home",
+ },
+ {
+ input: "http://www.mozilla.org",
+ fixedURI: "http://www.mozilla.org/",
+ },
+ {
+ input: "http://127.0.0.1/",
+ fixedURI: "http://127.0.0.1/",
+ },
+ {
+ input: "file:///foo/bar",
+ fixedURI: "file:///foo/bar",
+ },
+ {
+ input: "://www.mozilla.org",
+ fixedURI: "http://www.mozilla.org/",
+ protocolChange: true,
+ },
+ {
+ input: "www.mozilla.org",
+ fixedURI: "http://www.mozilla.org/",
+ protocolChange: true,
+ },
+ {
+ input: "http://mozilla/",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ },
+ {
+ input: "http://test./",
+ fixedURI: "http://test./",
+ alternateURI: "https://www.test./",
+ },
+ {
+ input: "127.0.0.1",
+ fixedURI: "http://127.0.0.1/",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3.4/",
+ fixedURI: "http://1.2.3.4/",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3.4/foo",
+ fixedURI: "http://1.2.3.4/foo",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3.4:8000",
+ fixedURI: "http://1.2.3.4:8000/",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3.4:8000/",
+ fixedURI: "http://1.2.3.4:8000/",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3.4:8000/foo",
+ fixedURI: "http://1.2.3.4:8000/foo",
+ protocolChange: true,
+ },
+ {
+ input: "192.168.10.110",
+ fixedURI: "http://192.168.10.110/",
+ protocolChange: true,
+ },
+ {
+ input: "192.168.10.110/123",
+ fixedURI: "http://192.168.10.110/123",
+ protocolChange: true,
+ },
+ {
+ input: "192.168.10.110/123foo",
+ fixedURI: "http://192.168.10.110/123foo",
+ protocolChange: true,
+ },
+ {
+ input: "192.168.10.110:1234/123",
+ fixedURI: "http://192.168.10.110:1234/123",
+ protocolChange: true,
+ },
+ {
+ input: "192.168.10.110:1234/123foo",
+ fixedURI: "http://192.168.10.110:1234/123foo",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3",
+ fixedURI: "http://1.2.0.3/",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3/",
+ fixedURI: "http://1.2.0.3/",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3/foo",
+ fixedURI: "http://1.2.0.3/foo",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3/123",
+ fixedURI: "http://1.2.0.3/123",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3:8000",
+ fixedURI: "http://1.2.0.3:8000/",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3:8000/",
+ fixedURI: "http://1.2.0.3:8000/",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3:8000/foo",
+ fixedURI: "http://1.2.0.3:8000/foo",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3:8000/123",
+ fixedURI: "http://1.2.0.3:8000/123",
+ protocolChange: true,
+ },
+ {
+ input: "http://1.2.3",
+ fixedURI: "http://1.2.0.3/",
+ },
+ {
+ input: "http://1.2.3/",
+ fixedURI: "http://1.2.0.3/",
+ },
+ {
+ input: "http://1.2.3/foo",
+ fixedURI: "http://1.2.0.3/foo",
+ },
+ {
+ input: "[::1]",
+ fixedURI: "http://[::1]/",
+ protocolChange: true,
+ },
+ {
+ input: "[::1]/",
+ fixedURI: "http://[::1]/",
+ protocolChange: true,
+ },
+ {
+ input: "[::1]:8000",
+ fixedURI: "http://[::1]:8000/",
+ protocolChange: true,
+ },
+ {
+ input: "[::1]:8000/",
+ fixedURI: "http://[::1]:8000/",
+ protocolChange: true,
+ },
+ {
+ input: "[[::1]]/",
+ keywordLookup: true,
+ },
+ {
+ input: "[fe80:cd00:0:cde:1257:0:211e:729c]",
+ fixedURI: "http://[fe80:cd00:0:cde:1257:0:211e:729c]/",
+ protocolChange: true,
+ },
+ {
+ input: "[64:ff9b::8.8.8.8]",
+ fixedURI: "http://[64:ff9b::808:808]/",
+ protocolChange: true,
+ },
+ {
+ input: "[64:ff9b::8.8.8.8]/~moz",
+ fixedURI: "http://[64:ff9b::808:808]/~moz",
+ protocolChange: true,
+ },
+ {
+ input: "[::1][::1]",
+ keywordLookup: true,
+ },
+ {
+ input: "[::1][100",
+ keywordLookup: true,
+ },
+ {
+ input: "[::1]]",
+ keywordLookup: true,
+ },
+ {
+ input: "1234",
+ fixedURI: "http://0.0.4.210/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "whitelisted/foo.txt",
+ fixedURI: "http://whitelisted/foo.txt",
+ alternateURI: "https://www.whitelisted.com/foo.txt",
+ protocolChange: true,
+ },
+ {
+ input: "mozilla",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "test.",
+ fixedURI: "http://test./",
+ alternateURI: "https://www.test./",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: ".test",
+ fixedURI: "http://.test/",
+ alternateURI: "https://www.test/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "mozilla is amazing",
+ keywordLookup: true,
+ },
+ {
+ input: "search ?mozilla",
+ keywordLookup: true,
+ },
+ {
+ input: "mozilla .com",
+ keywordLookup: true,
+ },
+ {
+ input: "what if firefox?",
+ keywordLookup: true,
+ },
+ {
+ input: "london's map",
+ keywordLookup: true,
+ },
+ {
+ input: "mozilla ",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: " mozilla ",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "mozilla \\",
+ keywordLookup: true,
+ },
+ {
+ input: "mozilla \\ foo.txt",
+ keywordLookup: true,
+ },
+ {
+ input: "mozilla \\\r foo.txt",
+ keywordLookup: true,
+ },
+ {
+ input: "mozilla\n",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "mozilla \r\n",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "moz\r\nfirefox\nos\r",
+ fixedURI: "http://mozfirefoxos/",
+ alternateURI: "https://www.mozfirefoxos.com/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "moz\r\n firefox\n",
+ keywordLookup: true,
+ },
+ {
+ input: "",
+ keywordLookup: true,
+ },
+ {
+ input: "[]",
+ keywordLookup: true,
+ },
+ {
+ input: "http://whitelisted/",
+ fixedURI: "http://whitelisted/",
+ alternateURI: "https://www.whitelisted.com/",
+ inWhitelist: true,
+ },
+ {
+ input: "whitelisted",
+ fixedURI: "http://whitelisted/",
+ alternateURI: "https://www.whitelisted.com/",
+ protocolChange: true,
+ inWhitelist: true,
+ },
+ {
+ input: "whitelisted.",
+ fixedURI: "http://whitelisted./",
+ alternateURI: "https://www.whitelisted./",
+ protocolChange: true,
+ inWhitelist: true,
+ },
+ {
+ input: "mochi.test",
+ fixedURI: "http://mochi.test/",
+ alternateURI: "https://www.mochi.test/",
+ protocolChange: true,
+ inWhitelist: true,
+ },
+ // local.domain is a whitelisted suffix...
+ {
+ input: "some.local.domain",
+ fixedURI: "http://some.local.domain/",
+ protocolChange: true,
+ inWhitelist: true,
+ },
+ // ...but .domain is not.
+ {
+ input: "some.domain",
+ fixedURI: "http://some.domain/",
+ alternateURI: "https://www.some.domain/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "café.com",
+ fixedURI: "http://xn--caf-dma.com/",
+ alternateURI: "https://www.xn--caf-dma.com/",
+ protocolChange: true,
+ },
+ {
+ input: "mozilla.nonexistent",
+ fixedURI: "http://mozilla.nonexistent/",
+ alternateURI: "https://www.mozilla.nonexistent/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "mochi.ocm",
+ fixedURI: "http://mochi.com/",
+ alternateURI: "https://www.mochi.com/",
+ protocolChange: true,
+ },
+ {
+ input: "47.6182,-122.830",
+ fixedURI: "http://47.6182,-122.830/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "-47.6182,-23.51",
+ fixedURI: "http://-47.6182,-23.51/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "-22.14,23.51-",
+ fixedURI: "http://-22.14,23.51-/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "32.7",
+ fixedURI: "http://32.0.0.7/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "5+2",
+ fixedURI: "http://5+2/",
+ alternateURI: "https://www.5+2.com/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "5/2",
+ fixedURI: "http://0.0.0.5/2",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "moz ?.::%27",
+ keywordLookup: true,
+ },
+ {
+ input: "mozilla.com/?q=search",
+ fixedURI: "http://mozilla.com/?q=search",
+ alternateURI: "https://www.mozilla.com/?q=search",
+ protocolChange: true,
+ },
+ {
+ input: "mozilla.com?q=search",
+ fixedURI: "http://mozilla.com/?q=search",
+ alternateURI: "https://www.mozilla.com/?q=search",
+ protocolChange: true,
+ },
+ {
+ input: "mozilla.com ?q=search",
+ keywordLookup: true,
+ },
+ {
+ input: "mozilla.com.?q=search",
+ fixedURI: "http://mozilla.com./?q=search",
+ protocolChange: true,
+ },
+ {
+ input: "mozilla.com'?q=search",
+ fixedURI: "http://mozilla.com/?q=search",
+ alternateURI: "https://www.mozilla.com/?q=search",
+ protocolChange: true,
+ },
+ {
+ input: "mozilla.com':search",
+ keywordLookup: true,
+ },
+ {
+ input: "[mozilla]",
+ keywordLookup: true,
+ },
+ {
+ input: "':?",
+ fixedURI: "http://'/?",
+ alternateURI: "https://www.'.com/?",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "whitelisted?.com",
+ fixedURI: "http://whitelisted/?.com",
+ alternateURI: "https://www.whitelisted.com/?.com",
+ protocolChange: true,
+ },
+ {
+ input: "?'.com",
+ keywordLookup: true,
+ },
+ {
+ input: ".com",
+ keywordLookup: true,
+ affectedByDNSForSingleWordHosts: true,
+ fixedURI: "http://.com/",
+ alternateURI: "https://www.com/",
+ protocolChange: true,
+ },
+ {
+ input: "' ?.com",
+ keywordLookup: true,
+ },
+ {
+ input: "?mozilla",
+ keywordLookup: true,
+ },
+ {
+ input: "??mozilla",
+ keywordLookup: true,
+ },
+ {
+ input: "mozilla/",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ protocolChange: true,
+ },
+ {
+ input: "mozilla",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ protocolChange: true,
+ keywordLookup: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "mozilla5/2",
+ fixedURI: "http://mozilla5/2",
+ alternateURI: "https://www.mozilla5.com/2",
+ protocolChange: true,
+ keywordLookup: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "mozilla/foo",
+ fixedURI: "http://mozilla/foo",
+ alternateURI: "https://www.mozilla.com/foo",
+ protocolChange: true,
+ keywordLookup: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "mozilla\\",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "localhost",
+ fixedURI: "http://localhost/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "http",
+ fixedURI: "http://http/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "https",
+ fixedURI: "http://https/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "localhost:8080",
+ fixedURI: "http://localhost:8080/",
+ protocolChange: true,
+ },
+ {
+ input: "plonk:8080",
+ fixedURI: "http://plonk:8080/",
+ protocolChange: true,
+ },
+ {
+ input: "plonk:8080?test",
+ fixedURI: "http://plonk:8080/?test",
+ protocolChange: true,
+ },
+ {
+ input: "plonk:8080#test",
+ fixedURI: "http://plonk:8080/#test",
+ protocolChange: true,
+ },
+ {
+ input: "plonk/ #",
+ fixedURI: "http://plonk/%20#",
+ alternateURI: "https://www.plonk.com/%20#",
+ protocolChange: true,
+ keywordLookup: false,
+ },
+ {
+ input: "blah.com.",
+ fixedURI: "http://blah.com./",
+ protocolChange: true,
+ },
+ {
+ input:
+ "\u10E0\u10D4\u10D2\u10D8\u10E1\u10E2\u10E0\u10D0\u10EA\u10D8\u10D0.\u10D2\u10D4",
+ fixedURI: "http://xn--lodaehvb5cdik4g.xn--node/",
+ alternateURI: "https://www.xn--lodaehvb5cdik4g.xn--node/",
+ protocolChange: true,
+ },
+ {
+ input: " \t mozilla.org/\t \t ",
+ fixedURI: "http://mozilla.org/",
+ alternateURI: "https://www.mozilla.org/",
+ protocolChange: true,
+ },
+ {
+ input: " moz\ti\tlla.org ",
+ keywordLookup: true,
+ },
+ {
+ input: "mozilla/",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ protocolChange: true,
+ },
+ {
+ input: "mozilla/ test /",
+ fixedURI: "http://mozilla/%20test%20/",
+ alternateURI: "https://www.mozilla.com/%20test%20/",
+ protocolChange: true,
+ },
+ {
+ input: "mozilla /test/",
+ keywordLookup: true,
+ },
+ {
+ input: "pserver:8080",
+ fixedURI: "http://pserver:8080/",
+ protocolChange: true,
+ },
+ {
+ input: "http;mozilla",
+ fixedURI: "http://http;mozilla/",
+ alternateURI: "https://www.http;mozilla.com/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "http//mozilla.org",
+ fixedURI: "http://mozilla.org/",
+ shouldRunTest: flags =>
+ flags & Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS,
+ },
+ {
+ input: "http//mozilla.org",
+ fixedURI: "http://http//mozilla.org",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ shouldRunTest: flags =>
+ !(flags & Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS),
+ },
+ {
+ input: "www.mozilla",
+ fixedURI: "http://www.mozilla/",
+ protocolChange: true,
+ },
+ {
+ input: "https://sub.www..mozilla...org/",
+ fixedURI: "https://sub.www.mozilla.org/",
+ },
+ {
+ input: "sub.www..mozilla...org/",
+ fixedURI: "http://sub.www.mozilla.org/",
+ protocolChange: true,
+ },
+ {
+ input: "sub.www..mozilla...org",
+ fixedURI: "http://sub.www.mozilla.org/",
+ protocolChange: true,
+ },
+ {
+ input: "www...mozilla",
+ fixedURI: "http://www.mozilla/",
+ keywordLookup: true,
+ protocolChange: true,
+ shouldRunTest: flags =>
+ !gSingleWordDNSLookup &&
+ flags & Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP,
+ },
+ {
+ input: "www...mozilla",
+ fixedURI: "http://www.mozilla/",
+ protocolChange: true,
+ shouldRunTest: flags =>
+ gSingleWordDNSLookup ||
+ !(flags & Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP),
+ },
+ {
+ input: "mozilla...org",
+ fixedURI: "http://mozilla.org/",
+ protocolChange: true,
+ },
+ {
+ input: "モジラ...org",
+ fixedURI: "http://xn--yck6dwa.org/",
+ protocolChange: true,
+ },
+ {
+ input: "user@localhost",
+ fixedURI: "http://user@localhost/",
+ protocolChange: true,
+ shouldRunTest: () => gSingleWordDNSLookup,
+ },
+ {
+ input: "user@localhost",
+ fixedURI: "http://user@localhost/",
+ keywordLookup: true,
+ protocolChange: true,
+ shouldRunTest: () => !gSingleWordDNSLookup,
+ },
+ {
+ input: "user@192.168.0.1",
+ fixedURI: "http://user@192.168.0.1/",
+ protocolChange: true,
+ },
+ {
+ input: "user@dummy-host",
+ fixedURI: "http://user@dummy-host/",
+ protocolChange: true,
+ shouldRunTest: () => gSingleWordDNSLookup,
+ },
+ {
+ input: "user@dummy-host",
+ fixedURI: "http://user@dummy-host/",
+ keywordLookup: true,
+ protocolChange: true,
+ shouldRunTest: () => !gSingleWordDNSLookup,
+ },
+ {
+ input: "user:pass@dummy-host",
+ fixedURI: "http://user:pass@dummy-host/",
+ protocolChange: true,
+ },
+ {
+ input: ":pass@dummy-host",
+ fixedURI: "http://:pass@dummy-host/",
+ protocolChange: true,
+ },
+ {
+ input: "user@dummy-host/path",
+ fixedURI: "http://user@dummy-host/path",
+ protocolChange: true,
+ shouldRunTest: () => gSingleWordDNSLookup,
+ },
+ {
+ input: "jar:file:///omni.ja!/",
+ fixedURI: "jar:file:///omni.ja!/",
+ },
+];
+
+if (AppConstants.platform == "win") {
+ testcases.push({
+ input: "C:\\some\\file.txt",
+ fixedURI: "file:///C:/some/file.txt",
+ protocolChange: true,
+ });
+ testcases.push({
+ input: "//mozilla",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ protocolChange: true,
+ });
+ testcases.push({
+ input: "/a",
+ fixedURI: "http://a/",
+ alternateURI: "https://www.a.com/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ });
+} else {
+ testcases.push({
+ input: "/some/file.txt",
+ fixedURI: "file:///some/file.txt",
+ protocolChange: true,
+ });
+ testcases.push({
+ input: "//mozilla",
+ fixedURI: "file:////mozilla",
+ protocolChange: true,
+ });
+ testcases.push({
+ input: "/a",
+ fixedURI: "file:///a",
+ protocolChange: true,
+ });
+}
+
+function sanitize(input) {
+ return input.replace(/\r|\n/g, "").trim();
+}
+
+add_task(async function setup() {
+ var prefList = [
+ "browser.fixup.typo.scheme",
+ "keyword.enabled",
+ "browser.fixup.domainwhitelist.whitelisted",
+ "browser.fixup.domainsuffixwhitelist.test",
+ "browser.fixup.domainsuffixwhitelist.local.domain",
+ "browser.search.separatePrivateDefault",
+ "browser.search.separatePrivateDefault.ui.enabled",
+ ];
+ for (let pref of prefList) {
+ Services.prefs.setBoolPref(pref, true);
+ }
+
+ await setupSearchService();
+ await addTestEngines();
+
+ await Services.search.setDefault(
+ Services.search.getEngineByName(kSearchEngineID),
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ await Services.search.setDefaultPrivate(
+ Services.search.getEngineByName(kPrivateSearchEngineID),
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+});
+
+var gSingleWordDNSLookup = false;
+add_task(async function run_test() {
+ // Only keywordlookup things should be affected by requiring a DNS lookup for single-word hosts:
+ info(
+ "Check only keyword lookup testcases should be affected by requiring DNS for single hosts"
+ );
+ let affectedTests = testcases.filter(
+ t => !t.keywordLookup && t.affectedByDNSForSingleWordHosts
+ );
+ if (affectedTests.length) {
+ for (let testcase of affectedTests) {
+ info("Affected: " + testcase.input);
+ }
+ }
+ Assert.equal(affectedTests.length, 0);
+ await do_single_test_run();
+ await do_single_test_run(true);
+ gSingleWordDNSLookup = true;
+ await do_single_test_run();
+ await do_single_test_run(true);
+ gSingleWordDNSLookup = false;
+ await Services.search.setDefault(
+ Services.search.getEngineByName(kPostSearchEngineID),
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ await do_single_test_run();
+ await do_single_test_run(true);
+});
+
+async function do_single_test_run(browserFixupAlternateEnabled = false) {
+ Services.prefs.setBoolPref(kForceDNSLookup, gSingleWordDNSLookup);
+ Services.prefs.setBoolPref(kFixupEnabled, browserFixupAlternateEnabled);
+ let relevantTests = gSingleWordDNSLookup
+ ? testcases.filter(t => t.keywordLookup)
+ : testcases;
+
+ let engine = await Services.search.getDefault();
+ let engineUrl =
+ engine.name == kPostSearchEngineID
+ ? kPostSearchEngineURL
+ : kSearchEngineURL;
+ let privateEngine = await Services.search.getDefaultPrivate();
+ let privateEngineUrl = kPrivateSearchEngineURL;
+
+ for (let {
+ input: testInput,
+ fixedURI: expectedFixedURI,
+ alternateURI: alternativeURI,
+ keywordLookup: expectKeywordLookup,
+ protocolChange: expectProtocolChange,
+ inWhitelist: inWhitelist,
+ affectedByDNSForSingleWordHosts: affectedByDNSForSingleWordHosts,
+ shouldRunTest,
+ } of relevantTests) {
+ // Explicitly force these into a boolean
+ expectKeywordLookup = !!expectKeywordLookup;
+ expectProtocolChange = !!expectProtocolChange;
+ inWhitelist = !!inWhitelist;
+ affectedByDNSForSingleWordHosts = !!affectedByDNSForSingleWordHosts;
+
+ expectKeywordLookup =
+ expectKeywordLookup &&
+ (!affectedByDNSForSingleWordHosts || !gSingleWordDNSLookup);
+
+ for (let flags of flagInputs) {
+ info(
+ 'Checking "' +
+ testInput +
+ '" with flags ' +
+ flags +
+ " (DNS lookup for single words: " +
+ (gSingleWordDNSLookup ? "yes" : "no") +
+ "," +
+ " browser fixup alt enabled: " +
+ (browserFixupAlternateEnabled ? "yes" : "no") +
+ ")"
+ );
+
+ if (shouldRunTest && !shouldRunTest(flags)) {
+ continue;
+ }
+
+ let URIInfo;
+ try {
+ URIInfo = Services.uriFixup.getFixupURIInfo(testInput, flags);
+ } catch (ex) {
+ // Both APIs should return an error in the same cases.
+ info("Caught exception: " + ex);
+ Assert.equal(expectedFixedURI, null);
+ continue;
+ }
+
+ // Check the fixedURI:
+ let fixupFlag = browserFixupAlternateEnabled
+ ? Services.uriFixup.FIXUP_FLAGS_MAKE_ALTERNATE_URI
+ : Services.uriFixup.FIXUP_FLAG_NONE;
+ let forceAlternativeURI =
+ flags & Services.uriFixup.FIXUP_FLAG_FORCE_ALTERNATE_URI;
+ let makeAlternativeURI = flags & fixupFlag || forceAlternativeURI;
+
+ if (makeAlternativeURI && alternativeURI != null) {
+ Assert.equal(
+ URIInfo.fixedURI.spec,
+ alternativeURI,
+ "should have gotten alternate URI"
+ );
+ } else {
+ Assert.equal(
+ URIInfo.fixedURI && URIInfo.fixedURI.spec,
+ expectedFixedURI,
+ "should get correct fixed URI"
+ );
+ }
+
+ // Check booleans on input:
+ let couldDoKeywordLookup =
+ flags & Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
+ Assert.equal(
+ !!URIInfo.keywordProviderName,
+ couldDoKeywordLookup && expectKeywordLookup,
+ "keyword lookup as expected"
+ );
+
+ let expectProtocolChangeAfterAlternate = false;
+ // If alternativeURI was created, the protocol of the URI
+ // might have been changed to browser.fixup.alternate.protocol
+ // If the protocol is not the same as what was in expectedFixedURI,
+ // the protocol must've changed in the fixup process.
+ if (
+ makeAlternativeURI &&
+ alternativeURI != null &&
+ !expectedFixedURI.startsWith(URIInfo.fixedURI.scheme)
+ ) {
+ expectProtocolChangeAfterAlternate = true;
+ }
+
+ Assert.equal(
+ URIInfo.fixupChangedProtocol,
+ expectProtocolChange || expectProtocolChangeAfterAlternate,
+ "protocol change as expected"
+ );
+ Assert.equal(
+ URIInfo.fixupCreatedAlternateURI,
+ makeAlternativeURI && alternativeURI != null,
+ "alternative URI as expected"
+ );
+
+ // Check the preferred URI
+ if (couldDoKeywordLookup) {
+ if (expectKeywordLookup) {
+ if (!inWhitelist) {
+ let urlparamInput = encodeURIComponent(sanitize(testInput)).replace(
+ /%20/g,
+ "+"
+ );
+ // If the input starts with `?`, then URIInfo.preferredURI.spec will omit it
+ // In order to test this behaviour, remove `?` only if it is the first character
+ if (urlparamInput.startsWith("%3F")) {
+ urlparamInput = urlparamInput.replace("%3F", "");
+ }
+ let isPrivate =
+ flags & Services.uriFixup.FIXUP_FLAG_PRIVATE_CONTEXT;
+ let searchEngineUrl = isPrivate ? privateEngineUrl : engineUrl;
+ let searchURL = searchEngineUrl.replace(
+ "{searchTerms}",
+ urlparamInput
+ );
+ let spec = URIInfo.preferredURI.spec.replace(/%27/g, "'");
+ Assert.equal(spec, searchURL, "should get correct search URI");
+ let providerName = isPrivate ? privateEngine.name : engine.name;
+ Assert.equal(
+ URIInfo.keywordProviderName,
+ providerName,
+ "should get correct provider name"
+ );
+ // Also check keywordToURI() uses the right engine.
+ let kwInfo = Services.uriFixup.keywordToURI(
+ urlparamInput,
+ isPrivate
+ );
+ Assert.equal(kwInfo.providerName, URIInfo.providerName);
+ if (providerName == kPostSearchEngineID) {
+ Assert.ok(kwInfo.postData);
+ let submission = engine.getSubmission(urlparamInput);
+ let enginePostData = NetUtil.readInputStreamToString(
+ submission.postData,
+ submission.postData.available()
+ );
+ let postData = NetUtil.readInputStreamToString(
+ kwInfo.postData,
+ kwInfo.postData.available()
+ );
+ Assert.equal(postData, enginePostData);
+ }
+ } else {
+ Assert.equal(
+ URIInfo.preferredURI,
+ null,
+ "not expecting a preferred URI"
+ );
+ }
+ } else {
+ Assert.equal(
+ URIInfo.preferredURI.spec,
+ URIInfo.fixedURI.spec,
+ "fixed URI should match"
+ );
+ }
+ } else {
+ // In these cases, we should never be doing a keyword lookup and
+ // the fixed URI should be preferred:
+ let prefURI = URIInfo.preferredURI && URIInfo.preferredURI.spec;
+ let fixedURI = URIInfo.fixedURI && URIInfo.fixedURI.spec;
+ Assert.equal(prefURI, fixedURI, "fixed URI should be same as expected");
+ }
+ Assert.equal(
+ sanitize(testInput),
+ URIInfo.originalInput,
+ "should mirror original input"
+ );
+ }
+ }
+}
diff --git a/docshell/test/unit/test_URIFixup_search.js b/docshell/test/unit/test_URIFixup_search.js
new file mode 100644
index 0000000000..fa1c5b38ba
--- /dev/null
+++ b/docshell/test/unit/test_URIFixup_search.js
@@ -0,0 +1,143 @@
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+var isWin = AppConstants.platform == "win";
+
+var data = [
+ {
+ // Valid should not be changed.
+ wrong: "https://example.com/this/is/a/test.html",
+ fixed: "https://example.com/this/is/a/test.html",
+ },
+ {
+ // Unrecognized protocols should be changed.
+ wrong: "whatever://this/is/a/test.html",
+ fixed: kSearchEngineURL.replace(
+ "{searchTerms}",
+ encodeURIComponent("whatever://this/is/a/test.html")
+ ),
+ },
+
+ {
+ // Unrecognized protocols should be changed.
+ wrong: "whatever://this/is/a/test.html",
+ fixed: kPrivateSearchEngineURL.replace(
+ "{searchTerms}",
+ encodeURIComponent("whatever://this/is/a/test.html")
+ ),
+ inPrivateBrowsing: true,
+ },
+
+ // The following tests check that when a user:password is present in the URL
+ // `user:` isn't treated as an unknown protocol thus leaking the user and
+ // password to the search engine.
+ {
+ wrong: "user:pass@example.com/this/is/a/test.html",
+ fixed: "http://user:pass@example.com/this/is/a/test.html",
+ },
+ {
+ wrong: "user@example.com:8080/this/is/a/test.html",
+ fixed: "http://user@example.com:8080/this/is/a/test.html",
+ },
+ {
+ wrong: "https:pass@example.com/this/is/a/test.html",
+ fixed: "https://pass@example.com/this/is/a/test.html",
+ },
+ {
+ wrong: "user:pass@example.com:8080/this/is/a/test.html",
+ fixed: "http://user:pass@example.com:8080/this/is/a/test.html",
+ },
+ {
+ wrong: "http:user:pass@example.com:8080/this/is/a/test.html",
+ fixed: "http://user:pass@example.com:8080/this/is/a/test.html",
+ },
+ {
+ wrong: "ttp:user:pass@example.com:8080/this/is/a/test.html",
+ fixed: "http://user:pass@example.com:8080/this/is/a/test.html",
+ },
+ {
+ wrong: "nonsense:user:pass@example.com:8080/this/is/a/test.html",
+ fixed: "http://nonsense:user%3Apass@example.com:8080/this/is/a/test.html",
+ },
+ {
+ wrong: "user:@example.com:8080/this/is/a/test.html",
+ fixed: "http://user@example.com:8080/this/is/a/test.html",
+ },
+ {
+ wrong: "//user:pass@example.com:8080/this/is/a/test.html",
+ fixed:
+ (isWin ? "http:" : "file://") +
+ "//user:pass@example.com:8080/this/is/a/test.html",
+ },
+ {
+ wrong: "://user:pass@example.com:8080/this/is/a/test.html",
+ fixed: "http://user:pass@example.com:8080/this/is/a/test.html",
+ },
+ {
+ wrong: "localhost:8080/?param=1",
+ fixed: "http://localhost:8080/?param=1",
+ },
+ {
+ wrong: "localhost:8080?param=1",
+ fixed: "http://localhost:8080/?param=1",
+ },
+ {
+ wrong: "localhost:8080#somewhere",
+ fixed: "http://localhost:8080/#somewhere",
+ },
+ {
+ wrong: "whatever://this/is/a@b/test.html",
+ fixed: kSearchEngineURL.replace(
+ "{searchTerms}",
+ encodeURIComponent("whatever://this/is/a@b/test.html")
+ ),
+ },
+];
+
+var extProtocolSvc = Cc[
+ "@mozilla.org/uriloader/external-protocol-service;1"
+].getService(Ci.nsIExternalProtocolService);
+
+if (extProtocolSvc && extProtocolSvc.externalProtocolHandlerExists("mailto")) {
+ data.push({
+ wrong: "mailto:foo@bar.com",
+ fixed: "mailto:foo@bar.com",
+ });
+}
+
+var len = data.length;
+
+add_task(async function setup() {
+ await setupSearchService();
+ await addTestEngines();
+
+ Services.prefs.setBoolPref("keyword.enabled", true);
+ Services.prefs.setBoolPref("browser.search.separatePrivateDefault", true);
+ Services.prefs.setBoolPref(
+ "browser.search.separatePrivateDefault.ui.enabled",
+ true
+ );
+
+ await Services.search.setDefault(
+ Services.search.getEngineByName(kSearchEngineID),
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ await Services.search.setDefaultPrivate(
+ Services.search.getEngineByName(kPrivateSearchEngineID),
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+});
+
+// Make sure we fix what needs fixing
+add_task(function test_fix_unknown_schemes() {
+ for (let i = 0; i < len; ++i) {
+ let item = data[i];
+ let flags = Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS;
+ if (item.inPrivateBrowsing) {
+ flags |= Services.uriFixup.FIXUP_FLAG_PRIVATE_CONTEXT;
+ }
+ let { preferredURI } = Services.uriFixup.getFixupURIInfo(item.wrong, flags);
+ Assert.equal(preferredURI.spec, item.fixed);
+ }
+});
diff --git a/docshell/test/unit/test_allowJavascript.js b/docshell/test/unit/test_allowJavascript.js
new file mode 100644
index 0000000000..d1341c7bba
--- /dev/null
+++ b/docshell/test/unit/test_allowJavascript.js
@@ -0,0 +1,291 @@
+"use strict";
+
+const { XPCShellContentUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/XPCShellContentUtils.sys.mjs"
+);
+
+XPCShellContentUtils.init(this);
+
+const ACTOR = "AllowJavascript";
+
+const HTML = String.raw`<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <script type="application/javascript">
+ "use strict";
+ var gFiredOnload = false;
+ var gFiredOnclick = false;
+ </script>
+</head>
+<body onload="gFiredOnload = true;" onclick="gFiredOnclick = true;">
+</body>
+</html>`;
+
+const server = XPCShellContentUtils.createHttpServer({
+ hosts: ["example.com", "example.org"],
+});
+
+server.registerPathHandler("/", (request, response) => {
+ response.setHeader("Content-Type", "text/html");
+ response.write(HTML);
+});
+
+const { AllowJavascriptParent } = ChromeUtils.importESModule(
+ "resource://test/AllowJavascriptParent.sys.mjs"
+);
+
+async function assertScriptsAllowed(bc, expectAllowed, desc) {
+ let actor = bc.currentWindowGlobal.getActor(ACTOR);
+ let allowed = await actor.sendQuery("CheckScriptsAllowed");
+ equal(
+ allowed,
+ expectAllowed,
+ `Scripts should be ${expectAllowed ? "" : "dis"}allowed for ${desc}`
+ );
+}
+
+async function assertLoadFired(bc, expectFired, desc) {
+ let actor = bc.currentWindowGlobal.getActor(ACTOR);
+ let fired = await actor.sendQuery("CheckFiredLoadEvent");
+ equal(
+ fired,
+ expectFired,
+ `Should ${expectFired ? "" : "not "}have fired load for ${desc}`
+ );
+}
+
+function createSubframe(bc, url) {
+ let actor = bc.currentWindowGlobal.getActor(ACTOR);
+ return actor.sendQuery("CreateIframe", { url });
+}
+
+add_task(async function () {
+ Services.prefs.setBoolPref("dom.security.https_first", false);
+ ChromeUtils.registerWindowActor(ACTOR, {
+ allFrames: true,
+ child: {
+ esModuleURI: "resource://test/AllowJavascriptChild.sys.mjs",
+ events: { load: { capture: true } },
+ },
+ parent: {
+ esModuleURI: "resource://test/AllowJavascriptParent.sys.mjs",
+ },
+ });
+
+ let page = await XPCShellContentUtils.loadContentPage("http://example.com/", {
+ remote: true,
+ remoteSubframes: true,
+ });
+
+ let bc = page.browsingContext;
+
+ {
+ let oopFrame1 = await createSubframe(bc, "http://example.org/");
+ let inprocFrame1 = await createSubframe(bc, "http://example.com/");
+
+ let oopFrame1OopSub = await createSubframe(
+ oopFrame1,
+ "http://example.com/"
+ );
+ let inprocFrame1OopSub = await createSubframe(
+ inprocFrame1,
+ "http://example.org/"
+ );
+
+ equal(
+ oopFrame1.allowJavascript,
+ true,
+ "OOP BC should inherit allowJavascript from parent"
+ );
+ equal(
+ inprocFrame1.allowJavascript,
+ true,
+ "In-process BC should inherit allowJavascript from parent"
+ );
+ equal(
+ oopFrame1OopSub.allowJavascript,
+ true,
+ "OOP BC child should inherit allowJavascript from parent"
+ );
+ equal(
+ inprocFrame1OopSub.allowJavascript,
+ true,
+ "In-process child BC should inherit allowJavascript from parent"
+ );
+
+ await assertLoadFired(bc, true, "top BC");
+ await assertScriptsAllowed(bc, true, "top BC");
+
+ await assertLoadFired(oopFrame1, true, "OOP frame 1");
+ await assertScriptsAllowed(oopFrame1, true, "OOP frame 1");
+
+ await assertLoadFired(inprocFrame1, true, "In-process frame 1");
+ await assertScriptsAllowed(inprocFrame1, true, "In-process frame 1");
+
+ await assertLoadFired(oopFrame1OopSub, true, "OOP frame 1 subframe");
+ await assertScriptsAllowed(oopFrame1OopSub, true, "OOP frame 1 subframe");
+
+ await assertLoadFired(
+ inprocFrame1OopSub,
+ true,
+ "In-process frame 1 subframe"
+ );
+ await assertScriptsAllowed(
+ inprocFrame1OopSub,
+ true,
+ "In-process frame 1 subframe"
+ );
+
+ bc.allowJavascript = false;
+ await assertScriptsAllowed(bc, false, "top BC with scripts disallowed");
+ await assertScriptsAllowed(
+ oopFrame1,
+ false,
+ "OOP frame 1 with top BC with scripts disallowed"
+ );
+ await assertScriptsAllowed(
+ inprocFrame1,
+ false,
+ "In-process frame 1 with top BC with scripts disallowed"
+ );
+ await assertScriptsAllowed(
+ oopFrame1OopSub,
+ false,
+ "OOP frame 1 subframe with top BC with scripts disallowed"
+ );
+ await assertScriptsAllowed(
+ inprocFrame1OopSub,
+ false,
+ "In-process frame 1 subframe with top BC with scripts disallowed"
+ );
+
+ let oopFrame2 = await createSubframe(bc, "http://example.org/");
+ let inprocFrame2 = await createSubframe(bc, "http://example.com/");
+
+ equal(
+ oopFrame2.allowJavascript,
+ false,
+ "OOP BC 2 should inherit allowJavascript from parent"
+ );
+ equal(
+ inprocFrame2.allowJavascript,
+ false,
+ "In-process BC 2 should inherit allowJavascript from parent"
+ );
+
+ await assertLoadFired(
+ oopFrame2,
+ undefined,
+ "OOP frame 2 with top BC with scripts disallowed"
+ );
+ await assertScriptsAllowed(
+ oopFrame2,
+ false,
+ "OOP frame 2 with top BC with scripts disallowed"
+ );
+ await assertLoadFired(
+ inprocFrame2,
+ undefined,
+ "In-process frame 2 with top BC with scripts disallowed"
+ );
+ await assertScriptsAllowed(
+ inprocFrame2,
+ false,
+ "In-process frame 2 with top BC with scripts disallowed"
+ );
+
+ bc.allowJavascript = true;
+ await assertScriptsAllowed(bc, true, "top BC");
+
+ await assertScriptsAllowed(oopFrame1, true, "OOP frame 1");
+ await assertScriptsAllowed(inprocFrame1, true, "In-process frame 1");
+ await assertScriptsAllowed(oopFrame1OopSub, true, "OOP frame 1 subframe");
+ await assertScriptsAllowed(
+ inprocFrame1OopSub,
+ true,
+ "In-process frame 1 subframe"
+ );
+
+ await assertScriptsAllowed(oopFrame2, false, "OOP frame 2");
+ await assertScriptsAllowed(inprocFrame2, false, "In-process frame 2");
+
+ oopFrame1.currentWindowGlobal.allowJavascript = false;
+ inprocFrame1.currentWindowGlobal.allowJavascript = false;
+
+ await assertScriptsAllowed(
+ oopFrame1,
+ false,
+ "OOP frame 1 with second level WC scripts disallowed"
+ );
+ await assertScriptsAllowed(
+ inprocFrame1,
+ false,
+ "In-process frame 1 with second level WC scripts disallowed"
+ );
+ await assertScriptsAllowed(
+ oopFrame1OopSub,
+ false,
+ "OOP frame 1 subframe second level WC scripts disallowed"
+ );
+ await assertScriptsAllowed(
+ inprocFrame1OopSub,
+ false,
+ "In-process frame 1 subframe with second level WC scripts disallowed"
+ );
+
+ oopFrame1.reload(0);
+ inprocFrame1.reload(0);
+ await Promise.all([
+ AllowJavascriptParent.promiseLoad(oopFrame1),
+ AllowJavascriptParent.promiseLoad(inprocFrame1),
+ ]);
+
+ equal(
+ oopFrame1.currentWindowGlobal.allowJavascript,
+ true,
+ "WindowContext.allowJavascript does not persist after navigation for OOP frame 1"
+ );
+ equal(
+ inprocFrame1.currentWindowGlobal.allowJavascript,
+ true,
+ "WindowContext.allowJavascript does not persist after navigation for in-process frame 1"
+ );
+
+ await assertScriptsAllowed(oopFrame1, true, "OOP frame 1");
+ await assertScriptsAllowed(inprocFrame1, true, "In-process frame 1");
+ }
+
+ bc.allowJavascript = false;
+
+ bc.reload(0);
+ await AllowJavascriptParent.promiseLoad(bc);
+
+ await assertLoadFired(
+ bc,
+ undefined,
+ "top BC with scripts disabled after reload"
+ );
+ await assertScriptsAllowed(
+ bc,
+ false,
+ "top BC with scripts disabled after reload"
+ );
+
+ await page.loadURL("http://example.org/?other");
+ bc = page.browsingContext;
+
+ await assertLoadFired(
+ bc,
+ undefined,
+ "top BC with scripts disabled after navigation"
+ );
+ await assertScriptsAllowed(
+ bc,
+ false,
+ "top BC with scripts disabled after navigation"
+ );
+
+ await page.close();
+ Services.prefs.clearUserPref("dom.security.https_first");
+});
diff --git a/docshell/test/unit/test_browsing_context_structured_clone.js b/docshell/test/unit/test_browsing_context_structured_clone.js
new file mode 100644
index 0000000000..1c91a9b0b2
--- /dev/null
+++ b/docshell/test/unit/test_browsing_context_structured_clone.js
@@ -0,0 +1,70 @@
+"use strict";
+
+add_task(async function test_BrowsingContext_structured_clone() {
+ let browser = Services.appShell.createWindowlessBrowser(false);
+
+ let frame = browser.document.createElement("iframe");
+
+ await new Promise(r => {
+ frame.onload = () => r();
+ browser.document.body.appendChild(frame);
+ });
+
+ let { browsingContext } = frame;
+
+ let sch = new StructuredCloneHolder("debug name", "<anonymized> debug name", {
+ browsingContext,
+ });
+
+ let deserialize = () => sch.deserialize({}, true);
+
+ // Check that decoding a live browsing context produces the correct
+ // object.
+ equal(
+ deserialize().browsingContext,
+ browsingContext,
+ "Got correct browsing context from StructuredClone deserialize"
+ );
+
+ // Check that decoding a second time still succeeds.
+ equal(
+ deserialize().browsingContext,
+ browsingContext,
+ "Got correct browsing context from second StructuredClone deserialize"
+ );
+
+ // Destroy the browsing context and make sure that the decode fails
+ // with a DataCloneError.
+ //
+ // Making sure the BrowsingContext is actually destroyed by the time
+ // we do the second decode is a bit tricky. We obviously have clear
+ // our local references to it, and give the GC a chance to reap them.
+ // And we also, of course, have to destroy the frame that it belongs
+ // to, or its frame loader and window global would hold it alive.
+ //
+ // Beyond that, we don't *have* to reload or destroy the parent
+ // document, but we do anyway just to be safe.
+ //
+
+ frame.remove();
+ frame = null;
+ browsingContext = null;
+
+ browser.document.location.reload();
+ browser.close();
+
+ // We will schedule a precise GC and do both GC and CC a few times, to make
+ // sure we have completely destroyed the WindowGlobal actors (which keep
+ // references to their BrowsingContexts) in order
+ // to allow their (now snow-white) references to be collected.
+ await schedulePreciseGCAndForceCC(3);
+
+ // OK. We can be fairly confident that the BrowsingContext object
+ // stored in our structured clone data has been destroyed. Make sure
+ // that attempting to decode it again leads to the appropriate error.
+ Assert.throws(
+ deserialize,
+ e => e.name === "DataCloneError",
+ "Should get a DataCloneError when trying to decode a dead BrowsingContext"
+ );
+});
diff --git a/docshell/test/unit/test_bug442584.js b/docshell/test/unit/test_bug442584.js
new file mode 100644
index 0000000000..c109557f50
--- /dev/null
+++ b/docshell/test/unit/test_bug442584.js
@@ -0,0 +1,35 @@
+var prefetch = Cc["@mozilla.org/prefetch-service;1"].getService(
+ Ci.nsIPrefetchService
+);
+
+var ReferrerInfo = Components.Constructor(
+ "@mozilla.org/referrer-info;1",
+ "nsIReferrerInfo",
+ "init"
+);
+
+function run_test() {
+ // Fill up the queue
+ Services.prefs.setBoolPref("network.prefetch-next", true);
+ for (var i = 0; i < 5; i++) {
+ var uri = Services.io.newURI("http://localhost/" + i);
+ var referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, uri);
+ prefetch.prefetchURI(uri, referrerInfo, null, true);
+ }
+
+ // Make sure the queue has items in it...
+ Assert.ok(prefetch.hasMoreElements());
+
+ // Now disable the pref to force the queue to empty...
+ Services.prefs.setBoolPref("network.prefetch-next", false);
+ Assert.ok(!prefetch.hasMoreElements());
+
+ // Now reenable the pref, and add more items to the queue.
+ Services.prefs.setBoolPref("network.prefetch-next", true);
+ for (var k = 0; k < 5; k++) {
+ var uri2 = Services.io.newURI("http://localhost/" + k);
+ var referrerInfo2 = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, uri2);
+ prefetch.prefetchURI(uri2, referrerInfo2, null, true);
+ }
+ Assert.ok(prefetch.hasMoreElements());
+}
diff --git a/docshell/test/unit/test_pb_notification.js b/docshell/test/unit/test_pb_notification.js
new file mode 100644
index 0000000000..51cd3b95ff
--- /dev/null
+++ b/docshell/test/unit/test_pb_notification.js
@@ -0,0 +1,18 @@
+function destroy_transient_docshell() {
+ let windowlessBrowser = Services.appShell.createWindowlessBrowser(true);
+ windowlessBrowser.docShell.setOriginAttributes({ privateBrowsingId: 1 });
+ windowlessBrowser.close();
+ do_test_pending();
+ do_timeout(0, Cu.forceGC);
+}
+
+function run_test() {
+ var obs = {
+ observe(aSubject, aTopic, aData) {
+ Assert.equal(aTopic, "last-pb-context-exited");
+ do_test_finished();
+ },
+ };
+ Services.obs.addObserver(obs, "last-pb-context-exited");
+ destroy_transient_docshell();
+}
diff --git a/docshell/test/unit/test_privacy_transition.js b/docshell/test/unit/test_privacy_transition.js
new file mode 100644
index 0000000000..ae1bf71284
--- /dev/null
+++ b/docshell/test/unit/test_privacy_transition.js
@@ -0,0 +1,21 @@
+var gNotifications = 0;
+
+var observer = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIPrivacyTransitionObserver",
+ "nsISupportsWeakReference",
+ ]),
+
+ privateModeChanged(enabled) {
+ gNotifications++;
+ },
+};
+
+function run_test() {
+ let windowlessBrowser = Services.appShell.createWindowlessBrowser(true);
+ windowlessBrowser.docShell.addWeakPrivacyTransitionObserver(observer);
+ windowlessBrowser.docShell.setOriginAttributes({ privateBrowsingId: 1 });
+ windowlessBrowser.docShell.setOriginAttributes({ privateBrowsingId: 0 });
+ windowlessBrowser.close();
+ Assert.equal(gNotifications, 2);
+}
diff --git a/docshell/test/unit/test_subframe_stop_after_parent_error.js b/docshell/test/unit/test_subframe_stop_after_parent_error.js
new file mode 100644
index 0000000000..2f80d18ebb
--- /dev/null
+++ b/docshell/test/unit/test_subframe_stop_after_parent_error.js
@@ -0,0 +1,146 @@
+"use strict";
+// Tests that pending subframe requests for an initial about:blank
+// document do not delay showing load errors (and possibly result in a
+// crash at docShell destruction) for failed document loads.
+
+const { PromiseTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromiseTestUtils.sys.mjs"
+);
+PromiseTestUtils.allowMatchingRejectionsGlobally(/undefined/);
+
+const { XPCShellContentUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/XPCShellContentUtils.sys.mjs"
+);
+
+XPCShellContentUtils.init(this);
+
+const server = XPCShellContentUtils.createHttpServer({
+ hosts: ["example.com"],
+});
+
+// Registers a URL with the HTTP server which will not return a response
+// until we're ready to.
+function registerSlowPage(path) {
+ let result = {
+ url: `http://example.com/${path}`,
+ };
+
+ let finishedPromise = new Promise(resolve => {
+ result.finish = resolve;
+ });
+
+ server.registerPathHandler(`/${path}`, async (request, response) => {
+ response.processAsync();
+
+ response.setHeader("Content-Type", "text/html");
+ response.write("<html><body>Hello.</body></html>");
+
+ await finishedPromise;
+
+ response.finish();
+ });
+
+ return result;
+}
+
+let topFrameRequest = registerSlowPage("top.html");
+let subFrameRequest = registerSlowPage("frame.html");
+
+let thunks = new Set();
+function promiseStateStop(webProgress) {
+ return new Promise(resolve => {
+ let listener = {
+ onStateChange(aWebProgress, request, stateFlags, status) {
+ if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+ webProgress.removeProgressListener(listener);
+
+ thunks.delete(listener);
+ resolve();
+ }
+ },
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+
+ // Keep the listener alive, since it's stored as a weak reference.
+ thunks.add(listener);
+ webProgress.addProgressListener(
+ listener,
+ Ci.nsIWebProgress.NOTIFY_STATE_REQUEST
+ );
+ });
+}
+
+async function runTest(waitForErrorPage) {
+ let page = await XPCShellContentUtils.loadContentPage("about:blank", {
+ remote: false,
+ });
+ let { browser } = page;
+
+ // Watch for the HTTP request for the top frame so that we can cancel
+ // it with an error.
+ let requestPromise = TestUtils.topicObserved(
+ "http-on-modify-request",
+ subject => subject.QueryInterface(Ci.nsIRequest).name == topFrameRequest.url
+ );
+
+ // Create a frame with a source URL which will not return a response
+ // before we cancel it with an error.
+ let doc = browser.contentDocument;
+ let frame = doc.createElement("iframe");
+ frame.src = topFrameRequest.url;
+ doc.body.appendChild(frame);
+
+ // Create a subframe in the initial about:blank document for the above
+ // frame which will not return a response before we cancel the
+ // document request.
+ let frameDoc = frame.contentDocument;
+ let subframe = frameDoc.createElement("iframe");
+ subframe.src = subFrameRequest.url;
+ frameDoc.body.appendChild(subframe);
+
+ let [req] = await requestPromise;
+
+ info("Cancel request for parent frame");
+ req.cancel(Cr.NS_ERROR_PROXY_CONNECTION_REFUSED);
+
+ // Request cancelation is not synchronous, so wait for the STATE_STOP
+ // event to fire.
+ await promiseStateStop(
+ browser.docShell.nsIInterfaceRequestor.getInterface(Ci.nsIWebProgress)
+ );
+
+ // And make a trip through the event loop to give the DocLoader a
+ // chance to update its state.
+ await new Promise(executeSoon);
+
+ if (waitForErrorPage) {
+ // Make sure that canceling the request with an error code actually
+ // shows an error page without waiting for a subframe response.
+ await TestUtils.waitForCondition(() =>
+ frame.contentDocument.documentURI.startsWith("about:neterror?")
+ );
+ }
+
+ info("Remove frame");
+ frame.remove();
+
+ await page.close();
+}
+
+add_task(async function testRemoveFrameImmediately() {
+ await runTest(false);
+});
+
+add_task(async function testRemoveFrameAfterErrorPage() {
+ await runTest(true);
+});
+
+add_task(async function () {
+ // Allow the document requests for the frames to complete.
+ topFrameRequest.finish();
+ subFrameRequest.finish();
+});
diff --git a/docshell/test/unit/xpcshell.ini b/docshell/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..8bd7ef4f28
--- /dev/null
+++ b/docshell/test/unit/xpcshell.ini
@@ -0,0 +1,35 @@
+[DEFAULT]
+head = head_docshell.js
+support-files =
+ data/engine.xml
+ data/enginePost.xml
+ data/enginePrivate.xml
+
+[test_allowJavascript.js]
+skip-if = os == 'android'
+support-files =
+ AllowJavascriptChild.sys.mjs
+ AllowJavascriptParent.sys.mjs
+[test_bug442584.js]
+[test_browsing_context_structured_clone.js]
+[test_URIFixup.js]
+[test_URIFixup_check_host.js]
+[test_URIFixup_external_protocol_fallback.js]
+skip-if = os == 'android'
+[test_URIFixup_forced.js]
+# Disabled for 1563343 -- URI fixup should be done at the app level in GV.
+skip-if = os == 'android'
+[test_URIFixup_search.js]
+skip-if = os == 'android'
+[test_URIFixup_info.js]
+skip-if =
+ os == 'android'
+ win10_2004 && debug # Bug 1727925
+[test_pb_notification.js]
+# Bug 751575: unrelated JS changes cause timeouts on random platforms
+skip-if = true
+[test_privacy_transition.js]
+[test_subframe_stop_after_parent_error.js]
+skip-if =
+ os == 'android'
+ appname == 'thunderbird' # Needs to run without E10s, can't do that.