/* 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/. */ // This test makes sure that arrowing down and up through the view's results // works correctly with regard to stale results. "use strict"; add_setup(async function () { await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.scotchBonnet.enableOverride", false]], }); }); // This tests the case where queryContext.results.length < the number of rows in // the view, i.e., the view contains stale rows. add_task(async function viewContainsStaleRows() { // Set the remove-stale-rows timer to a very large value, so there's no // possibility it interferes with this test. await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.removeStaleRowsTimeout", 10000]], }); // For the test stability we need a slow provider that ensures the search // doesn't complete too fast. let slowProvider = new UrlbarTestUtils.TestProvider({ results: [], name: "emptySlowProvider", addTimeout: 1000, }); let providersManager = ProvidersManager.getInstanceForSap("urlbar"); providersManager.registerProvider(slowProvider); registerCleanupFunction(() => { providersManager.unregisterProvider(slowProvider); }); await PlacesUtils.history.clear(); await PlacesUtils.bookmarks.eraseEverything(); let maxResults = UrlbarPrefs.get("maxRichResults"); let halfResults = Math.floor(maxResults / 2); // Add enough visits to pages with "xx" in the title to fill up half the view. for (let i = 0; i < halfResults; i++) { await PlacesTestUtils.addVisits({ uri: "http://mochi.test:8888/" + i, title: "xx" + i, }); } // Add enough visits to pages with "x" in the title to fill up the entire // view. for (let i = 0; i < maxResults; i++) { await PlacesTestUtils.addVisits({ uri: "http://example.com/" + i, title: "x" + i, }); } gURLBar.focus(); // Search for "x" and wait for the search to finish. All the "x" results // added above should be in the view. (Actually one fewer will be in the // view due to the heuristic result, but that's not important.) await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "x", fireInputEvent: true, }); // Below we'll do a search for "xx". Get the row that will show the last // result in that search, and await for it to be updated. Assert.ok( !UrlbarTestUtils.getRowAt(window, halfResults).hasAttribute("stale"), "Should not be stale" ); let lastMatchingResultUpdatedPromise = TestUtils.waitForCondition(() => { let row = UrlbarTestUtils.getRowAt(window, halfResults); let { value } = row.result.getDisplayableValueAndHighlights("title"); return value.startsWith("xx"); }, "Wait for the result to be updated"); // Type another "x" so that we search for "xx", but don't wait for the search // to finish. Instead, wait for the row to be updated. EventUtils.synthesizeKey("x"); await lastMatchingResultUpdatedPromise; // Now arrow down. The search, which is still ongoing, will now stop and the // view won't be updated anymore. EventUtils.synthesizeKey("KEY_ArrowDown"); // Wait for the search to stop. info("Waiting for the search to stop... "); let queryContext = await gURLBar.lastQueryContextPromise; // Check stale status of results. Assert.ok( !UrlbarTestUtils.getRowAt(window, halfResults).hasAttribute("stale"), "Should not be stale" ); Assert.ok( UrlbarTestUtils.getRowAt(window, halfResults + 1).hasAttribute("stale"), "Should be stale" ); // The query context for the last search ("xx") should contain only // halfResults + 1 results (+ 1 for the heuristic). Assert.ok(queryContext); Assert.equal(queryContext.results.length, halfResults + 1); // But there should be maxResults visible rows in the view. let items = Array.from( UrlbarTestUtils.getResultsContainer(window).children ).filter(r => BrowserTestUtils.isVisible(r)); Assert.equal(items.length, maxResults); // Arrow down through all the results. After arrowing down from the last "xx" // result, the stale "x" results should be selected. We should *not* enter // the one-off search buttons at that point. for (let i = 1; i < maxResults; i++) { Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), i); let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i); Assert.equal(result.element.row.result.rowIndex, i); EventUtils.synthesizeKey("KEY_ArrowDown"); } // Now the first one-off should be selected. Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), -1); Assert.equal(gURLBar.view.oneOffSearchButtons.selectedButtonIndex, 0); // Arrow back up through all the results. for (let i = maxResults - 1; i >= 0; i--) { EventUtils.synthesizeKey("KEY_ArrowUp"); Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), i); } await UrlbarTestUtils.promisePopupClose(window, () => EventUtils.synthesizeKey("KEY_Escape") ); ProvidersManager.getInstanceForSap("urlbar").unregisterProvider(slowProvider); }); // This tests the case where, before the search finishes, stale results have // been removed and replaced with non-stale results. add_task(async function staleReplacedWithFresh() { // For this test, we need one set of results that's added quickly and another // set that's added after a delay. We do an initial search and wait for both // sets to be added. Then we do another search, but this time only wait for // the fast results to be added, and then we arrow down to stop the search // before the delayed results are added. The order in which things should // happen after the second search goes like this: // // (1) second search // (2) fast results are added // (3) remove-stale-rows timer fires and removes stale rows (the rows from // the delayed set of results from the first search) // (4) we arrow down to stop the search // // We use history for the fast results and a slow search engine for the // delayed results. // // NB: If this test ends up failing, it may be because the remove-stale-rows // timer fires before the history results are added. i.e., steps 2 and 3 // above happen out of order. If that happens, try increasing it. await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.removeStaleRowsTimeout", 1000]], }); await PlacesUtils.history.clear(); await PlacesUtils.bookmarks.eraseEverything(); // Enable search suggestions, and add an engine that returns suggestions on a // delay. await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.suggest.searches", true]], }); let engine = await SearchTestUtils.installOpenSearchEngine({ url: getRootDirectory(gTestPath) + "searchSuggestionEngineSlow.xml", }); let oldDefaultEngine = await SearchService.getDefault(); await SearchService.moveEngine(engine, 0); await SearchService.setDefault(engine, SearchService.CHANGE_REASON.UNKNOWN); let maxResults = UrlbarPrefs.get("maxRichResults"); // Add enough visits to pages with "test" in the title to fill up the entire // view. for (let i = 0; i < maxResults; i++) { await PlacesTestUtils.addVisits({ uri: "http://example.com/" + i, title: "test" + i, }); } gURLBar.focus(); // Search for "tes" and wait for the search to finish. await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "tes", fireInputEvent: true, }); // Sanity check the results. They should be: // // tes -- Search with searchSuggestionEngineSlow [heuristic] // tesfoo [search suggestion] // tesbar [search suggestion] // test9 [history] // test8 [history] // test7 [history] // test6 [history] // test5 [history] // test4 [history] // test3 [history] let count = UrlbarTestUtils.getResultCount(window); Assert.equal(count, maxResults); let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0); Assert.ok(result.heuristic); result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1); Assert.ok(result.searchParams); Assert.equal(result.searchParams.suggestion, "tesfoo"); result = await UrlbarTestUtils.getDetailsOfResultAt(window, 2); Assert.ok(result.searchParams); Assert.equal(result.searchParams.suggestion, "tesbar"); for (let i = 3; i < maxResults; i++) { result = await UrlbarTestUtils.getDetailsOfResultAt(window, i); Assert.equal(result.type, UrlbarShared.RESULT_TYPE.URL); Assert.equal(result.title, "test" + (maxResults - i + 2)); } // Below we'll do a search for "test" *but* not wait for the two search // suggestion results to be added. We'll only wait for the history results to // be added. To determine when the history results are added, use a mutation // listener on the node containing the rows, and wait until the title of the // next-to-last row is "test2". At that point, the results should be: // // test -- Search with searchSuggestionEngineSlow // test9 // test8 // test7 // test6 // test5 // test4 // test3 // test2 // test1 let mutationPromise = new Promise(resolve => { let observer = new MutationObserver(() => { let row = UrlbarTestUtils.getRowAt(window, maxResults - 2); if (row && row._elements.get("title").textContent == "test2") { observer.disconnect(); resolve(); } }); observer.observe(UrlbarTestUtils.getResultsContainer(window), { subtree: true, characterData: true, childList: true, attributes: true, }); }); // Now type a "t" so that we search for "test", but only wait for history // results to be added, as described above. EventUtils.synthesizeKey("t"); info("Waiting for the 'test2' row... "); await mutationPromise; // Now arrow down. The search, which is still ongoing, will now stop and the // view won't be updated anymore. EventUtils.synthesizeKey("KEY_ArrowDown"); // Wait for the search to stop. info("Waiting for the search to stop... "); await gURLBar.lastQueryContextPromise; // Sanity check the results. They should be as described above. count = UrlbarTestUtils.getResultCount(window); Assert.equal(count, maxResults); result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0); Assert.ok(result.heuristic); Assert.equal(result.element.row.result.rowIndex, 0); for (let i = 1; i < maxResults; i++) { result = await UrlbarTestUtils.getDetailsOfResultAt(window, i); Assert.equal(result.type, UrlbarShared.RESULT_TYPE.URL); Assert.equal(result.title, "test" + (maxResults - i)); Assert.equal(result.element.row.result.rowIndex, i); } // Arrow down through all the results. After arrowing down from "test3", we // should continue on to "test2". We should *not* enter the one-off search // buttons at that point. for (let i = 1; i < maxResults; i++) { Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), i); EventUtils.synthesizeKey("KEY_ArrowDown"); } // Now the first one-off should be selected. Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), -1); Assert.equal(gURLBar.view.oneOffSearchButtons.selectedButtonIndex, 0); // Arrow back up through all the results. for (let i = maxResults - 1; i >= 0; i--) { EventUtils.synthesizeKey("KEY_ArrowUp"); Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), i); } await UrlbarTestUtils.promisePopupClose(window, () => EventUtils.synthesizeKey("KEY_Escape") ); await SpecialPowers.popPrefEnv(); await SearchService.setDefault( oldDefaultEngine, SearchService.CHANGE_REASON.UNKNOWN ); });