diff options
Diffstat (limited to '')
21 files changed, 2632 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. diff --git a/docshell/test/unit_ipc/test_pb_notification_ipc.js b/docshell/test/unit_ipc/test_pb_notification_ipc.js new file mode 100644 index 0000000000..6408e7753e --- /dev/null +++ b/docshell/test/unit_ipc/test_pb_notification_ipc.js @@ -0,0 +1,15 @@ +function run_test() { + var notifications = 0; + var obs = { + observe(aSubject, aTopic, aData) { + Assert.equal(aTopic, "last-pb-context-exited"); + notifications++; + }, + }; + Services.obs.addObserver(obs, "last-pb-context-exited"); + + run_test_in_child("../unit/test_pb_notification.js", function () { + Assert.equal(notifications, 1); + do_test_finished(); + }); +} diff --git a/docshell/test/unit_ipc/xpcshell.ini b/docshell/test/unit_ipc/xpcshell.ini new file mode 100644 index 0000000000..30e98a270f --- /dev/null +++ b/docshell/test/unit_ipc/xpcshell.ini @@ -0,0 +1,7 @@ +[DEFAULT] +head = +skip-if = toolkit == 'android' + +[test_pb_notification_ipc.js] +# Bug 751575: Perma-fails with: command timed out: 1200 seconds without output +skip-if = true |