diff options
Diffstat (limited to 'toolkit/modules/tests/browser/browser_FinderHighlighter.js')
-rw-r--r-- | toolkit/modules/tests/browser/browser_FinderHighlighter.js | 460 |
1 files changed, 460 insertions, 0 deletions
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); + }); +}); |