diff options
Diffstat (limited to 'toolkit/modules/tests/browser')
42 files changed, 3840 insertions, 0 deletions
diff --git a/toolkit/modules/tests/browser/.eslintrc.js b/toolkit/modules/tests/browser/.eslintrc.js new file mode 100644 index 000000000..c764b133d --- /dev/null +++ b/toolkit/modules/tests/browser/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../testing/mochitest/browser.eslintrc.js" + ] +}; diff --git a/toolkit/modules/tests/browser/WebRequest_dynamic.sjs b/toolkit/modules/tests/browser/WebRequest_dynamic.sjs new file mode 100644 index 000000000..7b34a377d --- /dev/null +++ b/toolkit/modules/tests/browser/WebRequest_dynamic.sjs @@ -0,0 +1,13 @@ +function handleRequest(aRequest, aResponse) { + aResponse.setStatusLine(aRequest.httpVersion, 200); + if (aRequest.hasHeader('Cookie')) { + let value = aRequest.getHeader("Cookie"); + if (value == "blinky=1") { + aResponse.setHeader("Set-Cookie", "dinky=1"); + } + aResponse.write("cookie-present"); + } else { + aResponse.setHeader("Set-Cookie", "foopy=1"); + aResponse.write("cookie-not-present"); + } +} diff --git a/toolkit/modules/tests/browser/WebRequest_redirection.sjs b/toolkit/modules/tests/browser/WebRequest_redirection.sjs new file mode 100644 index 000000000..370ecd213 --- /dev/null +++ b/toolkit/modules/tests/browser/WebRequest_redirection.sjs @@ -0,0 +1,4 @@ +function handleRequest(aRequest, aResponse) { + aResponse.setStatusLine(aRequest.httpVersion, 302); + aResponse.setHeader("Location", "./dummy_page.html"); +} diff --git a/toolkit/modules/tests/browser/browser.ini b/toolkit/modules/tests/browser/browser.ini new file mode 100644 index 000000000..e82feaa42 --- /dev/null +++ b/toolkit/modules/tests/browser/browser.ini @@ -0,0 +1,41 @@ +[DEFAULT] +support-files = + dummy_page.html + metadata_*.html + testremotepagemanager.html + file_WebNavigation_page1.html + file_WebNavigation_page2.html + file_WebNavigation_page3.html + file_WebRequest_page1.html + file_WebRequest_page2.html + file_image_good.png + file_image_bad.png + file_image_redirect.png + file_style_good.css + file_style_bad.css + file_style_redirect.css + file_script_good.js + file_script_bad.js + file_script_redirect.js + file_script_xhr.js + WebRequest_dynamic.sjs + WebRequest_redirection.sjs + +[browser_AsyncPrefs.js] +[browser_Battery.js] +[browser_Deprecated.js] +[browser_Finder.js] +[browser_Finder_hidden_textarea.js] +[browser_FinderHighlighter.js] +skip-if = debug || os = "linux" +support-files = file_FinderSample.html +[browser_Geometry.js] +[browser_InlineSpellChecker.js] +[browser_WebNavigation.js] +[browser_WebRequest.js] +[browser_WebRequest_cookies.js] +[browser_WebRequest_filtering.js] +[browser_PageMetadata.js] +[browser_PromiseMessage.js] +[browser_RemotePageManager.js] +[browser_Troubleshoot.js] diff --git a/toolkit/modules/tests/browser/browser_AsyncPrefs.js b/toolkit/modules/tests/browser/browser_AsyncPrefs.js new file mode 100644 index 000000000..1d20a3789 --- /dev/null +++ b/toolkit/modules/tests/browser/browser_AsyncPrefs.js @@ -0,0 +1,97 @@ +"use strict"; + +const kWhiteListedBool = "testing.allowed-prefs.some-bool-pref"; +const kWhiteListedChar = "testing.allowed-prefs.some-char-pref"; +const kWhiteListedInt = "testing.allowed-prefs.some-int-pref"; + +function resetPrefs() { + for (let pref of [kWhiteListedBool, kWhiteListedChar, kWhiteListedBool]) { + Services.prefs.clearUserPref(pref); + } +} + +registerCleanupFunction(resetPrefs); + +Services.prefs.getDefaultBranch("testing.allowed-prefs.").setBoolPref("some-bool-pref", false); +Services.prefs.getDefaultBranch("testing.allowed-prefs.").setCharPref("some-char-pref", ""); +Services.prefs.getDefaultBranch("testing.allowed-prefs.").setIntPref("some-int-pref", 0); + +function* runTest() { + let {AsyncPrefs} = Cu.import("resource://gre/modules/AsyncPrefs.jsm", {}); + const kInChildProcess = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT; + + // Need to define these again because when run in a content task we have no scope access. + const kNotWhiteListed = "some.pref.thats.not.whitelisted"; + const kWhiteListedBool = "testing.allowed-prefs.some-bool-pref"; + const kWhiteListedChar = "testing.allowed-prefs.some-char-pref"; + const kWhiteListedInt = "testing.allowed-prefs.some-int-pref"; + + const procDesc = kInChildProcess ? "child process" : "parent process"; + + const valueResultMap = [ + [true, "Bool"], + [false, "Bool"], + [10, "Int"], + [-1, "Int"], + ["", "Char"], + ["stuff", "Char"], + [[], false], + [{}, false], + [BrowserUtils.makeURI("http://mozilla.org/"), false], + ]; + + const prefMap = [ + ["Bool", kWhiteListedBool], + ["Char", kWhiteListedChar], + ["Int", kWhiteListedInt], + ]; + + function doesFail(pref, value) { + let msg = `Should not succeed setting ${pref} to ${value} in ${procDesc}`; + return AsyncPrefs.set(pref, value).then(() => ok(false, msg), error => ok(true, msg + "; " + error)); + } + + function doesWork(pref, value) { + let msg = `Should be able to set ${pref} to ${value} in ${procDesc}`; + return AsyncPrefs.set(pref, value).then(() => ok(true, msg), error => ok(false, msg + "; " + error)); + } + + function doReset(pref) { + let msg = `Should be able to reset ${pref} in ${procDesc}`; + return AsyncPrefs.reset(pref).then(() => ok(true, msg), () => ok(false, msg)); + } + + for (let [val, ] of valueResultMap) { + yield doesFail(kNotWhiteListed, val); + is(Services.prefs.prefHasUserValue(kNotWhiteListed), false, "Pref shouldn't get changed"); + } + + let resetMsg = `Should not succeed resetting ${kNotWhiteListed} in ${procDesc}`; + AsyncPrefs.reset(kNotWhiteListed).then(() => ok(false, resetMsg), error => ok(true, resetMsg + "; " + error)); + + for (let [type, pref] of prefMap) { + for (let [val, result] of valueResultMap) { + if (result == type) { + yield doesWork(pref, val); + is(Services.prefs["get" + type + "Pref"](pref), val, "Pref should have been updated"); + yield doReset(pref); + } else { + yield doesFail(pref, val); + is(Services.prefs.prefHasUserValue(pref), false, `Pref ${pref} shouldn't get changed`); + } + } + } +} + +add_task(function* runInParent() { + yield runTest(); + resetPrefs(); +}); + +if (gMultiProcessBrowser) { + add_task(function* runInChild() { + ok(gBrowser.selectedBrowser.isRemoteBrowser, "Should actually run this in child process"); + yield ContentTask.spawn(gBrowser.selectedBrowser, null, runTest); + resetPrefs(); + }); +} diff --git a/toolkit/modules/tests/browser/browser_Battery.js b/toolkit/modules/tests/browser/browser_Battery.js new file mode 100644 index 000000000..2d3ba5da1 --- /dev/null +++ b/toolkit/modules/tests/browser/browser_Battery.js @@ -0,0 +1,51 @@ +/* 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 imported = Components.utils.import("resource://gre/modules/Battery.jsm", this); +Cu.import("resource://gre/modules/Services.jsm", this); + +function test() { + waitForExplicitFinish(); + + is(imported.Debugging.fake, false, "Battery spoofing is initially false") + + GetBattery().then(function (battery) { + for (let k of ["charging", "chargingTime", "dischargingTime", "level"]) { + let backup = battery[k]; + try { + battery[k] = "__magic__"; + } catch (e) { + // We are testing that we cannot set battery to new values + // when "use strict" is enabled, this throws a TypeError + if (e.name != "TypeError") + throw e; + } + is(battery[k], backup, "Setting battery " + k + " preference without spoofing enabled should fail"); + } + + imported.Debugging.fake = true; + + // reload again to get the fake one + GetBattery().then(function (battery) { + battery.charging = true; + battery.chargingTime = 100; + battery.level = 0.5; + ok(battery.charging, "Test for charging setter"); + is(battery.chargingTime, 100, "Test for chargingTime setter"); + is(battery.level, 0.5, "Test for level setter"); + + battery.charging = false; + battery.dischargingTime = 50; + battery.level = 0.7; + ok(!battery.charging, "Test for charging setter"); + is(battery.dischargingTime, 50, "Test for dischargingTime setter"); + is(battery.level, 0.7, "Test for level setter"); + + // Resetting the value to make the test run successful + // for multiple runs in same browser session. + imported.Debugging.fake = false; + finish(); + }); + }); +} diff --git a/toolkit/modules/tests/browser/browser_Deprecated.js b/toolkit/modules/tests/browser/browser_Deprecated.js new file mode 100644 index 000000000..3217bdd22 --- /dev/null +++ b/toolkit/modules/tests/browser/browser_Deprecated.js @@ -0,0 +1,157 @@ +/* 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 Ci = Components.interfaces; +var Cu = Components.utils; +const PREF_DEPRECATION_WARNINGS = "devtools.errorconsole.deprecation_warnings"; + +Cu.import("resource://gre/modules/Services.jsm", this); +Cu.import("resource://gre/modules/Deprecated.jsm", this); + +// Using this named functions to test deprecation and the properly logged +// callstacks. +function basicDeprecatedFunction () { + Deprecated.warning("this method is deprecated.", "http://example.com"); + return true; +} + +function deprecationFunctionBogusCallstack () { + Deprecated.warning("this method is deprecated.", "http://example.com", { + caller: {} + }); + return true; +} + +function deprecationFunctionCustomCallstack () { + // Get the nsIStackFrame that will contain the name of this function. + function getStack () { + return Components.stack; + } + Deprecated.warning("this method is deprecated.", "http://example.com", + getStack()); + return true; +} + +var tests = [ +// Test deprecation warning without passing the callstack. +{ + deprecatedFunction: basicDeprecatedFunction, + expectedObservation: function (aMessage) { + testAMessage(aMessage); + ok(aMessage.errorMessage.indexOf("basicDeprecatedFunction") > 0, + "Callstack is correctly logged."); + } +}, +// Test a reported error when URL to documentation is not passed. +{ + deprecatedFunction: function () { + Deprecated.warning("this method is deprecated."); + return true; + }, + expectedObservation: function (aMessage) { + ok(aMessage.errorMessage.indexOf("must provide a URL") > 0, + "Deprecation warning logged an empty URL argument."); + } +}, +// Test deprecation with a bogus callstack passed as an argument (it will be +// replaced with the current call stack). +{ + deprecatedFunction: deprecationFunctionBogusCallstack, + expectedObservation: function (aMessage) { + testAMessage(aMessage); + ok(aMessage.errorMessage.indexOf("deprecationFunctionBogusCallstack") > 0, + "Callstack is correctly logged."); + } +}, +// When pref is unset Deprecated.warning should not log anything. +{ + deprecatedFunction: basicDeprecatedFunction, + expectedObservation: null, + // Set pref to false. + logWarnings: false +}, +// Test deprecation with a valid custom callstack passed as an argument. +{ + deprecatedFunction: deprecationFunctionCustomCallstack, + expectedObservation: function (aMessage) { + testAMessage(aMessage); + ok(aMessage.errorMessage.indexOf("deprecationFunctionCustomCallstack") > 0, + "Callstack is correctly logged."); + }, + // Set pref to true. + logWarnings: true +}]; + +// Which test are we running now? +var idx = -1; + +function test() { + waitForExplicitFinish(); + + // Check if Deprecated is loaded. + ok(Deprecated, "Deprecated object exists"); + + nextTest(); +} + +// Test Consle Message attributes. +function testAMessage (aMessage) { + ok(aMessage.errorMessage.indexOf("DEPRECATION WARNING: " + + "this method is deprecated.") === 0, + "Deprecation is correctly logged."); + ok(aMessage.errorMessage.indexOf("http://example.com") > 0, + "URL is correctly logged."); +} + +function nextTest() { + idx++; + + if (idx == tests.length) { + finish(); + return; + } + + info("Running test #" + idx); + let test = tests[idx]; + + // Deprecation warnings will be logged only when the preference is set. + if (typeof test.logWarnings !== "undefined") { + Services.prefs.setBoolPref(PREF_DEPRECATION_WARNINGS, test.logWarnings); + } + + // Create a console listener. + let consoleListener = { + observe: function (aMessage) { + // Ignore unexpected messages. + if (!(aMessage instanceof Ci.nsIScriptError)) { + return; + } + if (aMessage.errorMessage.indexOf("DEPRECATION WARNING: ") < 0 && + aMessage.errorMessage.indexOf("must provide a URL") < 0) { + return; + } + ok(aMessage instanceof Ci.nsIScriptError, + "Deprecation log message is an instance of type nsIScriptError."); + + + if (test.expectedObservation === null) { + ok(false, "Deprecated warning not expected"); + } + else { + test.expectedObservation(aMessage); + } + + Services.console.unregisterListener(consoleListener); + executeSoon(nextTest); + } + }; + Services.console.registerListener(consoleListener); + test.deprecatedFunction(); + if (test.expectedObservation === null) { + executeSoon(function() { + Services.console.unregisterListener(consoleListener); + executeSoon(nextTest); + }); + } +} diff --git a/toolkit/modules/tests/browser/browser_Finder.js b/toolkit/modules/tests/browser/browser_Finder.js new file mode 100644 index 000000000..4dfd921d0 --- /dev/null +++ b/toolkit/modules/tests/browser/browser_Finder.js @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var Ci = Components.interfaces; + +add_task(function* () { + const url = "data:text/html;base64," + + btoa("<body><iframe srcdoc=\"content\"/></iframe>" + + "<a href=\"http://test.com\">test link</a>"); + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url); + + let finder = tab.linkedBrowser.finder; + let listener = { + onFindResult: function () { + ok(false, "onFindResult callback wasn't replaced"); + }, + onHighlightFinished: function () { + ok(false, "onHighlightFinished callback wasn't replaced"); + } + }; + finder.addResultListener(listener); + + function waitForFind(which = "onFindResult") { + return new Promise(resolve => { + listener[which] = resolve; + }) + } + + let promiseFind = waitForFind("onHighlightFinished"); + finder.highlight(true, "content"); + let findResult = yield promiseFind; + Assert.ok(findResult.found, "should find string"); + + promiseFind = waitForFind("onHighlightFinished"); + finder.highlight(true, "Bla"); + findResult = yield promiseFind; + Assert.ok(!findResult.found, "should not find string"); + + // Search only for links and draw outlines. + promiseFind = waitForFind(); + finder.fastFind("test link", true, true); + findResult = yield promiseFind; + is(findResult.result, Ci.nsITypeAheadFind.FIND_FOUND, "should find link"); + + yield ContentTask.spawn(tab.linkedBrowser, {}, function* (arg) { + Assert.ok(!!content.document.getElementsByTagName("a")[0].style.outline, "outline set"); + }); + + // Just a simple search for "test link". + promiseFind = waitForFind(); + finder.fastFind("test link", false, false); + findResult = yield promiseFind; + is(findResult.result, Ci.nsITypeAheadFind.FIND_FOUND, "should find link again"); + + yield ContentTask.spawn(tab.linkedBrowser, {}, function* (arg) { + Assert.ok(!content.document.getElementsByTagName("a")[0].style.outline, "outline not set"); + }); + + finder.removeResultListener(listener); + gBrowser.removeTab(tab); +}); diff --git a/toolkit/modules/tests/browser/browser_FinderHighlighter.js b/toolkit/modules/tests/browser/browser_FinderHighlighter.js new file mode 100644 index 000000000..cd7eefa11 --- /dev/null +++ b/toolkit/modules/tests/browser/browser_FinderHighlighter.js @@ -0,0 +1,460 @@ +"use strict"; + +Cu.import("resource://testing-common/BrowserTestUtils.jsm", this); +Cu.import("resource://testing-common/ContentTask.jsm", this); +Cu.import("resource://gre/modules/Promise.jsm", this); +Cu.import("resource://gre/modules/Services.jsm", this); +Cu.import("resource://gre/modules/Task.jsm", this); +Cu.import("resource://gre/modules/Timer.jsm", this); +Cu.import("resource://gre/modules/AppConstants.jsm"); + +const kHighlightAllPref = "findbar.highlightAll"; +const kPrefModalHighlight = "findbar.modalHighlight"; +const kFixtureBaseURL = "https://example.com/browser/toolkit/modules/tests/browser/"; +const kIteratorTimeout = Services.prefs.getIntPref("findbar.iteratorTimeout"); + +function promiseOpenFindbar(findbar) { + findbar.onFindCommand() + return gFindBar._startFindDeferred && gFindBar._startFindDeferred.promise; +} + +function promiseFindResult(findbar, str = null) { + let highlightFinished = false; + let findFinished = false; + return new Promise(resolve => { + let listener = { + onFindResult({ searchString }) { + if (str !== null && str != searchString) { + return; + } + findFinished = true; + if (highlightFinished) { + findbar.browser.finder.removeResultListener(listener); + resolve(); + } + }, + onHighlightFinished() { + highlightFinished = true; + if (findFinished) { + findbar.browser.finder.removeResultListener(listener); + resolve(); + } + }, + onMatchesCountResult: () => {} + }; + findbar.browser.finder.addResultListener(listener); + }); +} + +function promiseEnterStringIntoFindField(findbar, str) { + let promise = promiseFindResult(findbar, str); + for (let i = 0; i < str.length; i++) { + let event = document.createEvent("KeyboardEvent"); + event.initKeyEvent("keypress", true, true, null, false, false, + false, false, 0, str.charCodeAt(i)); + findbar._findField.inputField.dispatchEvent(event); + } + return promise; +} + +function promiseTestHighlighterOutput(browser, word, expectedResult, extraTest = () => {}) { + return ContentTask.spawn(browser, { word, expectedResult, extraTest: extraTest.toSource() }, + function* ({ word, expectedResult, extraTest }) { + Cu.import("resource://gre/modules/Timer.jsm", this); + + return new Promise((resolve, reject) => { + let stubbed = {}; + let callCounts = { + insertCalls: [], + removeCalls: [] + }; + let lastMaskNode, lastOutlineNode; + let rects = []; + + // Amount of milliseconds to wait after the last time one of our stubs + // was called. + const kTimeoutMs = 1000; + // The initial timeout may wait for a while for results to come in. + let timeout = setTimeout(() => finish(false, "Timeout"), kTimeoutMs * 5); + + function finish(ok = true, message = "finished with error") { + // Restore the functions we stubbed out. + try { + content.document.insertAnonymousContent = stubbed.insert; + content.document.removeAnonymousContent = stubbed.remove; + } catch (ex) {} + stubbed = {}; + clearTimeout(timeout); + + if (expectedResult.rectCount !== 0) + Assert.ok(ok, message); + + Assert.greaterOrEqual(callCounts.insertCalls.length, expectedResult.insertCalls[0], + `Min. insert calls should match for '${word}'.`); + Assert.lessOrEqual(callCounts.insertCalls.length, expectedResult.insertCalls[1], + `Max. insert calls should match for '${word}'.`); + Assert.greaterOrEqual(callCounts.removeCalls.length, expectedResult.removeCalls[0], + `Min. remove calls should match for '${word}'.`); + Assert.lessOrEqual(callCounts.removeCalls.length, expectedResult.removeCalls[1], + `Max. remove calls should match for '${word}'.`); + + // We reached the amount of calls we expected, so now we can check + // the amount of rects. + if (!lastMaskNode && expectedResult.rectCount !== 0) { + Assert.ok(false, `No mask node found, but expected ${expectedResult.rectCount} rects.`); + } + + Assert.equal(rects.length, expectedResult.rectCount, + `Amount of inserted rects should match for '${word}'.`); + + // Allow more specific assertions to be tested in `extraTest`. + extraTest = eval(extraTest); + extraTest(lastMaskNode, lastOutlineNode, rects); + + resolve(); + } + + function stubAnonymousContentNode(domNode, anonNode) { + let originals = [anonNode.setTextContentForElement, + anonNode.setAttributeForElement, anonNode.removeAttributeForElement, + anonNode.setCutoutRectsForElement]; + anonNode.setTextContentForElement = (id, text) => { + try { + (domNode.querySelector("#" + id) || domNode).textContent = text; + } catch (ex) {} + return originals[0].call(anonNode, id, text); + }; + anonNode.setAttributeForElement = (id, attrName, attrValue) => { + try { + (domNode.querySelector("#" + id) || domNode).setAttribute(attrName, attrValue); + } catch (ex) {} + return originals[1].call(anonNode, id, attrName, attrValue); + }; + anonNode.removeAttributeForElement = (id, attrName) => { + try { + let node = domNode.querySelector("#" + id) || domNode; + if (node.hasAttribute(attrName)) + node.removeAttribute(attrName); + } catch (ex) {} + return originals[2].call(anonNode, id, attrName); + }; + anonNode.setCutoutRectsForElement = (id, cutoutRects) => { + rects = cutoutRects; + return originals[3].call(anonNode, id, cutoutRects); + }; + } + + // Create a function that will stub the original version and collects + // the arguments so we can check the results later. + function stub(which) { + stubbed[which] = content.document[which + "AnonymousContent"]; + let prop = which + "Calls"; + return function(node) { + callCounts[prop].push(node); + if (which == "insert") { + if (node.outerHTML.indexOf("outlineMask") > -1) + lastMaskNode = node; + else + lastOutlineNode = node; + } + clearTimeout(timeout); + timeout = setTimeout(() => { + finish(); + }, kTimeoutMs); + let res = stubbed[which].call(content.document, node); + if (which == "insert") + stubAnonymousContentNode(node, res); + return res; + }; + } + content.document.insertAnonymousContent = stub("insert"); + content.document.removeAnonymousContent = stub("remove"); + }); + }); +} + +add_task(function* setup() { + yield SpecialPowers.pushPrefEnv({ set: [ + [kHighlightAllPref, true], + [kPrefModalHighlight, true] + ]}); +}); + +// Test the results of modal highlighting, which is on by default. +add_task(function* testModalResults() { + let tests = new Map([ + ["Roland", { + rectCount: 2, + insertCalls: [2, 4], + removeCalls: [0, 1] + }], + ["their law might propagate their kind", { + rectCount: 2, + insertCalls: [5, 6], + removeCalls: [4, 5], + extraTest: function(maskNode, outlineNode, rects) { + Assert.equal(outlineNode.getElementsByTagName("div").length, 2, + "There should be multiple rects drawn"); + } + }], + ["ro", { + rectCount: 41, + insertCalls: [1, 4], + removeCalls: [1, 3] + }], + ["new", { + rectCount: 2, + insertCalls: [1, 4], + removeCalls: [0, 2] + }], + ["o", { + rectCount: 492, + insertCalls: [1, 4], + removeCalls: [0, 2] + }] + ]); + let url = kFixtureBaseURL + "file_FinderSample.html"; + yield BrowserTestUtils.withNewTab(url, function* (browser) { + let findbar = gBrowser.getFindBar(); + + for (let [word, expectedResult] of tests) { + yield promiseOpenFindbar(findbar); + Assert.ok(!findbar.hidden, "Findbar should be open now."); + + let timeout = kIteratorTimeout; + if (word.length == 1) + timeout *= 4; + else if (word.length == 2) + timeout *= 2; + yield new Promise(resolve => setTimeout(resolve, timeout)); + let promise = promiseTestHighlighterOutput(browser, word, expectedResult, + expectedResult.extraTest); + yield promiseEnterStringIntoFindField(findbar, word); + yield promise; + + findbar.close(true); + } + }); +}); + +// Test if runtime switching of highlight modes between modal and non-modal works +// as expected. +add_task(function* testModalSwitching() { + let url = kFixtureBaseURL + "file_FinderSample.html"; + yield BrowserTestUtils.withNewTab(url, function* (browser) { + let findbar = gBrowser.getFindBar(); + + yield promiseOpenFindbar(findbar); + Assert.ok(!findbar.hidden, "Findbar should be open now."); + + let word = "Roland"; + let expectedResult = { + rectCount: 2, + insertCalls: [2, 4], + removeCalls: [0, 1] + }; + let promise = promiseTestHighlighterOutput(browser, word, expectedResult); + yield promiseEnterStringIntoFindField(findbar, word); + yield promise; + + yield SpecialPowers.pushPrefEnv({ "set": [[ kPrefModalHighlight, false ]] }); + + expectedResult = { + rectCount: 0, + insertCalls: [0, 0], + removeCalls: [0, 0] + }; + promise = promiseTestHighlighterOutput(browser, word, expectedResult); + findbar.clear(); + yield promiseEnterStringIntoFindField(findbar, word); + yield promise; + + findbar.close(true); + }); + + yield SpecialPowers.pushPrefEnv({ "set": [[ kPrefModalHighlight, true ]] }); +}); + +// Test if highlighting a dark page is detected properly. +add_task(function* testDarkPageDetection() { + let url = kFixtureBaseURL + "file_FinderSample.html"; + yield BrowserTestUtils.withNewTab(url, function* (browser) { + let findbar = gBrowser.getFindBar(); + + yield promiseOpenFindbar(findbar); + + let word = "Roland"; + let expectedResult = { + rectCount: 2, + insertCalls: [1, 3], + removeCalls: [0, 1] + }; + let promise = promiseTestHighlighterOutput(browser, word, expectedResult, function(node) { + Assert.ok(node.style.background.startsWith("rgba(0, 0, 0"), + "White HTML page should have a black background color set for the mask"); + }); + yield promiseEnterStringIntoFindField(findbar, word); + yield promise; + + findbar.close(true); + }); + + yield BrowserTestUtils.withNewTab(url, function* (browser) { + let findbar = gBrowser.getFindBar(); + + yield promiseOpenFindbar(findbar); + + let word = "Roland"; + let expectedResult = { + rectCount: 2, + insertCalls: [2, 4], + removeCalls: [0, 1] + }; + + yield ContentTask.spawn(browser, null, function* () { + let dwu = content.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + let uri = "data:text/css;charset=utf-8," + encodeURIComponent(` + body { + background: maroon radial-gradient(circle, #a01010 0%, #800000 80%) center center / cover no-repeat; + color: white; + }`); + try { + dwu.loadSheetUsingURIString(uri, dwu.USER_SHEET); + } catch (e) {} + }); + + let promise = promiseTestHighlighterOutput(browser, word, expectedResult, node => { + Assert.ok(node.style.background.startsWith("rgba(255, 255, 255"), + "Dark HTML page should have a white background color set for the mask"); + }); + yield promiseEnterStringIntoFindField(findbar, word); + yield promise; + + findbar.close(true); + }); +}); + +add_task(function* testHighlightAllToggle() { + let url = kFixtureBaseURL + "file_FinderSample.html"; + yield BrowserTestUtils.withNewTab(url, function* (browser) { + let findbar = gBrowser.getFindBar(); + + yield promiseOpenFindbar(findbar); + + let word = "Roland"; + let expectedResult = { + rectCount: 2, + insertCalls: [2, 4], + removeCalls: [0, 1] + }; + let promise = promiseTestHighlighterOutput(browser, word, expectedResult); + yield promiseEnterStringIntoFindField(findbar, word); + yield promise; + + // We now know we have multiple rectangles highlighted, so it's a good time + // to flip the pref. + expectedResult = { + rectCount: 0, + insertCalls: [0, 1], + removeCalls: [0, 1] + }; + promise = promiseTestHighlighterOutput(browser, word, expectedResult); + yield SpecialPowers.pushPrefEnv({ "set": [[ kHighlightAllPref, false ]] }); + yield promise; + + // For posterity, let's switch back. + expectedResult = { + rectCount: 2, + insertCalls: [1, 3], + removeCalls: [0, 1] + }; + promise = promiseTestHighlighterOutput(browser, word, expectedResult); + yield SpecialPowers.pushPrefEnv({ "set": [[ kHighlightAllPref, true ]] }); + yield promise; + }); +}); + +add_task(function* testXMLDocument() { + let url = "data:text/xml;charset=utf-8," + encodeURIComponent(`<?xml version="1.0"?> +<result> + <Title>Example</Title> + <Error>Error</Error> +</result>`); + yield BrowserTestUtils.withNewTab(url, function* (browser) { + let findbar = gBrowser.getFindBar(); + + yield promiseOpenFindbar(findbar); + + let word = "Example"; + let expectedResult = { + rectCount: 0, + insertCalls: [1, 4], + removeCalls: [0, 1] + }; + let promise = promiseTestHighlighterOutput(browser, word, expectedResult); + yield promiseEnterStringIntoFindField(findbar, word); + yield promise; + + findbar.close(true); + }); +}); + +add_task(function* testHideOnLocationChange() { + let url = kFixtureBaseURL + "file_FinderSample.html"; + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url); + let browser = tab.linkedBrowser; + let findbar = gBrowser.getFindBar(); + + yield promiseOpenFindbar(findbar); + + let word = "Roland"; + let expectedResult = { + rectCount: 2, + insertCalls: [2, 4], + removeCalls: [0, 1] + }; + let promise = promiseTestHighlighterOutput(browser, word, expectedResult); + yield promiseEnterStringIntoFindField(findbar, word); + yield promise; + + // Now we try to navigate away! (Using the same page) + promise = promiseTestHighlighterOutput(browser, word, { + rectCount: 0, + insertCalls: [0, 0], + removeCalls: [1, 2] + }); + yield BrowserTestUtils.loadURI(browser, url); + yield promise; + + yield BrowserTestUtils.removeTab(tab); +}); + +add_task(function* testHideOnClear() { + let url = kFixtureBaseURL + "file_FinderSample.html"; + yield BrowserTestUtils.withNewTab(url, function* (browser) { + let findbar = gBrowser.getFindBar(); + yield promiseOpenFindbar(findbar); + + let word = "Roland"; + let expectedResult = { + rectCount: 2, + insertCalls: [2, 4], + removeCalls: [0, 2] + }; + let promise = promiseTestHighlighterOutput(browser, word, expectedResult); + yield promiseEnterStringIntoFindField(findbar, word); + yield promise; + + yield new Promise(resolve => setTimeout(resolve, kIteratorTimeout)); + promise = promiseTestHighlighterOutput(browser, "", { + rectCount: 0, + insertCalls: [0, 0], + removeCalls: [1, 2] + }); + findbar.clear(); + yield promise; + + findbar.close(true); + }); +}); diff --git a/toolkit/modules/tests/browser/browser_Finder_hidden_textarea.js b/toolkit/modules/tests/browser/browser_Finder_hidden_textarea.js new file mode 100644 index 000000000..99d838ada --- /dev/null +++ b/toolkit/modules/tests/browser/browser_Finder_hidden_textarea.js @@ -0,0 +1,52 @@ +/* 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/. */ +add_task(function* test_bug1174036() { + const URI = + "<body><textarea>e1</textarea><textarea>e2</textarea><textarea>e3</textarea></body>"; + yield BrowserTestUtils.withNewTab({ gBrowser, url: "data:text/html;charset=utf-8," + encodeURIComponent(URI) }, + function* (browser) { + // Hide the first textarea. + yield ContentTask.spawn(browser, null, function() { + content.document.getElementsByTagName("textarea")[0].style.display = "none"; + }); + + let finder = browser.finder; + let listener = { + onFindResult: function () { + ok(false, "callback wasn't replaced"); + } + }; + finder.addResultListener(listener); + + function waitForFind() { + return new Promise(resolve => { + listener.onFindResult = resolve; + }) + } + + // Find the first 'e' (which should be in the second textarea). + let promiseFind = waitForFind(); + finder.fastFind("e", false, false); + let findResult = yield promiseFind; + is(findResult.result, Ci.nsITypeAheadFind.FIND_FOUND, "find first string"); + + let firstRect = findResult.rect; + + // Find the second 'e' (in the third textarea). + promiseFind = waitForFind(); + finder.findAgain(false, false, false); + findResult = yield promiseFind; + is(findResult.result, Ci.nsITypeAheadFind.FIND_FOUND, "find second string"); + ok(!findResult.rect.equals(firstRect), "found new string"); + + // Ensure that we properly wrap to the second textarea. + promiseFind = waitForFind(); + finder.findAgain(false, false, false); + findResult = yield promiseFind; + is(findResult.result, Ci.nsITypeAheadFind.FIND_WRAPPED, "wrapped to first string"); + ok(findResult.rect.equals(firstRect), "wrapped to original string"); + + finder.removeResultListener(listener); + }); +}); diff --git a/toolkit/modules/tests/browser/browser_Geometry.js b/toolkit/modules/tests/browser/browser_Geometry.js new file mode 100644 index 000000000..aaca79a06 --- /dev/null +++ b/toolkit/modules/tests/browser/browser_Geometry.js @@ -0,0 +1,111 @@ +/* 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 tempScope = {}; +Components.utils.import("resource://gre/modules/Geometry.jsm", tempScope); +var Point = tempScope.Point; +var Rect = tempScope.Rect; + +function test() { + ok(Rect, "Rect class exists"); + for (var fname in tests) { + tests[fname](); + } +} + +var tests = { + testGetDimensions: function() { + let r = new Rect(5, 10, 100, 50); + ok(r.left == 5, "rect has correct left value"); + ok(r.top == 10, "rect has correct top value"); + ok(r.right == 105, "rect has correct right value"); + ok(r.bottom == 60, "rect has correct bottom value"); + ok(r.width == 100, "rect has correct width value"); + ok(r.height == 50, "rect has correct height value"); + ok(r.x == 5, "rect has correct x value"); + ok(r.y == 10, "rect has correct y value"); + }, + + testIsEmpty: function() { + let r = new Rect(0, 0, 0, 10); + ok(r.isEmpty(), "rect with nonpositive width is empty"); + r = new Rect(0, 0, 10, 0); + ok(r.isEmpty(), "rect with nonpositive height is empty"); + r = new Rect(0, 0, 10, 10); + ok(!r.isEmpty(), "rect with positive dimensions is not empty"); + }, + + testRestrictTo: function() { + let r1 = new Rect(10, 10, 100, 100); + let r2 = new Rect(50, 50, 100, 100); + r1.restrictTo(r2); + ok(r1.equals(new Rect(50, 50, 60, 60)), "intersection is non-empty"); + + r1 = new Rect(10, 10, 100, 100); + r2 = new Rect(120, 120, 100, 100); + r1.restrictTo(r2); + ok(r1.isEmpty(), "intersection is empty"); + + r1 = new Rect(10, 10, 100, 100); + r2 = new Rect(0, 0, 0, 0); + r1.restrictTo(r2); + ok(r1.isEmpty(), "intersection of rect and empty is empty"); + + r1 = new Rect(0, 0, 0, 0); + r2 = new Rect(0, 0, 0, 0); + r1.restrictTo(r2); + ok(r1.isEmpty(), "intersection of empty and empty is empty"); + }, + + testExpandToContain: function() { + let r1 = new Rect(10, 10, 100, 100); + let r2 = new Rect(50, 50, 100, 100); + r1.expandToContain(r2); + ok(r1.equals(new Rect(10, 10, 140, 140)), "correct expandToContain on intersecting rectangles"); + + r1 = new Rect(10, 10, 100, 100); + r2 = new Rect(120, 120, 100, 100); + r1.expandToContain(r2); + ok(r1.equals(new Rect(10, 10, 210, 210)), "correct expandToContain on non-intersecting rectangles"); + + r1 = new Rect(10, 10, 100, 100); + r2 = new Rect(0, 0, 0, 0); + r1.expandToContain(r2); + ok(r1.equals(new Rect(10, 10, 100, 100)), "expandToContain of rect and empty is rect"); + + r1 = new Rect(10, 10, 0, 0); + r2 = new Rect(0, 0, 0, 0); + r1.expandToContain(r2); + ok(r1.isEmpty(), "expandToContain of empty and empty is empty"); + }, + + testSubtract: function testSubtract() { + function equals(rects1, rects2) { + return rects1.length == rects2.length && rects1.every(function(r, i) { + return r.equals(rects2[i]); + }); + } + + let r1 = new Rect(0, 0, 100, 100); + let r2 = new Rect(500, 500, 100, 100); + ok(equals(r1.subtract(r2), [r1]), "subtract area outside of region yields same region"); + + r1 = new Rect(0, 0, 100, 100); + r2 = new Rect(-10, -10, 50, 120); + ok(equals(r1.subtract(r2), [new Rect(40, 0, 60, 100)]), "subtracting vertical bar from edge leaves one rect"); + + r1 = new Rect(0, 0, 100, 100); + r2 = new Rect(-10, -10, 120, 50); + ok(equals(r1.subtract(r2), [new Rect(0, 40, 100, 60)]), "subtracting horizontal bar from edge leaves one rect"); + + r1 = new Rect(0, 0, 100, 100); + r2 = new Rect(40, 40, 20, 20); + ok(equals(r1.subtract(r2), [ + new Rect(0, 0, 40, 100), + new Rect(40, 0, 20, 40), + new Rect(40, 60, 20, 40), + new Rect(60, 0, 40, 100)]), + "subtracting rect in middle leaves union of rects"); + }, +}; diff --git a/toolkit/modules/tests/browser/browser_InlineSpellChecker.js b/toolkit/modules/tests/browser/browser_InlineSpellChecker.js new file mode 100644 index 000000000..2bffc9722 --- /dev/null +++ b/toolkit/modules/tests/browser/browser_InlineSpellChecker.js @@ -0,0 +1,121 @@ +function test() { + let tempScope = {}; + Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm", tempScope); + let InlineSpellChecker = tempScope.InlineSpellChecker; + + ok(InlineSpellChecker, "InlineSpellChecker class exists"); + for (var fname in tests) { + tests[fname](); + } +} + +var tests = { + // Test various possible dictionary name to ensure they display as expected. + // XXX: This only works for the 'en-US' locale, as the testing involves localized output. + testDictionaryDisplayNames: function() { + let isc = new InlineSpellChecker(); + + // Check non-well-formed language tag. + is(isc.getDictionaryDisplayName("-invalid-"), "-invalid-", "'-invalid-' should display as '-invalid-'"); + + // XXX: It isn't clear how we'd ideally want to display variant subtags. + + // Check valid language subtag. + is(isc.getDictionaryDisplayName("en"), "English", "'en' should display as 'English'"); + is(isc.getDictionaryDisplayName("en-fonipa"), "English (fonipa)", "'en-fonipa' should display as 'English (fonipa)'"); + is(isc.getDictionaryDisplayName("en-qxqaaaaz"), "English (qxqaaaaz)", "'en-qxqaaaaz' should display as 'English (qxqaaaaz)'"); + + // Check valid language subtag and valid region subtag. + is(isc.getDictionaryDisplayName("en-US"), "English (United States)", "'en-US' should display as 'English (United States)'"); + is(isc.getDictionaryDisplayName("en-US-fonipa"), "English (United States) (fonipa)", "'en-US-fonipa' should display as 'English (United States) (fonipa)'"); + is(isc.getDictionaryDisplayName("en-US-qxqaaaaz"), "English (United States) (qxqaaaaz)", "'en-US-qxqaaaaz' should display as 'English (United States) (qxqaaaaz)'"); + + // Check valid language subtag and invalid but well-formed region subtag. + is(isc.getDictionaryDisplayName("en-WO"), "English (WO)", "'en-WO' should display as 'English (WO)'"); + is(isc.getDictionaryDisplayName("en-WO-fonipa"), "English (WO) (fonipa)", "'en-WO-fonipa' should display as 'English (WO) (fonipa)'"); + is(isc.getDictionaryDisplayName("en-WO-qxqaaaaz"), "English (WO) (qxqaaaaz)", "'en-WO-qxqaaaaz' should display as 'English (WO) (qxqaaaaz)'"); + + // Check valid language subtag and valid script subtag. + todo_is(isc.getDictionaryDisplayName("en-Cyrl"), "English / Cyrillic", "'en-Cyrl' should display as 'English / Cyrillic'"); + todo_is(isc.getDictionaryDisplayName("en-Cyrl-fonipa"), "English / Cyrillic (fonipa)", "'en-Cyrl-fonipa' should display as 'English / Cyrillic (fonipa)'"); + todo_is(isc.getDictionaryDisplayName("en-Cyrl-qxqaaaaz"), "English / Cyrillic (qxqaaaaz)", "'en-Cyrl-qxqaaaaz' should display as 'English / Cyrillic (qxqaaaaz)'"); + todo_is(isc.getDictionaryDisplayName("en-Cyrl-US"), "English (United States) / Cyrillic", "'en-Cyrl-US' should display as 'English (United States) / Cyrillic'"); + todo_is(isc.getDictionaryDisplayName("en-Cyrl-US-fonipa"), "English (United States) / Cyrillic (fonipa)", "'en-Cyrl-US-fonipa' should display as 'English (United States) / Cyrillic (fonipa)'"); + todo_is(isc.getDictionaryDisplayName("en-Cyrl-US-qxqaaaaz"), "English (United States) / Cyrillic (qxqaaaaz)", "'en-Cyrl-US-qxqaaaaz' should display as 'English (United States) / Cyrillic (qxqaaaaz)'"); + todo_is(isc.getDictionaryDisplayName("en-Cyrl-WO"), "English (WO) / Cyrillic", "'en-Cyrl-WO' should display as 'English (WO) / Cyrillic'"); + todo_is(isc.getDictionaryDisplayName("en-Cyrl-WO-fonipa"), "English (WO) / Cyrillic (fonipa)", "'en-Cyrl-WO-fonipa' should display as 'English (WO) / Cyrillic (fonipa)'"); + todo_is(isc.getDictionaryDisplayName("en-Cyrl-WO-qxqaaaaz"), "English (WO) / Cyrillic (qxqaaaaz)", "'en-Cyrl-WO-qxqaaaaz' should display as 'English (WO) / Cyrillic (qxqaaaaz)'"); + + // Check valid language subtag and invalid but well-formed script subtag. + is(isc.getDictionaryDisplayName("en-Qaaz"), "English / Qaaz", "'en-Qaaz' should display as 'English / Qaaz'"); + is(isc.getDictionaryDisplayName("en-Qaaz-fonipa"), "English / Qaaz (fonipa)", "'en-Qaaz-fonipa' should display as 'English / Qaaz (fonipa)'"); + is(isc.getDictionaryDisplayName("en-Qaaz-qxqaaaaz"), "English / Qaaz (qxqaaaaz)", "'en-Qaaz-qxqaaaaz' should display as 'English / Qaaz (qxqaaaaz)'"); + is(isc.getDictionaryDisplayName("en-Qaaz-US"), "English (United States) / Qaaz", "'en-Qaaz-US' should display as 'English (United States) / Qaaz'"); + is(isc.getDictionaryDisplayName("en-Qaaz-US-fonipa"), "English (United States) / Qaaz (fonipa)", "'en-Qaaz-US-fonipa' should display as 'English (United States) / Qaaz (fonipa)'"); + is(isc.getDictionaryDisplayName("en-Qaaz-US-qxqaaaaz"), "English (United States) / Qaaz (qxqaaaaz)", "'en-Qaaz-US-qxqaaaaz' should display as 'English (United States) / Qaaz (qxqaaaaz)'"); + is(isc.getDictionaryDisplayName("en-Qaaz-WO"), "English (WO) / Qaaz", "'en-Qaaz-WO' should display as 'English (WO) / Qaaz'"); + is(isc.getDictionaryDisplayName("en-Qaaz-WO-fonipa"), "English (WO) / Qaaz (fonipa)", "'en-Qaaz-WO-fonipa' should display as 'English (WO) / Qaaz (fonipa)'"); + is(isc.getDictionaryDisplayName("en-Qaaz-WO-qxqaaaaz"), "English (WO) / Qaaz (qxqaaaaz)", "'en-Qaaz-WO-qxqaaaaz' should display as 'English (WO) / Qaaz (qxqaaaaz)'"); + + // Check invalid but well-formed language subtag. + is(isc.getDictionaryDisplayName("qaz"), "qaz", "'qaz' should display as 'qaz'"); + is(isc.getDictionaryDisplayName("qaz-fonipa"), "qaz (fonipa)", "'qaz-fonipa' should display as 'qaz (fonipa)'"); + is(isc.getDictionaryDisplayName("qaz-qxqaaaaz"), "qaz (qxqaaaaz)", "'qaz-qxqaaaaz' should display as 'qaz (qxqaaaaz)'"); + + // Check invalid but well-formed language subtag and valid region subtag. + is(isc.getDictionaryDisplayName("qaz-US"), "qaz (United States)", "'qaz-US' should display as 'qaz (United States)'"); + is(isc.getDictionaryDisplayName("qaz-US-fonipa"), "qaz (United States) (fonipa)", "'qaz-US-fonipa' should display as 'qaz (United States) (fonipa)'"); + is(isc.getDictionaryDisplayName("qaz-US-qxqaaaaz"), "qaz (United States) (qxqaaaaz)", "'qaz-US-qxqaaaaz' should display as 'qaz (United States) (qxqaaaaz)'"); + + // Check invalid but well-formed language subtag and invalid but well-formed region subtag. + is(isc.getDictionaryDisplayName("qaz-WO"), "qaz (WO)", "'qaz-WO' should display as 'qaz (WO)'"); + is(isc.getDictionaryDisplayName("qaz-WO-fonipa"), "qaz (WO) (fonipa)", "'qaz-WO-fonipa' should display as 'qaz (WO) (fonipa)'"); + is(isc.getDictionaryDisplayName("qaz-WO-qxqaaaaz"), "qaz (WO) (qxqaaaaz)", "'qaz-WO-qxqaaaaz' should display as 'qaz (WO) (qxqaaaaz)'"); + + // Check invalid but well-formed language subtag and valid script subtag. + todo_is(isc.getDictionaryDisplayName("qaz-Cyrl"), "qaz / Cyrillic", "'qaz-Cyrl' should display as 'qaz / Cyrillic'"); + todo_is(isc.getDictionaryDisplayName("qaz-Cyrl-fonipa"), "qaz / Cyrillic (fonipa)", "'qaz-Cyrl-fonipa' should display as 'qaz / Cyrillic (fonipa)'"); + todo_is(isc.getDictionaryDisplayName("qaz-Cyrl-qxqaaaaz"), "qaz / Cyrillic (qxqaaaaz)", "'qaz-Cyrl-qxqaaaaz' should display as 'qaz / Cyrillic (qxqaaaaz)'"); + todo_is(isc.getDictionaryDisplayName("qaz-Cyrl-US"), "qaz (United States) / Cyrillic", "'qaz-Cyrl-US' should display as 'qaz (United States) / Cyrillic'"); + todo_is(isc.getDictionaryDisplayName("qaz-Cyrl-US-fonipa"), "qaz (United States) / Cyrillic (fonipa)", "'qaz-Cyrl-US-fonipa' should display as 'qaz (United States) / Cyrillic (fonipa)'"); + todo_is(isc.getDictionaryDisplayName("qaz-Cyrl-US-qxqaaaaz"), "qaz (United States) / Cyrillic (qxqaaaaz)", "'qaz-Cyrl-US-qxqaaaaz' should display as 'qaz (United States) / Cyrillic (qxqaaaaz)'"); + todo_is(isc.getDictionaryDisplayName("qaz-Cyrl-WO"), "qaz (WO) / Cyrillic", "'qaz-Cyrl-WO' should display as 'qaz (WO) / Cyrillic'"); + todo_is(isc.getDictionaryDisplayName("qaz-Cyrl-WO-fonipa"), "qaz (WO) / Cyrillic (fonipa)", "'qaz-Cyrl-WO-fonipa' should display as 'qaz (WO) / Cyrillic (fonipa)'"); + todo_is(isc.getDictionaryDisplayName("qaz-Cyrl-WO-qxqaaaaz"), "qaz (WO) / Cyrillic (qxqaaaaz)", "'qaz-Cyrl-WO-qxqaaaaz' should display as 'qaz (WO) / Cyrillic (qxqaaaaz)'"); + + // Check invalid but well-formed language subtag and invalid but well-formed script subtag. + is(isc.getDictionaryDisplayName("qaz-Qaaz"), "qaz / Qaaz", "'qaz-Qaaz' should display as 'qaz / Qaaz'"); + is(isc.getDictionaryDisplayName("qaz-Qaaz-fonipa"), "qaz / Qaaz (fonipa)", "'qaz-Qaaz-fonipa' should display as 'qaz / Qaaz (fonipa)'"); + is(isc.getDictionaryDisplayName("qaz-Qaaz-qxqaaaaz"), "qaz / Qaaz (qxqaaaaz)", "'qaz-Qaaz-qxqaaaaz' should display as 'qaz / Qaaz (qxqaaaaz)'"); + is(isc.getDictionaryDisplayName("qaz-Qaaz-US"), "qaz (United States) / Qaaz", "'qaz-Qaaz-US' should display as 'qaz (United States) / Qaaz'"); + is(isc.getDictionaryDisplayName("qaz-Qaaz-US-fonipa"), "qaz (United States) / Qaaz (fonipa)", "'qaz-Qaaz-US-fonipa' should display as 'qaz (United States) / Qaaz (fonipa)'"); + is(isc.getDictionaryDisplayName("qaz-Qaaz-US-qxqaaaaz"), "qaz (United States) / Qaaz (qxqaaaaz)", "'qaz-Qaaz-US-qxqaaaaz' should display as 'qaz (United States) / Qaaz (qxqaaaaz)'"); + is(isc.getDictionaryDisplayName("qaz-Qaaz-WO"), "qaz (WO) / Qaaz", "'qaz-Qaaz-WO' should display as 'qaz (WO) / Qaaz'"); + is(isc.getDictionaryDisplayName("qaz-Qaaz-WO-fonipa"), "qaz (WO) / Qaaz (fonipa)", "'qaz-Qaaz-WO-fonipa' should display as 'qaz (WO) / Qaaz (fonipa)'"); + is(isc.getDictionaryDisplayName("qaz-Qaaz-WO-qxqaaaaz"), "qaz (WO) / Qaaz (qxqaaaaz)", "'qaz-Qaaz-WO-qxqaaaaz' should display as 'qaz (WO) / Qaaz (qxqaaaaz)'"); + + // Check multiple variant subtags. + todo_is(isc.getDictionaryDisplayName("en-Cyrl-US-fonipa-fonxsamp"), "English (United States) / Cyrillic (fonipa / fonxsamp)", "'en-Cyrl-US-fonipa-fonxsamp' should display as 'English (United States) / Cyrillic (fonipa / fonxsamp)'"); + todo_is(isc.getDictionaryDisplayName("en-Cyrl-US-fonipa-qxqaaaaz"), "English (United States) / Cyrillic (fonipa / qxqaaaaz)", "'en-Cyrl-US-fonipa-qxqaaaaz' should display as 'English (United States) / Cyrillic (fonipa / qxqaaaaz)'"); + todo_is(isc.getDictionaryDisplayName("en-Cyrl-US-fonipa-fonxsamp-qxqaaaaz"), "English (United States) / Cyrillic (fonipa / fonxsamp / qxqaaaaz)", "'en-Cyrl-US-fonipa-fonxsamp-qxqaaaaz' should display as 'English (United States) / Cyrillic (fonipa / fonxsamp / qxqaaaaz)'"); + is(isc.getDictionaryDisplayName("qaz-Qaaz-WO-fonipa-fonxsamp"), "qaz (WO) / Qaaz (fonipa / fonxsamp)", "'qaz-Qaaz-WO-fonipa-fonxsamp' should display as 'qaz (WO) / Qaaz (fonipa / fonxsamp)'"); + is(isc.getDictionaryDisplayName("qaz-Qaaz-WO-fonipa-qxqaaaaz"), "qaz (WO) / Qaaz (fonipa / qxqaaaaz)", "'qaz-Qaaz-WO-fonipa-qxqaaaaz' should display as 'qaz (WO) / Qaaz (fonipa / qxqaaaaz)'"); + is(isc.getDictionaryDisplayName("qaz-Qaaz-WO-fonipa-fonxsamp-qxqaaaaz"), "qaz (WO) / Qaaz (fonipa / fonxsamp / qxqaaaaz)", "'qaz-Qaaz-WO-fonipa-fonxsamp-qxqaaaaz' should display as 'qaz (WO) / Qaaz (fonipa / fonxsamp / qxqaaaaz)'"); + + // Check numeric region subtag. + todo_is(isc.getDictionaryDisplayName("es-419"), "Spanish (Latin America and the Caribbean)", "'es-419' should display as 'Spanish (Latin America and the Caribbean)'"); + + // Check that extension subtags are ignored. + todo_is(isc.getDictionaryDisplayName("en-Cyrl-t-en-latn-m0-ungegn-2007"), "English / Cyrillic", "'en-Cyrl-t-en-latn-m0-ungegn-2007' should display as 'English / Cyrillic'"); + + // Check that privateuse subtags are ignored. + is(isc.getDictionaryDisplayName("en-x-ignore"), "English", "'en-x-ignore' should display as 'English'"); + is(isc.getDictionaryDisplayName("en-x-ignore-this"), "English", "'en-x-ignore-this' should display as 'English'"); + is(isc.getDictionaryDisplayName("en-x-ignore-this-subtag"), "English", "'en-x-ignore-this-subtag' should display as 'English'"); + + // Check that both extension and privateuse subtags are ignored. + todo_is(isc.getDictionaryDisplayName("en-Cyrl-t-en-latn-m0-ungegn-2007-x-ignore-this-subtag"), "English / Cyrillic", "'en-Cyrl-t-en-latn-m0-ungegn-2007-x-ignore-this-subtag' should display as 'English / Cyrillic'"); + + // XXX: Check grandfathered tags. + }, +}; diff --git a/toolkit/modules/tests/browser/browser_PageMetadata.js b/toolkit/modules/tests/browser/browser_PageMetadata.js new file mode 100644 index 000000000..ca6e18368 --- /dev/null +++ b/toolkit/modules/tests/browser/browser_PageMetadata.js @@ -0,0 +1,73 @@ +/** + * Tests PageMetadata.jsm, which extracts metadata and microdata from a + * document. + */ + +var {PageMetadata} = Cu.import("resource://gre/modules/PageMetadata.jsm", {}); + +var rootURL = "http://example.com/browser/toolkit/modules/tests/browser/"; + +function promiseDocument(fileName) { + let url = rootURL + fileName; + + return new Promise((resolve, reject) => { + let xhr = new XMLHttpRequest(); + xhr.onload = () => resolve(xhr.responseXML); + xhr.onerror = () => reject(new Error("Error loading document")); + xhr.open("GET", url); + xhr.responseType = "document"; + xhr.send(); + }); +} + +/** + * Load a simple document. + */ +add_task(function* simpleDoc() { + let fileName = "metadata_simple.html"; + info(`Loading a simple page, ${fileName}`); + + let doc = yield promiseDocument(fileName); + Assert.notEqual(doc, null, + "Should have a document to analyse"); + + let data = PageMetadata.getData(doc); + Assert.notEqual(data, null, + "Should have non-null result"); + Assert.equal(data.url, rootURL + fileName, + "Should have expected url property"); + Assert.equal(data.title, "Test Title", + "Should have expected title property"); + Assert.equal(data.description, "A very simple test page", + "Should have expected title property"); +}); + +add_task(function* titlesDoc() { + let fileName = "metadata_titles.html"; + info(`Loading titles page, ${fileName}`); + + let doc = yield promiseDocument(fileName); + Assert.notEqual(doc, null, + "Should have a document to analyse"); + + let data = PageMetadata.getData(doc); + Assert.notEqual(data, null, + "Should have non-null result"); + Assert.equal(data.title, "Test Titles", + "Should use the page title, not the open graph title"); +}); + +add_task(function* titlesFallbackDoc() { + let fileName = "metadata_titles_fallback.html"; + info(`Loading titles page, ${fileName}`); + + let doc = yield promiseDocument(fileName); + Assert.notEqual(doc, null, + "Should have a document to analyse"); + + let data = PageMetadata.getData(doc); + Assert.notEqual(data, null, + "Should have non-null result"); + Assert.equal(data.title, "Title", + "Should use the open graph title"); +}); diff --git a/toolkit/modules/tests/browser/browser_PromiseMessage.js b/toolkit/modules/tests/browser/browser_PromiseMessage.js new file mode 100644 index 000000000..e967ac4c9 --- /dev/null +++ b/toolkit/modules/tests/browser/browser_PromiseMessage.js @@ -0,0 +1,38 @@ +/* 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/. */ +/* global Cu, BrowserTestUtils, is, ok, add_task, gBrowser */ +"use strict"; +Cu.import("resource://gre/modules/PromiseMessage.jsm", this); + + +const url = "http://example.org/tests/dom/manifest/test/resource.sjs"; + +/** + * Test basic API error conditions + */ +add_task(function* () { + yield BrowserTestUtils.withNewTab({gBrowser, url}, testPromiseMessageAPI) +}); + +function* testPromiseMessageAPI(aBrowser) { + // Reusing an existing message. + const msgKey = "DOM:WebManifest:hasManifestLink"; + const mm = aBrowser.messageManager; + const id = "this should not change"; + const foo = "neitherShouldThis"; + const data = {id, foo}; + + // This just returns false, and it doesn't matter for this test. + yield PromiseMessage.send(mm, msgKey, data); + + // Check that no new props were added + const props = Object.getOwnPropertyNames(data); + ok(props.length === 2, "There should only be 2 props"); + ok(props.includes("id"), "Has the id property"); + ok(props.includes("foo"), "Has the foo property"); + + // Check that the props didn't change. + is(data.id, id, "The id prop must not change."); + is(data.foo, foo, "The foo prop must not change."); +} diff --git a/toolkit/modules/tests/browser/browser_RemotePageManager.js b/toolkit/modules/tests/browser/browser_RemotePageManager.js new file mode 100644 index 000000000..774d33034 --- /dev/null +++ b/toolkit/modules/tests/browser/browser_RemotePageManager.js @@ -0,0 +1,400 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const TEST_URL = "http://www.example.com/browser/toolkit/modules/tests/browser/testremotepagemanager.html"; + +var { RemotePages, RemotePageManager } = Cu.import("resource://gre/modules/RemotePageManager.jsm", {}); + +function failOnMessage(message) { + ok(false, "Should not have seen message " + message.name); +} + +function waitForMessage(port, message, expectedPort = port) { + return new Promise((resolve) => { + function listener(message) { + is(message.target, expectedPort, "Message should be from the right port."); + + port.removeMessageListener(listener); + resolve(message); + } + + port.addMessageListener(message, listener); + }); +} + +function waitForPort(url, createTab = true) { + return new Promise((resolve) => { + RemotePageManager.addRemotePageListener(url, (port) => { + RemotePageManager.removeRemotePageListener(url); + + waitForMessage(port, "RemotePage:Load").then(() => resolve(port)); + }); + + if (createTab) + gBrowser.selectedTab = gBrowser.addTab(url); + }); +} + +function waitForPage(pages) { + return new Promise((resolve) => { + function listener({ target }) { + pages.removeMessageListener("RemotePage:Init", listener); + + waitForMessage(target, "RemotePage:Load").then(() => resolve(target)); + } + + pages.addMessageListener("RemotePage:Init", listener); + gBrowser.selectedTab = gBrowser.addTab(TEST_URL); + }); +} + +function swapDocShells(browser1, browser2) { + // Swap frameLoaders. + browser1.swapDocShells(browser2); + + // Swap permanentKeys. + let tmp = browser1.permanentKey; + browser1.permanentKey = browser2.permanentKey; + browser2.permanentKey = tmp; +} + +// Test that opening a page creates a port, sends the load event and then +// navigating to a new page sends the unload event. Going back should create a +// new port +add_task(function* init_navigate() { + let port = yield waitForPort(TEST_URL); + is(port.browser, gBrowser.selectedBrowser, "Port is for the correct browser"); + + let loaded = new Promise(resolve => { + function listener() { + gBrowser.selectedBrowser.removeEventListener("load", listener, true); + resolve(); + } + gBrowser.selectedBrowser.addEventListener("load", listener, true); + gBrowser.loadURI("about:blank"); + }); + + yield waitForMessage(port, "RemotePage:Unload"); + + // Port should be destroyed now + try { + port.addMessageListener("Foo", failOnMessage); + ok(false, "Should have seen exception"); + } + catch (e) { + ok(true, "Should have seen exception"); + } + + try { + port.sendAsyncMessage("Foo"); + ok(false, "Should have seen exception"); + } + catch (e) { + ok(true, "Should have seen exception"); + } + + yield loaded; + + gBrowser.goBack(); + port = yield waitForPort(TEST_URL, false); + + port.sendAsyncMessage("Ping2"); + let message = yield waitForMessage(port, "Pong2"); + port.destroy(); + + gBrowser.removeCurrentTab(); +}); + +// Test that opening a page creates a port, sends the load event and then +// closing the tab sends the unload event +add_task(function* init_close() { + let port = yield waitForPort(TEST_URL); + is(port.browser, gBrowser.selectedBrowser, "Port is for the correct browser"); + + let unloadPromise = waitForMessage(port, "RemotePage:Unload"); + gBrowser.removeCurrentTab(); + yield unloadPromise; + + // Port should be destroyed now + try { + port.addMessageListener("Foo", failOnMessage); + ok(false, "Should have seen exception"); + } + catch (e) { + ok(true, "Should have seen exception"); + } + + try { + port.sendAsyncMessage("Foo"); + ok(false, "Should have seen exception"); + } + catch (e) { + ok(true, "Should have seen exception"); + } +}); + +// Tests that we can send messages to individual pages even when more than one +// is open +add_task(function* multiple_ports() { + let port1 = yield waitForPort(TEST_URL); + is(port1.browser, gBrowser.selectedBrowser, "Port is for the correct browser"); + + let port2 = yield waitForPort(TEST_URL); + is(port2.browser, gBrowser.selectedBrowser, "Port is for the correct browser"); + + port2.addMessageListener("Pong", failOnMessage); + port1.sendAsyncMessage("Ping", { str: "foobar", counter: 0 }); + let message = yield waitForMessage(port1, "Pong"); + port2.removeMessageListener("Pong", failOnMessage); + is(message.data.str, "foobar", "String should pass through"); + is(message.data.counter, 1, "Counter should be incremented"); + + port1.addMessageListener("Pong", failOnMessage); + port2.sendAsyncMessage("Ping", { str: "foobaz", counter: 5 }); + message = yield waitForMessage(port2, "Pong"); + port1.removeMessageListener("Pong", failOnMessage); + is(message.data.str, "foobaz", "String should pass through"); + is(message.data.counter, 6, "Counter should be incremented"); + + let unloadPromise = waitForMessage(port2, "RemotePage:Unload"); + gBrowser.removeTab(gBrowser.getTabForBrowser(port2.browser)); + yield unloadPromise; + + try { + port2.addMessageListener("Pong", failOnMessage); + ok(false, "Should not have been able to add a new message listener to a destroyed port."); + } + catch (e) { + ok(true, "Should not have been able to add a new message listener to a destroyed port."); + } + + port1.sendAsyncMessage("Ping", { str: "foobar", counter: 0 }); + message = yield waitForMessage(port1, "Pong"); + is(message.data.str, "foobar", "String should pass through"); + is(message.data.counter, 1, "Counter should be incremented"); + + unloadPromise = waitForMessage(port1, "RemotePage:Unload"); + gBrowser.removeTab(gBrowser.getTabForBrowser(port1.browser)); + yield unloadPromise; +}); + +// Tests that swapping browser docshells doesn't break the ports +add_task(function* browser_switch() { + let port1 = yield waitForPort(TEST_URL); + is(port1.browser, gBrowser.selectedBrowser, "Port is for the correct browser"); + let browser1 = gBrowser.selectedBrowser; + port1.sendAsyncMessage("SetCookie", { value: "om nom" }); + + let port2 = yield waitForPort(TEST_URL); + is(port2.browser, gBrowser.selectedBrowser, "Port is for the correct browser"); + let browser2 = gBrowser.selectedBrowser; + port2.sendAsyncMessage("SetCookie", { value: "om nom nom" }); + + port2.addMessageListener("Cookie", failOnMessage); + port1.sendAsyncMessage("GetCookie"); + let message = yield waitForMessage(port1, "Cookie"); + port2.removeMessageListener("Cookie", failOnMessage); + is(message.data.value, "om nom", "Should have the right cookie"); + + port1.addMessageListener("Cookie", failOnMessage); + port2.sendAsyncMessage("GetCookie", { str: "foobaz", counter: 5 }); + message = yield waitForMessage(port2, "Cookie"); + port1.removeMessageListener("Cookie", failOnMessage); + is(message.data.value, "om nom nom", "Should have the right cookie"); + + swapDocShells(browser1, browser2); + is(port1.browser, browser2, "Should have noticed the swap"); + is(port2.browser, browser1, "Should have noticed the swap"); + + // Cookies should have stayed the same + port2.addMessageListener("Cookie", failOnMessage); + port1.sendAsyncMessage("GetCookie"); + message = yield waitForMessage(port1, "Cookie"); + port2.removeMessageListener("Cookie", failOnMessage); + is(message.data.value, "om nom", "Should have the right cookie"); + + port1.addMessageListener("Cookie", failOnMessage); + port2.sendAsyncMessage("GetCookie", { str: "foobaz", counter: 5 }); + message = yield waitForMessage(port2, "Cookie"); + port1.removeMessageListener("Cookie", failOnMessage); + is(message.data.value, "om nom nom", "Should have the right cookie"); + + swapDocShells(browser1, browser2); + is(port1.browser, browser1, "Should have noticed the swap"); + is(port2.browser, browser2, "Should have noticed the swap"); + + // Cookies should have stayed the same + port2.addMessageListener("Cookie", failOnMessage); + port1.sendAsyncMessage("GetCookie"); + message = yield waitForMessage(port1, "Cookie"); + port2.removeMessageListener("Cookie", failOnMessage); + is(message.data.value, "om nom", "Should have the right cookie"); + + port1.addMessageListener("Cookie", failOnMessage); + port2.sendAsyncMessage("GetCookie", { str: "foobaz", counter: 5 }); + message = yield waitForMessage(port2, "Cookie"); + port1.removeMessageListener("Cookie", failOnMessage); + is(message.data.value, "om nom nom", "Should have the right cookie"); + + let unloadPromise = waitForMessage(port2, "RemotePage:Unload"); + gBrowser.removeTab(gBrowser.getTabForBrowser(browser2)); + yield unloadPromise; + + unloadPromise = waitForMessage(port1, "RemotePage:Unload"); + gBrowser.removeTab(gBrowser.getTabForBrowser(browser1)); + yield unloadPromise; +}); + +// Tests that removeMessageListener in chrome works +add_task(function* remove_chrome_listener() { + let port = yield waitForPort(TEST_URL); + is(port.browser, gBrowser.selectedBrowser, "Port is for the correct browser"); + + // This relies on messages sent arriving in the same order. Pong will be + // sent back before Pong2 so if removeMessageListener fails the test will fail + port.addMessageListener("Pong", failOnMessage); + port.removeMessageListener("Pong", failOnMessage); + port.sendAsyncMessage("Ping", { str: "remove_listener", counter: 27 }); + port.sendAsyncMessage("Ping2"); + yield waitForMessage(port, "Pong2"); + + let unloadPromise = waitForMessage(port, "RemotePage:Unload"); + gBrowser.removeCurrentTab(); + yield unloadPromise; +}); + +// Tests that removeMessageListener in content works +add_task(function* remove_content_listener() { + let port = yield waitForPort(TEST_URL); + is(port.browser, gBrowser.selectedBrowser, "Port is for the correct browser"); + + // This relies on messages sent arriving in the same order. Pong3 would be + // sent back before Pong2 so if removeMessageListener fails the test will fail + port.addMessageListener("Pong3", failOnMessage); + port.sendAsyncMessage("Ping3"); + port.sendAsyncMessage("Ping2"); + yield waitForMessage(port, "Pong2"); + + let unloadPromise = waitForMessage(port, "RemotePage:Unload"); + gBrowser.removeCurrentTab(); + yield unloadPromise; +}); + +// Test RemotePages works +add_task(function* remote_pages_basic() { + let pages = new RemotePages(TEST_URL); + let port = yield waitForPage(pages); + is(port.browser, gBrowser.selectedBrowser, "Port is for the correct browser"); + + // Listening to global messages should work + let unloadPromise = waitForMessage(pages, "RemotePage:Unload", port); + gBrowser.removeCurrentTab(); + yield unloadPromise; + + pages.destroy(); + + // RemotePages should be destroyed now + try { + pages.addMessageListener("Foo", failOnMessage); + ok(false, "Should have seen exception"); + } + catch (e) { + ok(true, "Should have seen exception"); + } + + try { + pages.sendAsyncMessage("Foo"); + ok(false, "Should have seen exception"); + } + catch (e) { + ok(true, "Should have seen exception"); + } +}); + +// Test sending messages to all remote pages works +add_task(function* remote_pages_multiple() { + let pages = new RemotePages(TEST_URL); + let port1 = yield waitForPage(pages); + let port2 = yield waitForPage(pages); + + let pongPorts = []; + yield new Promise((resolve) => { + function listener({ name, target, data }) { + is(name, "Pong", "Should have seen the right response."); + is(data.str, "remote_pages", "String should pass through"); + is(data.counter, 43, "Counter should be incremented"); + pongPorts.push(target); + if (pongPorts.length == 2) + resolve(); + } + + pages.addMessageListener("Pong", listener); + pages.sendAsyncMessage("Ping", { str: "remote_pages", counter: 42 }); + }); + + // We don't make any guarantees about which order messages are sent to known + // pages so the pongs could have come back in any order. + isnot(pongPorts[0], pongPorts[1], "Should have received pongs from different ports"); + ok(pongPorts.indexOf(port1) >= 0, "Should have seen a pong from port1"); + ok(pongPorts.indexOf(port2) >= 0, "Should have seen a pong from port2"); + + // After destroy we should see no messages + pages.addMessageListener("RemotePage:Unload", failOnMessage); + pages.destroy(); + + gBrowser.removeTab(gBrowser.getTabForBrowser(port1.browser)); + gBrowser.removeTab(gBrowser.getTabForBrowser(port2.browser)); +}); + +// Test sending various types of data across the boundary +add_task(function* send_data() { + let port = yield waitForPort(TEST_URL); + is(port.browser, gBrowser.selectedBrowser, "Port is for the correct browser"); + + let data = { + integer: 45, + real: 45.78, + str: "foobar", + array: [1, 2, 3, 5, 27] + }; + + port.sendAsyncMessage("SendData", data); + let message = yield waitForMessage(port, "ReceivedData"); + + ok(message.data.result, message.data.status); + + gBrowser.removeCurrentTab(); +}); + +// Test sending an object of data across the boundary +add_task(function* send_data2() { + let port = yield waitForPort(TEST_URL); + is(port.browser, gBrowser.selectedBrowser, "Port is for the correct browser"); + + let data = { + integer: 45, + real: 45.78, + str: "foobar", + array: [1, 2, 3, 5, 27] + }; + + port.sendAsyncMessage("SendData2", {data}); + let message = yield waitForMessage(port, "ReceivedData2"); + + ok(message.data.result, message.data.status); + + gBrowser.removeCurrentTab(); +}); + +add_task(function* get_ports_for_browser() { + let pages = new RemotePages(TEST_URL); + let port = yield waitForPage(pages); + // waitForPage creates a new tab and selects it by default, so + // the selected tab should be the one hosting this port. + let browser = gBrowser.selectedBrowser; + let foundPorts = pages.portsForBrowser(browser); + is(foundPorts.length, 1, "There should only be one port for this simple page"); + is(foundPorts[0], port, "Should find the port"); + gBrowser.removeCurrentTab(); +}); diff --git a/toolkit/modules/tests/browser/browser_Troubleshoot.js b/toolkit/modules/tests/browser/browser_Troubleshoot.js new file mode 100644 index 000000000..34c2a2791 --- /dev/null +++ b/toolkit/modules/tests/browser/browser_Troubleshoot.js @@ -0,0 +1,546 @@ +/* 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/. */ + +// Ideally this would be an xpcshell test, but Troubleshoot relies on things +// that aren't initialized outside of a XUL app environment like AddonManager +// and the "@mozilla.org/xre/app-info;1" component. + +Components.utils.import("resource://gre/modules/AppConstants.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://gre/modules/Troubleshoot.jsm"); + +function test() { + waitForExplicitFinish(); + function doNextTest() { + if (!tests.length) { + finish(); + return; + } + tests.shift()(doNextTest); + } + doNextTest(); +} + +registerCleanupFunction(function () { + // Troubleshoot.jsm is imported into the global scope -- the window -- above. + // If it's not deleted, it outlives the test and is reported as a leak. + delete window.Troubleshoot; +}); + +var tests = [ + + function snapshotSchema(done) { + Troubleshoot.snapshot(function (snapshot) { + try { + validateObject(snapshot, SNAPSHOT_SCHEMA); + ok(true, "The snapshot should conform to the schema."); + } + catch (err) { + ok(false, "Schema mismatch, " + err); + } + done(); + }); + }, + + function modifiedPreferences(done) { + let prefs = [ + "javascript.troubleshoot", + "troubleshoot.foo", + "javascript.print_to_filename", + "network.proxy.troubleshoot", + ]; + prefs.forEach(function (p) { + Services.prefs.setBoolPref(p, true); + is(Services.prefs.getBoolPref(p), true, "The pref should be set: " + p); + }); + Troubleshoot.snapshot(function (snapshot) { + let p = snapshot.modifiedPreferences; + is(p["javascript.troubleshoot"], true, + "The pref should be present because it's whitelisted " + + "but not blacklisted."); + ok(!("troubleshoot.foo" in p), + "The pref should be absent because it's not in the whitelist."); + ok(!("javascript.print_to_filename" in p), + "The pref should be absent because it's blacklisted."); + ok(!("network.proxy.troubleshoot" in p), + "The pref should be absent because it's blacklisted."); + prefs.forEach(p => Services.prefs.deleteBranch(p)); + done(); + }); + }, + + function unicodePreferences(done) { + let name = "font.name.sans-serif.x-western"; + let utf8Value = "\xc4\x8capk\xc5\xafv Krasopis" + let unicodeValue = "\u010Capk\u016Fv Krasopis"; + + // set/getCharPref work with 8bit strings (utf8) + Services.prefs.setCharPref(name, utf8Value); + + Troubleshoot.snapshot(function (snapshot) { + let p = snapshot.modifiedPreferences; + is(p[name], unicodeValue, "The pref should have correct Unicode value."); + Services.prefs.deleteBranch(name); + done(); + }); + } +]; + +// This is inspired by JSON Schema, or by the example on its Wikipedia page +// anyway. +const SNAPSHOT_SCHEMA = { + type: "object", + required: true, + properties: { + application: { + required: true, + type: "object", + properties: { + name: { + required: true, + type: "string", + }, + version: { + required: true, + type: "string", + }, + buildID: { + required: true, + type: "string", + }, + userAgent: { + required: true, + type: "string", + }, + osVersion: { + required: true, + type: "string", + }, + vendor: { + type: "string", + }, + updateChannel: { + type: "string", + }, + supportURL: { + type: "string", + }, + remoteAutoStart: { + type: "boolean", + required: true, + }, + autoStartStatus: { + type: "number", + }, + numTotalWindows: { + type: "number", + }, + numRemoteWindows: { + type: "number", + }, + safeMode: { + type: "boolean", + }, + }, + }, + crashes: { + required: false, + type: "object", + properties: { + pending: { + required: true, + type: "number", + }, + submitted: { + required: true, + type: "array", + items: { + type: "object", + properties: { + id: { + required: true, + type: "string", + }, + date: { + required: true, + type: "number", + }, + pending: { + required: true, + type: "boolean", + }, + }, + }, + }, + }, + }, + extensions: { + required: true, + type: "array", + items: { + type: "object", + properties: { + name: { + required: true, + type: "string", + }, + version: { + required: true, + type: "string", + }, + id: { + required: true, + type: "string", + }, + isActive: { + required: true, + type: "boolean", + }, + }, + }, + }, + modifiedPreferences: { + required: true, + type: "object", + }, + lockedPreferences: { + required: true, + type: "object", + }, + graphics: { + required: true, + type: "object", + properties: { + numTotalWindows: { + required: true, + type: "number", + }, + numAcceleratedWindows: { + required: true, + type: "number", + }, + windowLayerManagerType: { + type: "string", + }, + windowLayerManagerRemote: { + type: "boolean", + }, + supportsHardwareH264: { + type: "string", + }, + currentAudioBackend: { + type: "string", + }, + numAcceleratedWindowsMessage: { + type: "array", + }, + adapterDescription: { + type: "string", + }, + adapterVendorID: { + type: "string", + }, + adapterDeviceID: { + type: "string", + }, + adapterSubsysID: { + type: "string", + }, + adapterRAM: { + type: "string", + }, + adapterDrivers: { + type: "string", + }, + driverVersion: { + type: "string", + }, + driverDate: { + type: "string", + }, + adapterDescription2: { + type: "string", + }, + adapterVendorID2: { + type: "string", + }, + adapterDeviceID2: { + type: "string", + }, + adapterSubsysID2: { + type: "string", + }, + adapterRAM2: { + type: "string", + }, + adapterDrivers2: { + type: "string", + }, + driverVersion2: { + type: "string", + }, + driverDate2: { + type: "string", + }, + isGPU2Active: { + type: "boolean", + }, + direct2DEnabled: { + type: "boolean", + }, + directWriteEnabled: { + type: "boolean", + }, + directWriteVersion: { + type: "string", + }, + clearTypeParameters: { + type: "string", + }, + webglRenderer: { + type: "string", + }, + webgl2Renderer: { + type: "string", + }, + info: { + type: "object", + }, + failures: { + type: "array", + items: { + type: "string", + }, + }, + indices: { + type: "array", + items: { + type: "number", + }, + }, + featureLog: { + type: "object", + }, + crashGuards: { + type: "array", + }, + direct2DEnabledMessage: { + type: "array", + }, + }, + }, + javaScript: { + required: true, + type: "object", + properties: { + incrementalGCEnabled: { + type: "boolean", + }, + }, + }, + accessibility: { + required: true, + type: "object", + properties: { + isActive: { + required: true, + type: "boolean", + }, + forceDisabled: { + type: "number", + }, + }, + }, + libraryVersions: { + required: true, + type: "object", + properties: { + NSPR: { + required: true, + type: "object", + properties: { + minVersion: { + required: true, + type: "string", + }, + version: { + required: true, + type: "string", + }, + }, + }, + NSS: { + required: true, + type: "object", + properties: { + minVersion: { + required: true, + type: "string", + }, + version: { + required: true, + type: "string", + }, + }, + }, + NSSUTIL: { + required: true, + type: "object", + properties: { + minVersion: { + required: true, + type: "string", + }, + version: { + required: true, + type: "string", + }, + }, + }, + NSSSSL: { + required: true, + type: "object", + properties: { + minVersion: { + required: true, + type: "string", + }, + version: { + required: true, + type: "string", + }, + }, + }, + NSSSMIME: { + required: true, + type: "object", + properties: { + minVersion: { + required: true, + type: "string", + }, + version: { + required: true, + type: "string", + }, + }, + }, + }, + }, + userJS: { + required: true, + type: "object", + properties: { + exists: { + required: true, + type: "boolean", + }, + }, + }, + experiments: { + type: "array", + }, + sandbox: { + required: false, + type: "object", + properties: { + hasSeccompBPF: { + required: AppConstants.platform == "linux", + type: "boolean" + }, + hasSeccompTSync: { + required: AppConstants.platform == "linux", + type: "boolean" + }, + hasUserNamespaces: { + required: AppConstants.platform == "linux", + type: "boolean" + }, + hasPrivilegedUserNamespaces: { + required: AppConstants.platform == "linux", + type: "boolean" + }, + canSandboxContent: { + required: false, + type: "boolean" + }, + canSandboxMedia: { + required: false, + type: "boolean" + }, + contentSandboxLevel: { + required: AppConstants.MOZ_CONTENT_SANDBOX, + type: "number" + }, + }, + }, + }, +}; + +/** + * Throws an Error if obj doesn't conform to schema. That way you get a nice + * error message and a stack to help you figure out what went wrong, which you + * wouldn't get if this just returned true or false instead. There's still + * room for improvement in communicating validation failures, however. + * + * @param obj The object to validate. + * @param schema The schema that obj should conform to. + */ +function validateObject(obj, schema) { + if (obj === undefined && !schema.required) + return; + if (typeof(schema.type) != "string") + throw schemaErr("'type' must be a string", schema); + if (objType(obj) != schema.type) + throw validationErr("Object is not of the expected type", obj, schema); + let validatorFnName = "validateObject_" + schema.type; + if (!(validatorFnName in this)) + throw schemaErr("Validator function not defined for type", schema); + this[validatorFnName](obj, schema); +} + +function validateObject_object(obj, schema) { + if (typeof(schema.properties) != "object") + // Don't care what obj's properties are. + return; + // First check that all the schema's properties match the object. + for (let prop in schema.properties) + validateObject(obj[prop], schema.properties[prop]); + // Now check that the object doesn't have any properties not in the schema. + for (let prop in obj) + if (!(prop in schema.properties)) + throw validationErr("Object has property "+prop+" not in schema", obj, schema); +} + +function validateObject_array(array, schema) { + if (typeof(schema.items) != "object") + // Don't care what the array's elements are. + return; + array.forEach(elt => validateObject(elt, schema.items)); +} + +function validateObject_string(str, schema) {} +function validateObject_boolean(bool, schema) {} +function validateObject_number(num, schema) {} + +function validationErr(msg, obj, schema) { + return new Error("Validation error: " + msg + + ": object=" + JSON.stringify(obj) + + ", schema=" + JSON.stringify(schema)); +} + +function schemaErr(msg, schema) { + return new Error("Schema error: " + msg + ": " + JSON.stringify(schema)); +} + +function objType(obj) { + let type = typeof(obj); + if (type != "object") + return type; + if (Array.isArray(obj)) + return "array"; + if (obj === null) + return "null"; + return type; +} diff --git a/toolkit/modules/tests/browser/browser_WebNavigation.js b/toolkit/modules/tests/browser/browser_WebNavigation.js new file mode 100644 index 000000000..e09cb1994 --- /dev/null +++ b/toolkit/modules/tests/browser/browser_WebNavigation.js @@ -0,0 +1,140 @@ +"use strict"; + +var { interfaces: Ci, classes: Cc, utils: Cu, results: Cr } = Components; + +var {WebNavigation} = Cu.import("resource://gre/modules/WebNavigation.jsm", {}); + +const BASE = "http://example.com/browser/toolkit/modules/tests/browser"; +const URL = BASE + "/file_WebNavigation_page1.html"; +const FRAME = BASE + "/file_WebNavigation_page2.html"; +const FRAME2 = BASE + "/file_WebNavigation_page3.html"; + +const EVENTS = [ + "onBeforeNavigate", + "onCommitted", + "onDOMContentLoaded", + "onCompleted", + "onErrorOccurred", + "onReferenceFragmentUpdated", +]; + +const REQUIRED = [ + "onBeforeNavigate", + "onCommitted", + "onDOMContentLoaded", + "onCompleted", +]; + +var expectedBrowser; +var received = []; +var completedResolve; +var waitingURL, waitingEvent; +var rootWindowID; + +function gotEvent(event, details) +{ + if (!details.url.startsWith(BASE)) { + return; + } + info(`Got ${event} ${details.url} ${details.windowId} ${details.parentWindowId}`); + + is(details.browser, expectedBrowser, "correct <browser> element"); + + received.push({url: details.url, event}); + + if (typeof(rootWindowID) == "undefined") { + rootWindowID = details.windowId; + } + + if (details.url == URL) { + is(details.windowId, rootWindowID, "root window ID correct"); + } else { + is(details.parentWindowId, rootWindowID, "parent window ID correct"); + isnot(details.windowId, rootWindowID, "window ID probably okay"); + } + + isnot(details.windowId, undefined); + isnot(details.parentWindowId, undefined); + + if (details.url == waitingURL && event == waitingEvent) { + completedResolve(); + } +} + +function loadViaFrameScript(url, event, script) +{ + // Loading via a frame script ensures that the chrome process never + // "gets ahead" of frame scripts in non-e10s mode. + received = []; + waitingURL = url; + waitingEvent = event; + expectedBrowser.messageManager.loadFrameScript("data:," + script, false); + return new Promise(resolve => { completedResolve = resolve; }); +} + +add_task(function* webnav_ordering() { + let listeners = {}; + for (let event of EVENTS) { + listeners[event] = gotEvent.bind(null, event); + WebNavigation[event].addListener(listeners[event]); + } + + gBrowser.selectedTab = gBrowser.addTab(); + let browser = gBrowser.selectedBrowser; + expectedBrowser = browser; + + yield BrowserTestUtils.browserLoaded(browser); + + yield loadViaFrameScript(URL, "onCompleted", `content.location = "${URL}";`); + + function checkRequired(url) { + for (let event of REQUIRED) { + let found = false; + for (let r of received) { + if (r.url == url && r.event == event) { + found = true; + } + } + ok(found, `Received event ${event} from ${url}`); + } + } + + checkRequired(URL); + checkRequired(FRAME); + + function checkBefore(action1, action2) { + function find(action) { + for (let i = 0; i < received.length; i++) { + if (received[i].url == action.url && received[i].event == action.event) { + return i; + } + } + return -1; + } + + let index1 = find(action1); + let index2 = find(action2); + ok(index1 != -1, `Action ${JSON.stringify(action1)} happened`); + ok(index2 != -1, `Action ${JSON.stringify(action2)} happened`); + ok(index1 < index2, `Action ${JSON.stringify(action1)} happened before ${JSON.stringify(action2)}`); + } + + checkBefore({url: URL, event: "onCommitted"}, {url: FRAME, event: "onBeforeNavigate"}); + checkBefore({url: FRAME, event: "onCompleted"}, {url: URL, event: "onCompleted"}); + + yield loadViaFrameScript(FRAME2, "onCompleted", `content.frames[0].location = "${FRAME2}";`); + + checkRequired(FRAME2); + + yield loadViaFrameScript(FRAME2 + "#ref", "onReferenceFragmentUpdated", + "content.frames[0].document.getElementById('elt').click();"); + + info("Received onReferenceFragmentUpdated from FRAME2"); + + gBrowser.removeCurrentTab(); + + for (let event of EVENTS) { + WebNavigation[event].removeListener(listeners[event]); + } +}); + diff --git a/toolkit/modules/tests/browser/browser_WebRequest.js b/toolkit/modules/tests/browser/browser_WebRequest.js new file mode 100644 index 000000000..cdb28b16c --- /dev/null +++ b/toolkit/modules/tests/browser/browser_WebRequest.js @@ -0,0 +1,214 @@ +"use strict"; + +var { interfaces: Ci, classes: Cc, utils: Cu, results: Cr } = Components; + +var {WebRequest} = Cu.import("resource://gre/modules/WebRequest.jsm", {}); + +const BASE = "http://example.com/browser/toolkit/modules/tests/browser"; +const URL = BASE + "/file_WebRequest_page1.html"; + +var expected_browser; + +function checkType(details) +{ + let expected_type = "???"; + if (details.url.indexOf("style") != -1) { + expected_type = "stylesheet"; + } else if (details.url.indexOf("image") != -1) { + expected_type = "image"; + } else if (details.url.indexOf("script") != -1) { + expected_type = "script"; + } else if (details.url.indexOf("page1") != -1) { + expected_type = "main_frame"; + } else if (/page2|_redirection\.|dummy_page/.test(details.url)) { + expected_type = "sub_frame"; + } else if (details.url.indexOf("xhr") != -1) { + expected_type = "xmlhttprequest"; + } + is(details.type, expected_type, "resource type is correct"); +} + +var windowIDs = new Map(); + +var requested = []; + +function onBeforeRequest(details) +{ + info(`onBeforeRequest ${details.url}`); + if (details.url.startsWith(BASE)) { + requested.push(details.url); + + is(details.browser, expected_browser, "correct <browser> element"); + checkType(details); + + windowIDs.set(details.url, details.windowId); + if (details.url.indexOf("page2") != -1) { + let page1id = windowIDs.get(URL); + ok(details.windowId != page1id, "sub-frame gets its own window ID"); + is(details.parentWindowId, page1id, "parent window id is correct"); + } + } + if (details.url.indexOf("_bad.") != -1) { + return {cancel: true}; + } + return undefined; +} + +var sendHeaders = []; + +function onBeforeSendHeaders(details) +{ + info(`onBeforeSendHeaders ${details.url}`); + if (details.url.startsWith(BASE)) { + sendHeaders.push(details.url); + + is(details.browser, expected_browser, "correct <browser> element"); + checkType(details); + + let id = windowIDs.get(details.url); + is(id, details.windowId, "window ID same in onBeforeSendHeaders as onBeforeRequest"); + } + if (details.url.indexOf("_redirect.") != -1) { + return {redirectUrl: details.url.replace("_redirect.", "_good.")}; + } + return undefined; +} + +var beforeRedirect = []; + +function onBeforeRedirect(details) +{ + info(`onBeforeRedirect ${details.url} -> ${details.redirectUrl}`); + checkType(details); + if (details.url.startsWith(BASE)) { + beforeRedirect.push(details.url); + + is(details.browser, expected_browser, "correct <browser> element"); + checkType(details); + + let expectedUrl = details.url.replace("_redirect.", "_good.").replace(/\w+_redirection\..*/, "dummy_page.html") + is(details.redirectUrl, expectedUrl, "Correct redirectUrl value"); + } + let id = windowIDs.get(details.url); + is(id, details.windowId, "window ID same in onBeforeRedirect as onBeforeRequest"); + // associate stored windowId with final url + windowIDs.set(details.redirectUrl, details.windowId); + return {}; +} + +var headersReceived = []; + +function onResponseStarted(details) +{ + if (details.url.startsWith(BASE)) { + headersReceived.push(details.url); + } +} + +const expected_requested = [BASE + "/file_WebRequest_page1.html", + BASE + "/file_style_good.css", + BASE + "/file_style_bad.css", + BASE + "/file_style_redirect.css", + BASE + "/file_image_good.png", + BASE + "/file_image_bad.png", + BASE + "/file_image_redirect.png", + BASE + "/file_script_good.js", + BASE + "/file_script_bad.js", + BASE + "/file_script_redirect.js", + BASE + "/file_script_xhr.js", + BASE + "/file_WebRequest_page2.html", + BASE + "/nonexistent_script_url.js", + BASE + "/WebRequest_redirection.sjs", + BASE + "/dummy_page.html", + BASE + "/xhr_resource"]; + +const expected_sendHeaders = [BASE + "/file_WebRequest_page1.html", + BASE + "/file_style_good.css", + BASE + "/file_style_redirect.css", + BASE + "/file_image_good.png", + BASE + "/file_image_redirect.png", + BASE + "/file_script_good.js", + BASE + "/file_script_redirect.js", + BASE + "/file_script_xhr.js", + BASE + "/file_WebRequest_page2.html", + BASE + "/nonexistent_script_url.js", + BASE + "/WebRequest_redirection.sjs", + BASE + "/dummy_page.html", + BASE + "/xhr_resource"]; + +const expected_beforeRedirect = expected_sendHeaders.filter(u => /_redirect\./.test(u)) + .concat(BASE + "/WebRequest_redirection.sjs"); + +const expected_headersReceived = [BASE + "/file_WebRequest_page1.html", + BASE + "/file_style_good.css", + BASE + "/file_image_good.png", + BASE + "/file_script_good.js", + BASE + "/file_script_xhr.js", + BASE + "/file_WebRequest_page2.html", + BASE + "/nonexistent_script_url.js", + BASE + "/dummy_page.html", + BASE + "/xhr_resource"]; + +function removeDupes(list) +{ + let j = 0; + for (let i = 1; i < list.length; i++) { + if (list[i] != list[j]) { + j++; + if (i != j) { + list[j] = list[i]; + } + } + } + list.length = j + 1; +} + +function compareLists(list1, list2, kind) +{ + list1.sort(); + removeDupes(list1); + list2.sort(); + removeDupes(list2); + is(String(list1), String(list2), `${kind} URLs correct`); +} + +function* test_once() +{ + WebRequest.onBeforeRequest.addListener(onBeforeRequest, null, ["blocking"]); + WebRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, null, ["blocking"]); + WebRequest.onBeforeRedirect.addListener(onBeforeRedirect); + WebRequest.onResponseStarted.addListener(onResponseStarted); + + yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, + function* (browser) { + expected_browser = browser; + BrowserTestUtils.loadURI(browser, URL); + yield BrowserTestUtils.browserLoaded(expected_browser); + + expected_browser = null; + + yield ContentTask.spawn(browser, null, function() { + let win = content.wrappedJSObject; + is(win.success, 2, "Good script ran"); + is(win.failure, undefined, "Failure script didn't run"); + + let style = + content.getComputedStyle(content.document.getElementById("test"), null); + is(style.getPropertyValue("color"), "rgb(255, 0, 0)", "Good CSS loaded"); + }); + }); + + compareLists(requested, expected_requested, "requested"); + compareLists(sendHeaders, expected_sendHeaders, "sendHeaders"); + compareLists(beforeRedirect, expected_beforeRedirect, "beforeRedirect"); + compareLists(headersReceived, expected_headersReceived, "headersReceived"); + + WebRequest.onBeforeRequest.removeListener(onBeforeRequest); + WebRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders); + WebRequest.onBeforeRedirect.removeListener(onBeforeRedirect); + WebRequest.onResponseStarted.removeListener(onResponseStarted); +} + +// Run the test twice to make sure it works with caching. +add_task(test_once); +add_task(test_once); diff --git a/toolkit/modules/tests/browser/browser_WebRequest_cookies.js b/toolkit/modules/tests/browser/browser_WebRequest_cookies.js new file mode 100644 index 000000000..b8c4f24cb --- /dev/null +++ b/toolkit/modules/tests/browser/browser_WebRequest_cookies.js @@ -0,0 +1,89 @@ +"use strict"; + +var { interfaces: Ci, classes: Cc, utils: Cu, results: Cr } = Components; + +var {WebRequest} = Cu.import("resource://gre/modules/WebRequest.jsm", {}); + +const BASE = "http://example.com/browser/toolkit/modules/tests/browser"; +const URL = BASE + "/WebRequest_dynamic.sjs"; + +var countBefore = 0; +var countAfter = 0; + +function onBeforeSendHeaders(details) +{ + if (details.url != URL) { + return undefined; + } + + countBefore++; + + info(`onBeforeSendHeaders ${details.url}`); + let found = false; + let headers = []; + for (let {name, value} of details.requestHeaders) { + info(`Saw header ${name} '${value}'`); + if (name == "Cookie") { + is(value, "foopy=1", "Cookie is correct"); + headers.push({name, value: "blinky=1"}); + found = true; + } else { + headers.push({name, value}); + } + } + ok(found, "Saw cookie header"); + + return {requestHeaders: headers}; +} + +function onResponseStarted(details) +{ + if (details.url != URL) { + return; + } + + countAfter++; + + info(`onResponseStarted ${details.url}`); + let found = false; + for (let {name, value} of details.responseHeaders) { + info(`Saw header ${name} '${value}'`); + if (name == "Set-Cookie") { + is(value, "dinky=1", "Cookie is correct"); + found = true; + } + } + ok(found, "Saw cookie header"); +} + +add_task(function* filter_urls() { + // First load the URL so that we set cookie foopy=1. + gBrowser.selectedTab = gBrowser.addTab(URL); + yield waitForLoad(); + gBrowser.removeCurrentTab(); + + // Now load with WebRequest set up. + WebRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, null, ["blocking"]); + WebRequest.onResponseStarted.addListener(onResponseStarted, null); + + gBrowser.selectedTab = gBrowser.addTab(URL); + + yield waitForLoad(); + + gBrowser.removeCurrentTab(); + + WebRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders); + WebRequest.onResponseStarted.removeListener(onResponseStarted); + + is(countBefore, 1, "onBeforeSendHeaders hit once"); + is(countAfter, 1, "onResponseStarted hit once"); +}); + +function waitForLoad(browser = gBrowser.selectedBrowser) { + return new Promise(resolve => { + browser.addEventListener("load", function listener() { + browser.removeEventListener("load", listener, true); + resolve(); + }, true); + }); +} diff --git a/toolkit/modules/tests/browser/browser_WebRequest_filtering.js b/toolkit/modules/tests/browser/browser_WebRequest_filtering.js new file mode 100644 index 000000000..a456678c1 --- /dev/null +++ b/toolkit/modules/tests/browser/browser_WebRequest_filtering.js @@ -0,0 +1,118 @@ +"use strict"; + +var { interfaces: Ci, classes: Cc, utils: Cu, results: Cr } = Components; + +var {WebRequest} = Cu.import("resource://gre/modules/WebRequest.jsm", {}); +var {MatchPattern} = Cu.import("resource://gre/modules/MatchPattern.jsm", {}); + +const BASE = "http://example.com/browser/toolkit/modules/tests/browser"; +const URL = BASE + "/file_WebRequest_page2.html"; + +var requested = []; + +function onBeforeRequest(details) +{ + info(`onBeforeRequest ${details.url}`); + if (details.url.startsWith(BASE)) { + requested.push(details.url); + } +} + +var sendHeaders = []; + +function onBeforeSendHeaders(details) +{ + info(`onBeforeSendHeaders ${details.url}`); + if (details.url.startsWith(BASE)) { + sendHeaders.push(details.url); + } +} + +var completed = []; + +function onResponseStarted(details) +{ + if (details.url.startsWith(BASE)) { + completed.push(details.url); + } +} + +const expected_urls = [BASE + "/file_style_good.css", + BASE + "/file_style_bad.css", + BASE + "/file_style_redirect.css"]; + +function removeDupes(list) +{ + let j = 0; + for (let i = 1; i < list.length; i++) { + if (list[i] != list[j]) { + j++; + if (i != j) { + list[j] = list[i]; + } + } + } + list.length = j + 1; +} + +function compareLists(list1, list2, kind) +{ + list1.sort(); + removeDupes(list1); + list2.sort(); + removeDupes(list2); + is(String(list1), String(list2), `${kind} URLs correct`); +} + +add_task(function* filter_urls() { + let filter = {urls: new MatchPattern("*://*/*_style_*")}; + + WebRequest.onBeforeRequest.addListener(onBeforeRequest, filter, ["blocking"]); + WebRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, filter, ["blocking"]); + WebRequest.onResponseStarted.addListener(onResponseStarted, filter); + + gBrowser.selectedTab = gBrowser.addTab(URL); + + yield waitForLoad(); + + gBrowser.removeCurrentTab(); + + compareLists(requested, expected_urls, "requested"); + compareLists(sendHeaders, expected_urls, "sendHeaders"); + compareLists(completed, expected_urls, "completed"); + + WebRequest.onBeforeRequest.removeListener(onBeforeRequest); + WebRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders); + WebRequest.onResponseStarted.removeListener(onResponseStarted); +}); + +add_task(function* filter_types() { + let filter = {types: ["stylesheet"]}; + + WebRequest.onBeforeRequest.addListener(onBeforeRequest, filter, ["blocking"]); + WebRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, filter, ["blocking"]); + WebRequest.onResponseStarted.addListener(onResponseStarted, filter); + + gBrowser.selectedTab = gBrowser.addTab(URL); + + yield waitForLoad(); + + gBrowser.removeCurrentTab(); + + compareLists(requested, expected_urls, "requested"); + compareLists(sendHeaders, expected_urls, "sendHeaders"); + compareLists(completed, expected_urls, "completed"); + + WebRequest.onBeforeRequest.removeListener(onBeforeRequest); + WebRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders); + WebRequest.onResponseStarted.removeListener(onResponseStarted); +}); + +function waitForLoad(browser = gBrowser.selectedBrowser) { + return new Promise(resolve => { + browser.addEventListener("load", function listener() { + browser.removeEventListener("load", listener, true); + resolve(); + }, true); + }); +} diff --git a/toolkit/modules/tests/browser/dummy_page.html b/toolkit/modules/tests/browser/dummy_page.html new file mode 100644 index 000000000..c1c9a4e04 --- /dev/null +++ b/toolkit/modules/tests/browser/dummy_page.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> + +<html> +<body> +<p>Page</p> +</body> +</html> diff --git a/toolkit/modules/tests/browser/file_FinderSample.html b/toolkit/modules/tests/browser/file_FinderSample.html new file mode 100644 index 000000000..e952d1fe9 --- /dev/null +++ b/toolkit/modules/tests/browser/file_FinderSample.html @@ -0,0 +1,824 @@ +<!DOCTYPE html> +<html> +<head> + <title>Childe Roland</title> +</head> +<body> +<h1>"Childe Roland to the Dark Tower Came"</h1><h5>Robert Browning</h5> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>I.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>My first thought was, he lied in every word, +<dl> +<dd>That hoary cripple, with malicious eye</dd> +<dd>Askance to watch the working of his lie</dd> +</dl> +</dd> +<dd>On mine, and mouth scarce able to afford</dd> +<dd>Suppression of the glee that pursed and scored +<dl> +<dd>Its edge, at one more victim gained thereby.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>II.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>What else should he be set for, with his staff? +<dl> +<dd>What, save to waylay with his lies, ensnare</dd> +<dd>All travellers who might find him posted there,</dd> +</dl> +</dd> +<dd>And ask the road? I guessed what skull-like laugh</dd> +<dd>Would break, what crutch 'gin write my epitaph +<dl> +<dd>For pastime in the dusty thoroughfare,</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>III.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>If at his counsel I should turn aside +<dl> +<dd>Into that ominous tract which, all agree,</dd> +<dd>Hides the Dark Tower. Yet acquiescingly</dd> +</dl> +</dd> +<dd>I did turn as he pointed: neither pride</dd> +<dd>Nor hope rekindling at the end descried, +<dl> +<dd>So much as gladness that some end might be.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>IV.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>For, what with my whole world-wide wandering, +<dl> +<dd>What with my search drawn out thro' years, my hope</dd> +<dd>Dwindled into a ghost not fit to cope</dd> +</dl> +</dd> +<dd>With that obstreperous joy success would bring,</dd> +<dd>I hardly tried now to rebuke the spring +<dl> +<dd>My heart made, finding failure in its scope.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>V.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>As when a sick man very near to death +<dl> +<dd>Seems dead indeed, and feels begin and end</dd> +<dd>The tears and takes the farewell of each friend,</dd> +</dl> +</dd> +<dd>And hears one bid the other go, draw breath</dd> +<dd>Freelier outside ("since all is o'er," he saith, +<dl> +<dd>"And the blow fallen no grieving can amend;")</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>VI.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>While some discuss if near the other graves +<dl> +<dd>Be room enough for this, and when a day</dd> +<dd>Suits best for carrying the corpse away,</dd> +</dl> +</dd> +<dd>With care about the banners, scarves and staves:</dd> +<dd>And still the man hears all, and only craves +<dl> +<dd>He may not shame such tender love and stay.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>VII.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>Thus, I had so long suffered in this quest, +<dl> +<dd>Heard failure prophesied so oft, been writ</dd> +<dd>So many times among "The Band" - to wit,</dd> +</dl> +</dd> +<dd>The knights who to the Dark Tower's search addressed</dd> +<dd>Their steps - that just to fail as they, seemed best, +<dl> +<dd>And all the doubt was now—should I be fit?</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>VIII.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>So, quiet as despair, I turned from him, +<dl> +<dd>That hateful cripple, out of his highway</dd> +<dd>Into the path he pointed. All the day</dd> +</dl> +</dd> +<dd>Had been a dreary one at best, and dim</dd> +<dd>Was settling to its close, yet shot one grim +<dl> +<dd>Red leer to see the plain catch its estray.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>IX.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>For mark! no sooner was I fairly found +<dl> +<dd>Pledged to the plain, after a pace or two,</dd> +<dd>Than, pausing to throw backward a last view</dd> +</dl> +</dd> +<dd>O'er the safe road, 'twas gone; grey plain all round:</dd> +<dd>Nothing but plain to the horizon's bound. +<dl> +<dd>I might go on; nought else remained to do.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>X.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>So, on I went. I think I never saw +<dl> +<dd>Such starved ignoble nature; nothing throve:</dd> +<dd>For flowers - as well expect a cedar grove!</dd> +</dl> +</dd> +<dd>But cockle, spurge, according to their law</dd> +<dd>Might propagate their kind, with none to awe, +<dl> +<dd>You'd think; a burr had been a treasure trove.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XI.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>No! penury, inertness and grimace, +<dl> +<dd>In some strange sort, were the land's portion. "See</dd> +<dd>Or shut your eyes," said Nature peevishly,</dd> +</dl> +</dd> +<dd>"It nothing skills: I cannot help my case:</dd> +<dd>'Tis the Last Judgment's fire must cure this place, +<dl> +<dd>Calcine its clods and set my prisoners free."</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XII.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>If there pushed any ragged thistle-stalk +<dl> +<dd>Above its mates, the head was chopped; the bents</dd> +<dd>Were jealous else. What made those holes and rents</dd> +</dl> +</dd> +<dd>In the dock's harsh swarth leaves, bruised as to baulk</dd> +<dd>All hope of greenness? 'tis a brute must walk +<dl> +<dd>Pashing their life out, with a brute's intents.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XIII.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>As for the grass, it grew as scant as hair +<dl> +<dd>In leprosy; thin dry blades pricked the mud</dd> +<dd>Which underneath looked kneaded up with blood.</dd> +</dl> +</dd> +<dd>One stiff blind horse, his every bone a-stare,</dd> +<dd>Stood stupefied, however he came there: +<dl> +<dd>Thrust out past service from the devil's stud!</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XIV.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>Alive? he might be dead for aught I know, +<dl> +<dd>With that red gaunt and colloped neck a-strain,</dd> +<dd>And shut eyes underneath the rusty mane;</dd> +</dl> +</dd> +<dd>Seldom went such grotesqueness with such woe;</dd> +<dd>I never saw a brute I hated so; +<dl> +<dd>He must be wicked to deserve such pain.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XV.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>I shut my eyes and turned them on my heart. +<dl> +<dd>As a man calls for wine before he fights,</dd> +<dd>I asked one draught of earlier, happier sights,</dd> +</dl> +</dd> +<dd>Ere fitly I could hope to play my part.</dd> +<dd>Think first, fight afterwards - the soldier's art: +<dl> +<dd>One taste of the old time sets all to rights.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XVI.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>Not it! I fancied Cuthbert's reddening face +<dl> +<dd>Beneath its garniture of curly gold,</dd> +<dd>Dear fellow, till I almost felt him fold</dd> +</dl> +</dd> +<dd>An arm in mine to fix me to the place</dd> +<dd>That way he used. Alas, one night's disgrace! +<dl> +<dd>Out went my heart's new fire and left it cold.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XVII.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>Giles then, the soul of honour - there he stands +<dl> +<dd>Frank as ten years ago when knighted first.</dd> +<dd>What honest men should dare (he said) he durst.</dd> +</dl> +</dd> +<dd>Good - but the scene shifts - faugh! what hangman hands</dd> +<dd>Pin to his breast a parchment? His own bands +<dl> +<dd>Read it. Poor traitor, spit upon and curst!</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XVIII.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>Better this present than a past like that; +<dl> +<dd>Back therefore to my darkening path again!</dd> +<dd>No sound, no sight as far as eye could strain.</dd> +</dl> +</dd> +<dd>Will the night send a howlet or a bat?</dd> +<dd>I asked: when something on the dismal flat +<dl> +<dd>Came to arrest my thoughts and change their train.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XIX.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>A sudden little river crossed my path +<dl> +<dd>As unexpected as a serpent comes.</dd> +<dd>No sluggish tide congenial to the glooms;</dd> +</dl> +</dd> +<dd>This, as it frothed by, might have been a bath</dd> +<dd>For the fiend's glowing hoof - to see the wrath +<dl> +<dd>Of its black eddy bespate with flakes and spumes.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XX.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>So petty yet so spiteful! All along +<dl> +<dd>Low scrubby alders kneeled down over it;</dd> +<dd>Drenched willows flung them headlong in a fit</dd> +</dl> +</dd> +<dd>Of mute despair, a suicidal throng:</dd> +<dd>The river which had done them all the wrong, +<dl> +<dd>Whate'er that was, rolled by, deterred no whit.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XXI.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>Which, while I forded, - good saints, how I feared +<dl> +<dd>To set my foot upon a dead man's cheek,</dd> +<dd>Each step, or feel the spear I thrust to seek</dd> +</dl> +</dd> +<dd>For hollows, tangled in his hair or beard!</dd> +<dd>—It may have been a water-rat I speared, +<dl> +<dd>But, ugh! it sounded like a baby's shriek.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XXII.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>Glad was I when I reached the other bank. +<dl> +<dd>Now for a better country. Vain presage!</dd> +<dd>Who were the strugglers, what war did they wage,</dd> +</dl> +</dd> +<dd>Whose savage trample thus could pad the dank</dd> +<dd>Soil to a plash? Toads in a poisoned tank, +<dl> +<dd>Or wild cats in a red-hot iron cage—</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XXIII.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>The fight must so have seemed in that fell cirque. +<dl> +<dd>What penned them there, with all the plain to choose?</dd> +<dd>No foot-print leading to that horrid mews,</dd> +</dl> +</dd> +<dd>None out of it. Mad brewage set to work</dd> +<dd>Their brains, no doubt, like galley-slaves the Turk +<dl> +<dd>Pits for his pastime, Christians against Jews.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XXIV.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>And more than that - a furlong on - why, there! +<dl> +<dd>What bad use was that engine for, that wheel,</dd> +<dd>Or brake, not wheel - that harrow fit to reel</dd> +</dl> +</dd> +<dd>Men's bodies out like silk? with all the air</dd> +<dd>Of Tophet's tool, on earth left unaware, +<dl> +<dd>Or brought to sharpen its rusty teeth of steel.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XXV.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>Then came a bit of stubbed ground, once a wood, +<dl> +<dd>Next a marsh, it would seem, and now mere earth</dd> +<dd>Desperate and done with; (so a fool finds mirth,</dd> +</dl> +</dd> +<dd>Makes a thing and then mars it, till his mood</dd> +<dd>Changes and off he goes!) within a rood— +<dl> +<dd>Bog, clay and rubble, sand and stark black dearth.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XXVI.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>Now blotches rankling, coloured gay and grim, +<dl> +<dd>Now patches where some leanness of the soil's</dd> +<dd>Broke into moss or substances like boils;</dd> +</dl> +</dd> +<dd>Then came some palsied oak, a cleft in him</dd> +<dd>Like a distorted mouth that splits its rim +<dl> +<dd>Gaping at death, and dies while it recoils.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XXVII.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>And just as far as ever from the end! +<dl> +<dd>Nought in the distance but the evening, nought</dd> +<dd>To point my footstep further! At the thought,</dd> +</dl> +</dd> +<dd>A great black bird, Apollyon's bosom-friend,</dd> +<dd>Sailed past, nor beat his wide wing dragon-penned +<dl> +<dd>That brushed my cap—perchance the guide I sought.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XXVIII.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>For, looking up, aware I somehow grew, +<dl> +<dd>'Spite of the dusk, the plain had given place</dd> +<dd>All round to mountains - with such name to grace</dd> +</dl> +</dd> +<dd>Mere ugly heights and heaps now stolen in view.</dd> +<dd>How thus they had surprised me, - solve it, you! +<dl> +<dd>How to get from them was no clearer case.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XXIX.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>Yet half I seemed to recognise some trick +<dl> +<dd>Of mischief happened to me, God knows when—</dd> +<dd>In a bad dream perhaps. Here ended, then,</dd> +</dl> +</dd> +<dd>Progress this way. When, in the very nick</dd> +<dd>Of giving up, one time more, came a click +<dl> +<dd>As when a trap shuts - you're inside the den!</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XXX.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>Burningly it came on me all at once, +<dl> +<dd>This was the place! those two hills on the right,</dd> +<dd>Crouched like two bulls locked horn in horn in fight;</dd> +</dl> +</dd> +<dd>While to the left, a tall scalped mountain... Dunce,</dd> +<dd>Dotard, a-dozing at the very nonce, +<dl> +<dd>After a life spent training for the sight!</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XXXI.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>What in the midst lay but the Tower itself? +<dl> +<dd>The round squat turret, blind as the fool's heart</dd> +<dd>Built of brown stone, without a counterpart</dd> +</dl> +</dd> +<dd>In the whole world. The tempest's mocking elf</dd> +<dd>Points to the shipman thus the unseen shelf +<dl> +<dd>He strikes on, only when the timbers start.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XXXII.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>Not see? because of night perhaps? - why, day +<dl> +<dd>Came back again for that! before it left,</dd> +<dd>The dying sunset kindled through a cleft:</dd> +</dl> +</dd> +<dd>The hills, like giants at a hunting, lay</dd> +<dd>Chin upon hand, to see the game at bay,— +<dl> +<dd>"Now stab and end the creature - to the heft!"</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XXXIII.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>Not hear? when noise was everywhere! it tolled +<dl> +<dd>Increasing like a bell. Names in my ears</dd> +<dd>Of all the lost adventurers my peers,—</dd> +</dl> +</dd> +<dd>How such a one was strong, and such was bold,</dd> +<dd>And such was fortunate, yet each of old +<dl> +<dd>Lost, lost! one moment knelled the woe of years.</dd> +</dl> +</dd> +</dl> +<p><br /></p> +<dl> +<dd> +<dl> +<dd> +<dl> +<dd>XXXIV.</dd> +</dl> +</dd> +</dl> +</dd> +<dd>There they stood, ranged along the hillsides, met +<dl> +<dd>To view the last of me, a living frame</dd> +<dd>For one more picture! in a sheet of flame</dd> +</dl> +</dd> +<dd>I saw them and I knew them all. And yet</dd> +<dd>Dauntless the slug-horn to my lips I set, +<dl> +<dd>And blew "<i>Childe Roland to the Dark Tower came.</i>"</dd> +</dl> +</dd> +</dl> +</body> +</html> diff --git a/toolkit/modules/tests/browser/file_WebNavigation_page1.html b/toolkit/modules/tests/browser/file_WebNavigation_page1.html new file mode 100644 index 000000000..1b6869756 --- /dev/null +++ b/toolkit/modules/tests/browser/file_WebNavigation_page1.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML> + +<html> +<body> + +<iframe src="file_WebNavigation_page2.html" width="200" height="200"></iframe> + +</body> +</html> diff --git a/toolkit/modules/tests/browser/file_WebNavigation_page2.html b/toolkit/modules/tests/browser/file_WebNavigation_page2.html new file mode 100644 index 000000000..cc1acc83d --- /dev/null +++ b/toolkit/modules/tests/browser/file_WebNavigation_page2.html @@ -0,0 +1,7 @@ +<!DOCTYPE HTML> + +<html> +<body> + +</body> +</html> diff --git a/toolkit/modules/tests/browser/file_WebNavigation_page3.html b/toolkit/modules/tests/browser/file_WebNavigation_page3.html new file mode 100644 index 000000000..a0a26a2e9 --- /dev/null +++ b/toolkit/modules/tests/browser/file_WebNavigation_page3.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML> + +<html> +<body> + +<a id="elt" href="file_WebNavigation_page3.html#ref">click me</a> + +</body> +</html> diff --git a/toolkit/modules/tests/browser/file_WebRequest_page1.html b/toolkit/modules/tests/browser/file_WebRequest_page1.html new file mode 100644 index 000000000..00a0b9b4b --- /dev/null +++ b/toolkit/modules/tests/browser/file_WebRequest_page1.html @@ -0,0 +1,29 @@ +<!DOCTYPE HTML> + +<html> +<head> +<meta charset="utf-8"> +<link rel="stylesheet" href="file_style_good.css"> +<link rel="stylesheet" href="file_style_bad.css"> +<link rel="stylesheet" href="file_style_redirect.css"> +</head> +<body> + +<div id="test">Sample text</div> + +<img id="img_good" src="file_image_good.png"> +<img id="img_bad" src="file_image_bad.png"> +<img id="img_redirect" src="file_image_redirect.png"> + +<script src="file_script_good.js"></script> +<script src="file_script_bad.js"></script> +<script src="file_script_redirect.js"></script> + +<script src="file_script_xhr.js"></script> + +<script src="nonexistent_script_url.js"></script> + +<iframe src="file_WebRequest_page2.html" width="200" height="200"></iframe> +<iframe src="WebRequest_redirection.sjs" width="200" height="50"></iframe> +</body> +</html> diff --git a/toolkit/modules/tests/browser/file_WebRequest_page2.html b/toolkit/modules/tests/browser/file_WebRequest_page2.html new file mode 100644 index 000000000..b2cf48f9e --- /dev/null +++ b/toolkit/modules/tests/browser/file_WebRequest_page2.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML> + +<html> +<head> +<meta charset="utf-8"> +<link rel="stylesheet" href="file_style_good.css"> +<link rel="stylesheet" href="file_style_bad.css"> +<link rel="stylesheet" href="file_style_redirect.css"> +</head> +<body> + +<div class="test">Sample text</div> + +<img id="img_good" src="file_image_good.png"> +<img id="img_bad" src="file_image_bad.png"> +<img id="img_redirect" src="file_image_redirect.png"> + +<script src="file_script_good.js"></script> +<script src="file_script_bad.js"></script> +<script src="file_script_redirect.js"></script> + +<script src="nonexistent_script_url.js"></script> + +</body> +</html> diff --git a/toolkit/modules/tests/browser/file_image_bad.png b/toolkit/modules/tests/browser/file_image_bad.png Binary files differnew file mode 100644 index 000000000..4c3be5084 --- /dev/null +++ b/toolkit/modules/tests/browser/file_image_bad.png diff --git a/toolkit/modules/tests/browser/file_image_good.png b/toolkit/modules/tests/browser/file_image_good.png Binary files differnew file mode 100644 index 000000000..769c63634 --- /dev/null +++ b/toolkit/modules/tests/browser/file_image_good.png diff --git a/toolkit/modules/tests/browser/file_image_redirect.png b/toolkit/modules/tests/browser/file_image_redirect.png Binary files differnew file mode 100644 index 000000000..4c3be5084 --- /dev/null +++ b/toolkit/modules/tests/browser/file_image_redirect.png diff --git a/toolkit/modules/tests/browser/file_script_bad.js b/toolkit/modules/tests/browser/file_script_bad.js new file mode 100644 index 000000000..90655f136 --- /dev/null +++ b/toolkit/modules/tests/browser/file_script_bad.js @@ -0,0 +1 @@ +window.failure = true; diff --git a/toolkit/modules/tests/browser/file_script_good.js b/toolkit/modules/tests/browser/file_script_good.js new file mode 100644 index 000000000..b128e54a1 --- /dev/null +++ b/toolkit/modules/tests/browser/file_script_good.js @@ -0,0 +1 @@ +window.success = window.success ? window.success + 1 : 1; diff --git a/toolkit/modules/tests/browser/file_script_redirect.js b/toolkit/modules/tests/browser/file_script_redirect.js new file mode 100644 index 000000000..917b5d620 --- /dev/null +++ b/toolkit/modules/tests/browser/file_script_redirect.js @@ -0,0 +1,2 @@ +window.failure = true; + diff --git a/toolkit/modules/tests/browser/file_script_xhr.js b/toolkit/modules/tests/browser/file_script_xhr.js new file mode 100644 index 000000000..bc1f65eae --- /dev/null +++ b/toolkit/modules/tests/browser/file_script_xhr.js @@ -0,0 +1,3 @@ +var request = new XMLHttpRequest(); +request.open("get", "http://example.com/browser/toolkit/modules/tests/browser/xhr_resource", false); +request.send(); diff --git a/toolkit/modules/tests/browser/file_style_bad.css b/toolkit/modules/tests/browser/file_style_bad.css new file mode 100644 index 000000000..8dbc8dc7a --- /dev/null +++ b/toolkit/modules/tests/browser/file_style_bad.css @@ -0,0 +1,3 @@ +#test { + color: green !important; +} diff --git a/toolkit/modules/tests/browser/file_style_good.css b/toolkit/modules/tests/browser/file_style_good.css new file mode 100644 index 000000000..46f9774b5 --- /dev/null +++ b/toolkit/modules/tests/browser/file_style_good.css @@ -0,0 +1,3 @@ +#test { + color: red; +} diff --git a/toolkit/modules/tests/browser/file_style_redirect.css b/toolkit/modules/tests/browser/file_style_redirect.css new file mode 100644 index 000000000..8dbc8dc7a --- /dev/null +++ b/toolkit/modules/tests/browser/file_style_redirect.css @@ -0,0 +1,3 @@ +#test { + color: green !important; +} diff --git a/toolkit/modules/tests/browser/head.js b/toolkit/modules/tests/browser/head.js new file mode 100644 index 000000000..777e087e1 --- /dev/null +++ b/toolkit/modules/tests/browser/head.js @@ -0,0 +1,23 @@ +function removeDupes(list) +{ + let j = 0; + for (let i = 1; i < list.length; i++) { + if (list[i] != list[j]) { + j++; + if (i != j) { + list[j] = list[i]; + } + } + } + list.length = j + 1; +} + +function compareLists(list1, list2, kind) +{ + list1.sort(); + removeDupes(list1); + list2.sort(); + removeDupes(list2); + is(String(list1), String(list2), `${kind} URLs correct`); +} + diff --git a/toolkit/modules/tests/browser/metadata_simple.html b/toolkit/modules/tests/browser/metadata_simple.html new file mode 100644 index 000000000..18089e399 --- /dev/null +++ b/toolkit/modules/tests/browser/metadata_simple.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> + <head> + <title>Test Title</title> + <meta property="description" content="A very simple test page"> + </head> + <body> + Llama. + </body> +</html> diff --git a/toolkit/modules/tests/browser/metadata_titles.html b/toolkit/modules/tests/browser/metadata_titles.html new file mode 100644 index 000000000..bd4201304 --- /dev/null +++ b/toolkit/modules/tests/browser/metadata_titles.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> + <head> + <title>Test Titles</title> + <meta property="description" content="A very simple test page" /> + <meta property="og:title" content="Title" /> + </head> + <body> + Llama. + </body> +</html> diff --git a/toolkit/modules/tests/browser/metadata_titles_fallback.html b/toolkit/modules/tests/browser/metadata_titles_fallback.html new file mode 100644 index 000000000..5b71879b2 --- /dev/null +++ b/toolkit/modules/tests/browser/metadata_titles_fallback.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> + <head> + <meta property="description" content="A very simple test page" /> + <meta property="og:title" content="Title" /> + </head> + <body> + Llama. + </body> +</html> diff --git a/toolkit/modules/tests/browser/testremotepagemanager.html b/toolkit/modules/tests/browser/testremotepagemanager.html new file mode 100644 index 000000000..4303a38f5 --- /dev/null +++ b/toolkit/modules/tests/browser/testremotepagemanager.html @@ -0,0 +1,66 @@ +<!DOCTYPE HTML> + +<html> +<head> +<script type="text/javascript"> +addMessageListener("Ping", function(message) { + sendAsyncMessage("Pong", { + str: message.data.str, + counter: message.data.counter + 1 + }); +}); + +addMessageListener("Ping2", function(message) { + sendAsyncMessage("Pong2", message.data); +}); + +function neverCalled() { + sendAsyncMessage("Pong3"); +} +addMessageListener("Pong3", neverCalled); +removeMessageListener("Pong3", neverCalled); + +function testData(data) { + var response = { + result: true, + status: "All data correctly received" + } + + function compare(prop, expected) { + if (uneval(data[prop]) == uneval(expected)) + return; + if (response.result) + response.status = ""; + response.result = false; + response.status += "Property " + prop + " should have been " + expected + " but was " + data[prop] + "\n"; + } + + compare("integer", 45); + compare("real", 45.78); + compare("str", "foobar"); + compare("array", [1, 2, 3, 5, 27]); + + return response; +} + +addMessageListener("SendData", function(message) { + sendAsyncMessage("ReceivedData", testData(message.data)); +}); + +addMessageListener("SendData2", function(message) { + sendAsyncMessage("ReceivedData2", testData(message.data.data)); +}); + +var cookie = "nom"; +addMessageListener("SetCookie", function(message) { + cookie = message.data.value; +}); + +addMessageListener("GetCookie", function(message) { + sendAsyncMessage("Cookie", { value: cookie }); +}); +</script> +</head> +<body> +</body> +</html> |