diff options
Diffstat (limited to 'toolkit/content/tests/browser/browser_findbar.js')
-rw-r--r-- | toolkit/content/tests/browser/browser_findbar.js | 523 |
1 files changed, 523 insertions, 0 deletions
diff --git a/toolkit/content/tests/browser/browser_findbar.js b/toolkit/content/tests/browser/browser_findbar.js new file mode 100644 index 0000000000..b6f116245e --- /dev/null +++ b/toolkit/content/tests/browser/browser_findbar.js @@ -0,0 +1,523 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +ChromeUtils.import("resource://gre/modules/Timer.jsm", this); + +const TEST_PAGE_URI = "data:text/html;charset=utf-8,The letter s."; +// Using 'javascript' schema to bypass E10SUtils.canLoadURIInRemoteType, because +// it does not allow 'data:' URI to be loaded in the parent process. +const E10S_PARENT_TEST_PAGE_URI = + getRootDirectory(gTestPath) + "file_empty.html"; +const TEST_PAGE_URI_WITHIFRAME = + "https://example.com/browser/toolkit/content/tests/browser/file_findinframe.html"; + +/** + * Makes sure that the findbar hotkeys (' and /) event listeners + * are added to the system event group and do not get blocked + * by calling stopPropagation on a keypress event on a page. + */ +add_task(async function test_hotkey_event_propagation() { + info("Ensure hotkeys are not affected by stopPropagation."); + + // Opening new tab + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_PAGE_URI + ); + let browser = gBrowser.getBrowserForTab(tab); + let findbar = await gBrowser.getFindBar(); + + // Pressing these keys open the findbar. + const HOTKEYS = ["/", "'"]; + + // Checking if findbar appears when any hotkey is pressed. + for (let key of HOTKEYS) { + is(findbar.hidden, true, "Findbar is hidden now."); + gBrowser.selectedTab = tab; + await SimpleTest.promiseFocus(gBrowser.selectedBrowser); + await BrowserTestUtils.sendChar(key, browser); + is(findbar.hidden, false, "Findbar should not be hidden."); + await closeFindbarAndWait(findbar); + } + + // Stop propagation for all keyboard events. + await SpecialPowers.spawn(browser, [], () => { + const stopPropagation = e => { + e.stopImmediatePropagation(); + }; + let window = content.document.defaultView; + window.addEventListener("keydown", stopPropagation); + window.addEventListener("keypress", stopPropagation); + window.addEventListener("keyup", stopPropagation); + }); + + // Checking if findbar still appears when any hotkey is pressed. + for (let key of HOTKEYS) { + is(findbar.hidden, true, "Findbar is hidden now."); + gBrowser.selectedTab = tab; + await SimpleTest.promiseFocus(gBrowser.selectedBrowser); + await BrowserTestUtils.sendChar(key, browser); + is(findbar.hidden, false, "Findbar should not be hidden."); + await closeFindbarAndWait(findbar); + } + + gBrowser.removeTab(tab); +}); + +add_task(async function test_not_found() { + info("Check correct 'Phrase not found' on new tab"); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_PAGE_URI + ); + + // Search for the first word. + await promiseFindFinished("--- THIS SHOULD NEVER MATCH ---", false); + let findbar = gBrowser.getCachedFindBar(); + is( + findbar._findStatusDesc.textContent, + findbar._notFoundStr, + "Findbar status text should be 'Phrase not found'" + ); + + gBrowser.removeTab(tab); +}); + +add_task(async function test_found() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_PAGE_URI + ); + + // Search for a string that WILL be found, with 'Highlight All' on + await promiseFindFinished("S", true); + ok( + !gBrowser.getCachedFindBar()._findStatusDesc.textContent, + "Findbar status should be empty" + ); + + gBrowser.removeTab(tab); +}); + +// Setting first findbar to case-sensitive mode should not affect +// new tab find bar. +add_task(async function test_tabwise_case_sensitive() { + let tab1 = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_PAGE_URI + ); + let findbar1 = await gBrowser.getFindBar(); + + let tab2 = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_PAGE_URI + ); + let findbar2 = await gBrowser.getFindBar(); + + // Toggle case sensitivity for first findbar + findbar1.getElement("find-case-sensitive").click(); + + gBrowser.selectedTab = tab1; + + // Not found for first tab. + await promiseFindFinished("S", true); + is( + findbar1._findStatusDesc.textContent, + findbar1._notFoundStr, + "Findbar status text should be 'Phrase not found'" + ); + + gBrowser.selectedTab = tab2; + + // But it didn't affect the second findbar. + await promiseFindFinished("S", true); + ok(!findbar2._findStatusDesc.textContent, "Findbar status should be empty"); + + gBrowser.removeTab(tab1); + gBrowser.removeTab(tab2); +}); + +/** + * Navigating from a web page (for example mozilla.org) to an internal page + * (like about:addons) might trigger a change of browser's remoteness. + * 'Remoteness change' means that rendering page content moves from child + * process into the parent process or the other way around. + * This test ensures that findbar properly handles such a change. + */ +add_task(async function test_reinitialization_at_remoteness_change() { + // This test only makes sence in e10s evironment. + if (!gMultiProcessBrowser) { + info("Skipping this test because of non-e10s environment."); + return; + } + + info("Ensure findbar re-initialization at remoteness change."); + + // Load a remote page and trigger findbar construction. + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_PAGE_URI + ); + let browser = gBrowser.getBrowserForTab(tab); + let findbar = await gBrowser.getFindBar(); + + // Findbar should operate normally. + await promiseFindFinished("z", false); + is( + findbar._findStatusDesc.textContent, + findbar._notFoundStr, + "Findbar status text should be 'Phrase not found'" + ); + + await promiseFindFinished("s", false); + ok(!findbar._findStatusDesc.textContent, "Findbar status should be empty"); + + // Moving browser into the parent process and reloading sample data. + ok(browser.isRemoteBrowser, "Browser should be remote now."); + await promiseRemotenessChange(tab, false); + let docLoaded = BrowserTestUtils.browserLoaded( + browser, + false, + E10S_PARENT_TEST_PAGE_URI + ); + BrowserTestUtils.loadURI(browser, E10S_PARENT_TEST_PAGE_URI); + await docLoaded; + ok(!browser.isRemoteBrowser, "Browser should not be remote any more."); + browser.contentDocument.body.append("The letter s."); + browser.contentDocument.body.clientHeight; // Force flush. + + // Findbar should keep operating normally after remoteness change. + await promiseFindFinished("z", false); + is( + findbar._findStatusDesc.textContent, + findbar._notFoundStr, + "Findbar status text should be 'Phrase not found'" + ); + + await promiseFindFinished("s", false); + ok(!findbar._findStatusDesc.textContent, "Findbar status should be empty"); + + BrowserTestUtils.removeTab(tab); +}); + +/** + * Ensure that the initial typed characters aren't lost immediately after + * opening the find bar. + */ +add_task(async function e10sLostKeys() { + // This test only makes sence in e10s evironment. + if (!gMultiProcessBrowser) { + info("Skipping this test because of non-e10s environment."); + return; + } + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_PAGE_URI + ); + + ok(!gFindBarInitialized, "findbar isn't initialized yet"); + + await gFindBarPromise; + let findBar = gFindBar; + let initialValue = findBar._findField.value; + + await EventUtils.synthesizeAndWaitKey( + "F", + { accelKey: true }, + window, + null, + () => { + // We can't afford to wait for the promise to resolve, by then the + // find bar is visible and focused, so sending characters to the + // content browser wouldn't work. + isnot( + document.activeElement, + findBar._findField, + "findbar is not yet focused" + ); + EventUtils.synthesizeKey("a"); + EventUtils.synthesizeKey("b"); + EventUtils.synthesizeKey("c"); + is( + findBar._findField.value, + initialValue, + "still has initial find query" + ); + } + ); + + await BrowserTestUtils.waitForCondition( + () => findBar._findField.value.length == 3 + ); + is(document.activeElement, findBar._findField, "findbar is now focused"); + is(findBar._findField.value, "abc", "abc fully entered as find query"); + + BrowserTestUtils.removeTab(tab); +}); + +/** + * This test makes sure that keyboard operations still occur + * after the findbar is opened and closed. + */ +add_task(async function test_open_and_close_keys() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "data:text/html,<body style='height: 5000px;'>Hello There</body>" + ); + + await gFindBarPromise; + let findBar = gFindBar; + + is(findBar.hidden, true, "Findbar is hidden now."); + let openedPromise = BrowserTestUtils.waitForEvent(findBar, "findbaropen"); + await EventUtils.synthesizeKey("f", { accelKey: true }); + await openedPromise; + + is(findBar.hidden, false, "Findbar should not be hidden."); + + let closedPromise = BrowserTestUtils.waitForEvent(findBar, "findbarclose"); + await EventUtils.synthesizeKey("KEY_Escape"); + await closedPromise; + + let scrollPromise = BrowserTestUtils.waitForContentEvent( + tab.linkedBrowser, + "scroll" + ); + await EventUtils.synthesizeKey("KEY_ArrowDown"); + await scrollPromise; + + let scrollPosition = await SpecialPowers.spawn( + tab.linkedBrowser, + [], + async function() { + return content.document.body.scrollTop; + } + ); + + ok(scrollPosition > 0, "Scrolled ok to " + scrollPosition); + + BrowserTestUtils.removeTab(tab); +}); + +// This test loads an editable area within an iframe and then +// performs a search. Focusing the editable area should still +// allow keyboard events to be received. +add_task(async function test_hotkey_insubframe() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_PAGE_URI_WITHIFRAME + ); + + await gFindBarPromise; + let findBar = gFindBar; + + // Focus the editable area within the frame. + let browser = gBrowser.selectedBrowser; + let frameBC = browser.browsingContext.children[0]; + await SpecialPowers.spawn(frameBC, [], async () => { + content.document.body.focus(); + content.document.defaultView.focus(); + }); + + // Start a find and wait for the findbar to open. + let findBarOpenPromise = BrowserTestUtils.waitForEvent( + gBrowser, + "findbaropen" + ); + EventUtils.synthesizeKey("f", { accelKey: true }); + await findBarOpenPromise; + + // Opening the findbar would have focused the find textbox. + // Focus the editable area again. + let cursorPos = await SpecialPowers.spawn(frameBC, [], async () => { + content.document.body.focus(); + content.document.defaultView.focus(); + return content.getSelection().anchorOffset; + }); + is(cursorPos, 0, "initial cursor position"); + + // Try moving the caret. + await BrowserTestUtils.synthesizeKey("KEY_ArrowRight", {}, frameBC); + + cursorPos = await SpecialPowers.spawn(frameBC, [], async () => { + return content.getSelection().anchorOffset; + }); + is(cursorPos, 1, "cursor moved"); + + await closeFindbarAndWait(findBar); + gBrowser.removeTab(tab); +}); + +/** + * Reloading a page should use the same match case / whole word + * state for the search. + */ +add_task(async function test_preservestate_on_reload() { + for (let stateChange of ["case-sensitive", "entire-word"]) { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "data:text/html,<p>There is a cat named Theo in the kitchen with another cat named Catherine. The two of them are thirsty." + ); + + // Start a find and wait for the findbar to open. + let findBarOpenPromise = BrowserTestUtils.waitForEvent( + gBrowser, + "findbaropen" + ); + EventUtils.synthesizeKey("f", { accelKey: true }); + await findBarOpenPromise; + + let isEntireWord = stateChange == "entire-word"; + + let findbar = await gBrowser.getFindBar(); + + // Find some text. + let promiseMatches = promiseGetMatchCount(findbar); + await promiseFindFinished("The", true); + + let matches = await promiseMatches; + is(matches.current, 1, "Correct match position " + stateChange); + is(matches.total, 7, "Correct number of matches " + stateChange); + + // Turn on the case sensitive or entire word option. + findbar.getElement("find-" + stateChange).click(); + + promiseMatches = promiseGetMatchCount(findbar); + gFindBar.onFindAgainCommand(); + matches = await promiseMatches; + is( + matches.current, + 2, + "Correct match position after state change matches " + stateChange + ); + is( + matches.total, + isEntireWord ? 2 : 3, + "Correct number after state change matches " + stateChange + ); + + // Reload the page. + let loadedPromise = BrowserTestUtils.browserLoaded( + gBrowser.selectedBrowser, + true + ); + gBrowser.reload(); + await loadedPromise; + + // Perform a find again. The state should be preserved. + promiseMatches = promiseGetMatchCount(findbar); + gFindBar.onFindAgainCommand(); + matches = await promiseMatches; + is( + matches.current, + 1, + "Correct match position after reload and find again " + stateChange + ); + is( + matches.total, + isEntireWord ? 2 : 3, + "Correct number of matches after reload and find again " + stateChange + ); + + // Turn off the case sensitive or entire word option and find again. + findbar.getElement("find-" + stateChange).click(); + + promiseMatches = promiseGetMatchCount(findbar); + gFindBar.onFindAgainCommand(); + matches = await promiseMatches; + + is( + matches.current, + isEntireWord ? 4 : 2, + "Correct match position after reload and find again reset " + stateChange + ); + is( + matches.total, + 7, + "Correct number of matches after reload and find again reset " + + stateChange + ); + + findbar.clear(); + await closeFindbarAndWait(findbar); + + gBrowser.removeTab(tab); + } +}); + +async function promiseFindFinished(searchText, highlightOn) { + let findbar = await gBrowser.getFindBar(); + findbar.startFind(findbar.FIND_NORMAL); + let highlightElement = findbar.getElement("highlight"); + if (highlightElement.checked != highlightOn) { + highlightElement.click(); + } + return new Promise(resolve => { + executeSoon(() => { + findbar._findField.value = searchText; + + let resultListener; + // When highlighting is on the finder sends a second "FOUND" message after + // the search wraps. This causes timing problems with e10s. waitMore + // forces foundOrTimeout wait for the second "FOUND" message before + // resolving the promise. + let waitMore = highlightOn; + let findTimeout = setTimeout(() => foundOrTimedout(null), 2000); + let foundOrTimedout = function(aData) { + if (aData !== null && waitMore) { + waitMore = false; + return; + } + if (aData === null) { + info("Result listener not called, timeout reached."); + } + clearTimeout(findTimeout); + findbar.browser.finder.removeResultListener(resultListener); + resolve(); + }; + + resultListener = { + onFindResult: foundOrTimedout, + onCurrentSelection() {}, + onMatchesCountResult() {}, + onHighlightFinished() {}, + }; + findbar.browser.finder.addResultListener(resultListener); + findbar._find(); + }); + }); +} + +function promiseGetMatchCount(findbar) { + return new Promise(resolve => { + let resultListener = { + onFindResult() {}, + onCurrentSelection() {}, + onHighlightFinished() {}, + onMatchesCountResult(response) { + if (response.total > 0) { + findbar.browser.finder.removeResultListener(resultListener); + resolve(response); + } + }, + }; + findbar.browser.finder.addResultListener(resultListener); + }); +} + +function promiseRemotenessChange(tab, shouldBeRemote) { + return new Promise(resolve => { + let browser = gBrowser.getBrowserForTab(tab); + tab.addEventListener( + "TabRemotenessChange", + function() { + resolve(); + }, + { once: true } + ); + let remoteType = shouldBeRemote + ? E10SUtils.DEFAULT_REMOTE_TYPE + : E10SUtils.NOT_REMOTE; + gBrowser.updateBrowserRemoteness(browser, { remoteType }); + }); +} |