michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: * http://creativecommons.org/publicdomain/zero/1.0/ michael@0: */ michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Promise", michael@0: "resource://gre/modules/Promise.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Task", michael@0: "resource://gre/modules/Task.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "AboutHomeUtils", michael@0: "resource:///modules/AboutHome.jsm"); michael@0: michael@0: let gRightsVersion = Services.prefs.getIntPref("browser.rights.version"); michael@0: michael@0: registerCleanupFunction(function() { michael@0: // Ensure we don't pollute prefs for next tests. michael@0: Services.prefs.clearUserPref("network.cookies.cookieBehavior"); michael@0: Services.prefs.clearUserPref("network.cookie.lifetimePolicy"); michael@0: Services.prefs.clearUserPref("browser.rights.override"); michael@0: Services.prefs.clearUserPref("browser.rights." + gRightsVersion + ".shown"); michael@0: }); michael@0: michael@0: let gTests = [ michael@0: michael@0: { michael@0: desc: "Check that clearing cookies does not clear storage", michael@0: setup: function () michael@0: { michael@0: Cc["@mozilla.org/observer-service;1"] michael@0: .getService(Ci.nsIObserverService) michael@0: .notifyObservers(null, "cookie-changed", "cleared"); michael@0: }, michael@0: run: function (aSnippetsMap) michael@0: { michael@0: isnot(aSnippetsMap.get("snippets-last-update"), null, michael@0: "snippets-last-update should have a value"); michael@0: } michael@0: }, michael@0: michael@0: { michael@0: desc: "Check default snippets are shown", michael@0: setup: function () { }, michael@0: run: function () michael@0: { michael@0: let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; michael@0: let snippetsElt = doc.getElementById("snippets"); michael@0: ok(snippetsElt, "Found snippets element") michael@0: is(snippetsElt.getElementsByTagName("span").length, 1, michael@0: "A default snippet is present."); michael@0: } michael@0: }, michael@0: michael@0: { michael@0: desc: "Check default snippets are shown if snippets are invalid xml", michael@0: setup: function (aSnippetsMap) michael@0: { michael@0: // This must be some incorrect xhtml code. michael@0: aSnippetsMap.set("snippets", "
"); michael@0: }, michael@0: run: function (aSnippetsMap) michael@0: { michael@0: let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; michael@0: michael@0: let snippetsElt = doc.getElementById("snippets"); michael@0: ok(snippetsElt, "Found snippets element"); michael@0: is(snippetsElt.getElementsByTagName("span").length, 1, michael@0: "A default snippet is present."); michael@0: michael@0: aSnippetsMap.delete("snippets"); michael@0: } michael@0: }, michael@0: michael@0: { michael@0: desc: "Check that search engine logo has alt text", michael@0: setup: function () { }, michael@0: run: function () michael@0: { michael@0: let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; michael@0: michael@0: let searchEngineLogoElt = doc.getElementById("searchEngineLogo"); michael@0: ok(searchEngineLogoElt, "Found search engine logo"); michael@0: michael@0: let altText = searchEngineLogoElt.alt; michael@0: ok(typeof altText == "string" && altText.length > 0, michael@0: "Search engine logo's alt text is a nonempty string"); michael@0: michael@0: isnot(altText, "undefined", michael@0: "Search engine logo's alt text shouldn't be the string 'undefined'"); michael@0: } michael@0: }, michael@0: michael@0: // Disabled on Linux for intermittent issues with FHR, see Bug 945667. michael@0: { michael@0: desc: "Check that performing a search fires a search event and records to " + michael@0: "Firefox Health Report.", michael@0: setup: function () { }, michael@0: run: function () { michael@0: // Skip this test on Linux. michael@0: if (navigator.platform.indexOf("Linux") == 0) { return; } michael@0: michael@0: try { michael@0: let cm = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager); michael@0: cm.getCategoryEntry("healthreport-js-provider-default", "SearchesProvider"); michael@0: } catch (ex) { michael@0: // Health Report disabled, or no SearchesProvider. michael@0: return Promise.resolve(); michael@0: } michael@0: michael@0: let numSearchesBefore = 0; michael@0: let searchEventDeferred = Promise.defer(); michael@0: let doc = gBrowser.contentDocument; michael@0: let engineName = doc.documentElement.getAttribute("searchEngineName"); michael@0: michael@0: doc.addEventListener("AboutHomeSearchEvent", function onSearch(e) { michael@0: let data = JSON.parse(e.detail); michael@0: is(data.engineName, engineName, "Detail is search engine name"); michael@0: michael@0: // We use executeSoon() to ensure that this code runs after the michael@0: // count has been updated in browser.js, since it uses the same michael@0: // event. michael@0: executeSoon(function () { michael@0: getNumberOfSearches(engineName).then(num => { michael@0: is(num, numSearchesBefore + 1, "One more search recorded."); michael@0: searchEventDeferred.resolve(); michael@0: }); michael@0: }); michael@0: }, true, true); michael@0: michael@0: // Get the current number of recorded searches. michael@0: let searchStr = "a search"; michael@0: getNumberOfSearches(engineName).then(num => { michael@0: numSearchesBefore = num; michael@0: michael@0: info("Perform a search."); michael@0: doc.getElementById("searchText").value = searchStr; michael@0: doc.getElementById("searchSubmit").click(); michael@0: }); michael@0: michael@0: let expectedURL = Services.search.currentEngine. michael@0: getSubmission(searchStr, null, "homepage"). michael@0: uri.spec; michael@0: let loadPromise = waitForDocLoadAndStopIt(expectedURL); michael@0: michael@0: return Promise.all([searchEventDeferred.promise, loadPromise]); michael@0: } michael@0: }, michael@0: michael@0: { michael@0: desc: "Check snippets map is cleared if cached version is old", michael@0: setup: function (aSnippetsMap) michael@0: { michael@0: aSnippetsMap.set("snippets", "test"); michael@0: aSnippetsMap.set("snippets-cached-version", 0); michael@0: }, michael@0: run: function (aSnippetsMap) michael@0: { michael@0: ok(!aSnippetsMap.has("snippets"), "snippets have been properly cleared"); michael@0: ok(!aSnippetsMap.has("snippets-cached-version"), michael@0: "cached-version has been properly cleared"); michael@0: } michael@0: }, michael@0: michael@0: { michael@0: desc: "Check cached snippets are shown if cached version is current", michael@0: setup: function (aSnippetsMap) michael@0: { michael@0: aSnippetsMap.set("snippets", "test"); michael@0: }, michael@0: run: function (aSnippetsMap) michael@0: { michael@0: let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; michael@0: michael@0: let snippetsElt = doc.getElementById("snippets"); michael@0: ok(snippetsElt, "Found snippets element"); michael@0: is(snippetsElt.innerHTML, "test", "Cached snippet is present."); michael@0: michael@0: is(aSnippetsMap.get("snippets"), "test", "snippets still cached"); michael@0: is(aSnippetsMap.get("snippets-cached-version"), michael@0: AboutHomeUtils.snippetsVersion, michael@0: "cached-version is correct"); michael@0: ok(aSnippetsMap.has("snippets-last-update"), "last-update still exists"); michael@0: } michael@0: }, michael@0: michael@0: { michael@0: desc: "Check if the 'Know Your Rights default snippet is shown when 'browser.rights.override' pref is set", michael@0: beforeRun: function () michael@0: { michael@0: Services.prefs.setBoolPref("browser.rights.override", false); michael@0: }, michael@0: setup: function () { }, michael@0: run: function (aSnippetsMap) michael@0: { michael@0: let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; michael@0: let showRights = AboutHomeUtils.showKnowYourRights; michael@0: michael@0: ok(showRights, "AboutHomeUtils.showKnowYourRights should be TRUE"); michael@0: michael@0: let snippetsElt = doc.getElementById("snippets"); michael@0: ok(snippetsElt, "Found snippets element"); michael@0: is(snippetsElt.getElementsByTagName("a")[0].href, "about:rights", "Snippet link is present."); michael@0: michael@0: Services.prefs.clearUserPref("browser.rights.override"); michael@0: } michael@0: }, michael@0: michael@0: { michael@0: desc: "Check if the 'Know Your Rights default snippet is NOT shown when 'browser.rights.override' pref is NOT set", michael@0: beforeRun: function () michael@0: { michael@0: Services.prefs.setBoolPref("browser.rights.override", true); michael@0: }, michael@0: setup: function () { }, michael@0: run: function (aSnippetsMap) michael@0: { michael@0: let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; michael@0: let rightsData = AboutHomeUtils.knowYourRightsData; michael@0: michael@0: ok(!rightsData, "AboutHomeUtils.knowYourRightsData should be FALSE"); michael@0: michael@0: let snippetsElt = doc.getElementById("snippets"); michael@0: ok(snippetsElt, "Found snippets element"); michael@0: ok(snippetsElt.getElementsByTagName("a")[0].href != "about:rights", "Snippet link should not point to about:rights."); michael@0: michael@0: Services.prefs.clearUserPref("browser.rights.override"); michael@0: } michael@0: }, michael@0: michael@0: { michael@0: desc: "Check that the search UI/ action is updated when the search engine is changed", michael@0: setup: function() {}, michael@0: run: function() michael@0: { michael@0: let currEngine = Services.search.currentEngine; michael@0: let unusedEngines = [].concat(Services.search.getVisibleEngines()).filter(x => x != currEngine); michael@0: let searchbar = document.getElementById("searchbar"); michael@0: michael@0: function checkSearchUI(engine) { michael@0: let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; michael@0: let searchText = doc.getElementById("searchText"); michael@0: let logoElt = doc.getElementById("searchEngineLogo"); michael@0: let engineName = doc.documentElement.getAttribute("searchEngineName"); michael@0: michael@0: is(engineName, engine.name, "Engine name should've been updated"); michael@0: michael@0: if (!logoElt.parentNode.hidden) { michael@0: is(logoElt.alt, engineName, "Alt text of logo image should match search engine name") michael@0: } else { michael@0: is(searchText.placeholder, engineName, "Placeholder text should match search engine name"); michael@0: } michael@0: } michael@0: // Do a sanity check that all attributes are correctly set to begin with michael@0: checkSearchUI(currEngine); michael@0: michael@0: let deferred = Promise.defer(); michael@0: promiseBrowserAttributes(gBrowser.selectedTab).then(function() { michael@0: // Test if the update propagated michael@0: checkSearchUI(unusedEngines[0]); michael@0: searchbar.currentEngine = currEngine; michael@0: deferred.resolve(); michael@0: }); michael@0: michael@0: // The following cleanup function will set currentEngine back to the previous michael@0: // engine if we fail to do so above. michael@0: registerCleanupFunction(function() { michael@0: searchbar.currentEngine = currEngine; michael@0: }); michael@0: // Set the current search engine to an unused one michael@0: searchbar.currentEngine = unusedEngines[0]; michael@0: searchbar.select(); michael@0: return deferred.promise; michael@0: } michael@0: }, michael@0: michael@0: { michael@0: desc: "Check POST search engine support", michael@0: setup: function() {}, michael@0: run: function() michael@0: { michael@0: let deferred = Promise.defer(); michael@0: let currEngine = Services.search.defaultEngine; michael@0: let searchObserver = function search_observer(aSubject, aTopic, aData) { michael@0: let engine = aSubject.QueryInterface(Ci.nsISearchEngine); michael@0: info("Observer: " + aData + " for " + engine.name); michael@0: michael@0: if (aData != "engine-added") michael@0: return; michael@0: michael@0: if (engine.name != "POST Search") michael@0: return; michael@0: michael@0: // Ready to execute the tests! michael@0: let needle = "Search for something awesome."; michael@0: let document = gBrowser.selectedTab.linkedBrowser.contentDocument; michael@0: let searchText = document.getElementById("searchText"); michael@0: michael@0: // We're about to change the search engine. Once the change has michael@0: // propagated to the about:home content, we want to perform a search. michael@0: let mutationObserver = new MutationObserver(function (mutations) { michael@0: for (let mutation of mutations) { michael@0: if (mutation.attributeName == "searchEngineName") { michael@0: searchText.value = needle; michael@0: searchText.focus(); michael@0: EventUtils.synthesizeKey("VK_RETURN", {}); michael@0: } michael@0: } michael@0: }); michael@0: mutationObserver.observe(document.documentElement, { attributes: true }); michael@0: michael@0: // Change the search engine, triggering the observer above. michael@0: Services.search.defaultEngine = engine; michael@0: michael@0: registerCleanupFunction(function() { michael@0: mutationObserver.disconnect(); michael@0: Services.search.removeEngine(engine); michael@0: Services.search.defaultEngine = currEngine; michael@0: }); michael@0: michael@0: michael@0: // When the search results load, check them for correctness. michael@0: waitForLoad(function() { michael@0: let loadedText = gBrowser.contentDocument.body.textContent; michael@0: ok(loadedText, "search page loaded"); michael@0: is(loadedText, "searchterms=" + escape(needle.replace(/\s/g, "+")), michael@0: "Search text should arrive correctly"); michael@0: deferred.resolve(); michael@0: }); michael@0: }; michael@0: Services.obs.addObserver(searchObserver, "browser-search-engine-modified", false); michael@0: registerCleanupFunction(function () { michael@0: Services.obs.removeObserver(searchObserver, "browser-search-engine-modified"); michael@0: }); michael@0: Services.search.addEngine("http://test:80/browser/browser/base/content/test/general/POSTSearchEngine.xml", michael@0: Ci.nsISearchEngine.DATA_XML, null, false); michael@0: return deferred.promise; michael@0: } michael@0: }, michael@0: michael@0: { michael@0: desc: "Make sure that a page can't imitate about:home", michael@0: setup: function () { }, michael@0: run: function (aSnippetsMap) michael@0: { michael@0: let deferred = Promise.defer(); michael@0: michael@0: let browser = gBrowser.selectedTab.linkedBrowser; michael@0: waitForLoad(() => { michael@0: let button = browser.contentDocument.getElementById("settings"); michael@0: ok(button, "Found settings button in test page"); michael@0: button.click(); michael@0: michael@0: // It may take a few turns of the event loop before the window michael@0: // is displayed, so we wait. michael@0: function check(n) { michael@0: let win = Services.wm.getMostRecentWindow("Browser:Preferences"); michael@0: ok(!win, "Preferences window not showing"); michael@0: if (win) { michael@0: win.close(); michael@0: } michael@0: michael@0: if (n > 0) { michael@0: executeSoon(() => check(n-1)); michael@0: } else { michael@0: deferred.resolve(); michael@0: } michael@0: } michael@0: michael@0: check(5); michael@0: }); michael@0: michael@0: browser.loadURI("https://example.com/browser/browser/base/content/test/general/test_bug959531.html"); michael@0: return deferred.promise; michael@0: } michael@0: }, michael@0: michael@0: ]; michael@0: michael@0: function test() michael@0: { michael@0: waitForExplicitFinish(); michael@0: requestLongerTimeout(2); michael@0: ignoreAllUncaughtExceptions(); michael@0: michael@0: Task.spawn(function () { michael@0: for (let test of gTests) { michael@0: info(test.desc); michael@0: michael@0: if (test.beforeRun) michael@0: yield test.beforeRun(); michael@0: michael@0: // Create a tab to run the test. michael@0: let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank"); michael@0: michael@0: // Add an event handler to modify the snippets map once it's ready. michael@0: let snippetsPromise = promiseSetupSnippetsMap(tab, test.setup); michael@0: michael@0: // Start loading about:home and wait for it to complete. michael@0: yield promiseTabLoadEvent(tab, "about:home", "AboutHomeLoadSnippetsSucceeded"); michael@0: michael@0: // This promise should already be resolved since the page is done, michael@0: // but we still want to get the snippets map out of it. michael@0: let snippetsMap = yield snippetsPromise; michael@0: michael@0: info("Running test"); michael@0: yield test.run(snippetsMap); michael@0: info("Cleanup"); michael@0: gBrowser.removeCurrentTab(); michael@0: } michael@0: }).then(finish, ex => { michael@0: ok(false, "Unexpected Exception: " + ex); michael@0: finish(); michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * Starts a load in an existing tab and waits for it to finish (via some event). michael@0: * michael@0: * @param aTab michael@0: * The tab to load into. michael@0: * @param aUrl michael@0: * The url to load. michael@0: * @param aEvent michael@0: * The load event type to wait for. Defaults to "load". michael@0: * @return {Promise} resolved when the event is handled. michael@0: */ michael@0: function promiseTabLoadEvent(aTab, aURL, aEventType="load") michael@0: { michael@0: let deferred = Promise.defer(); michael@0: info("Wait tab event: " + aEventType); michael@0: aTab.linkedBrowser.addEventListener(aEventType, function load(event) { michael@0: if (event.originalTarget != aTab.linkedBrowser.contentDocument || michael@0: event.target.location.href == "about:blank") { michael@0: info("skipping spurious load event"); michael@0: return; michael@0: } michael@0: aTab.linkedBrowser.removeEventListener(aEventType, load, true); michael@0: info("Tab event received: " + aEventType); michael@0: deferred.resolve(); michael@0: }, true, true); michael@0: aTab.linkedBrowser.loadURI(aURL); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: /** michael@0: * Cleans up snippets and ensures that by default we don't try to check for michael@0: * remote snippets since that may cause network bustage or slowness. michael@0: * michael@0: * @param aTab michael@0: * The tab containing about:home. michael@0: * @param aSetupFn michael@0: * The setup function to be run. michael@0: * @return {Promise} resolved when the snippets are ready. Gets the snippets map. michael@0: */ michael@0: function promiseSetupSnippetsMap(aTab, aSetupFn) michael@0: { michael@0: let deferred = Promise.defer(); michael@0: info("Waiting for snippets map"); michael@0: aTab.linkedBrowser.addEventListener("AboutHomeLoadSnippets", function load(event) { michael@0: aTab.linkedBrowser.removeEventListener("AboutHomeLoadSnippets", load, true); michael@0: michael@0: let cw = aTab.linkedBrowser.contentWindow.wrappedJSObject; michael@0: // The snippets should already be ready by this point. Here we're michael@0: // just obtaining a reference to the snippets map. michael@0: cw.ensureSnippetsMapThen(function (aSnippetsMap) { michael@0: info("Got snippets map: " + michael@0: "{ last-update: " + aSnippetsMap.get("snippets-last-update") + michael@0: ", cached-version: " + aSnippetsMap.get("snippets-cached-version") + michael@0: " }"); michael@0: // Don't try to update. michael@0: aSnippetsMap.set("snippets-last-update", Date.now()); michael@0: aSnippetsMap.set("snippets-cached-version", AboutHomeUtils.snippetsVersion); michael@0: // Clear snippets. michael@0: aSnippetsMap.delete("snippets"); michael@0: aSetupFn(aSnippetsMap); michael@0: deferred.resolve(aSnippetsMap); michael@0: }); michael@0: }, true, true); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: /** michael@0: * Waits for the attributes being set by browser.js. michael@0: * michael@0: * @param aTab michael@0: * The tab containing about:home. michael@0: * @return {Promise} resolved when the attributes are ready. michael@0: */ michael@0: function promiseBrowserAttributes(aTab) michael@0: { michael@0: let deferred = Promise.defer(); michael@0: michael@0: let docElt = aTab.linkedBrowser.contentDocument.documentElement; michael@0: let observer = new MutationObserver(function (mutations) { michael@0: for (let mutation of mutations) { michael@0: info("Got attribute mutation: " + mutation.attributeName + michael@0: " from " + mutation.oldValue); michael@0: // Now we just have to wait for the last attribute. michael@0: if (mutation.attributeName == "searchEngineName") { michael@0: info("Remove attributes observer"); michael@0: observer.disconnect(); michael@0: // Must be sure to continue after the page mutation observer. michael@0: executeSoon(function() deferred.resolve()); michael@0: break; michael@0: } michael@0: } michael@0: }); michael@0: info("Add attributes observer"); michael@0: observer.observe(docElt, { attributes: true }); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: /** michael@0: * Retrieves the number of about:home searches recorded for the current day. michael@0: * michael@0: * @param aEngineName michael@0: * name of the setup search engine. michael@0: * michael@0: * @return {Promise} Returns a promise resolving to the number of searches. michael@0: */ michael@0: function getNumberOfSearches(aEngineName) { michael@0: let reporter = Components.classes["@mozilla.org/datareporting/service;1"] michael@0: .getService() michael@0: .wrappedJSObject michael@0: .healthReporter; michael@0: ok(reporter, "Health Reporter instance available."); michael@0: michael@0: return reporter.onInit().then(function onInit() { michael@0: let provider = reporter.getProvider("org.mozilla.searches"); michael@0: ok(provider, "Searches provider is available."); michael@0: michael@0: let m = provider.getMeasurement("counts", 3); michael@0: return m.getValues().then(data => { michael@0: let now = new Date(); michael@0: let yday = new Date(now); michael@0: yday.setDate(yday.getDate() - 1); michael@0: michael@0: // Add the number of searches recorded yesterday to the number of searches michael@0: // recorded today. This makes the test not fail intermittently when it is michael@0: // run at midnight and we accidentally compare the number of searches from michael@0: // different days. Tests are always run with an empty profile so there michael@0: // are no searches from yesterday, normally. Should the test happen to run michael@0: // past midnight we make sure to count them in as well. michael@0: return getNumberOfSearchesByDate(aEngineName, data, now) + michael@0: getNumberOfSearchesByDate(aEngineName, data, yday); michael@0: }); michael@0: }); michael@0: } michael@0: michael@0: function getNumberOfSearchesByDate(aEngineName, aData, aDate) { michael@0: if (aData.days.hasDay(aDate)) { michael@0: let id = Services.search.getEngineByName(aEngineName).identifier; michael@0: michael@0: let day = aData.days.getDay(aDate); michael@0: let field = id + ".abouthome"; michael@0: michael@0: if (day.has(field)) { michael@0: return day.get(field) || 0; michael@0: } michael@0: } michael@0: michael@0: return 0; // No records found. michael@0: } michael@0: michael@0: function waitForLoad(cb) { michael@0: let browser = gBrowser.selectedBrowser; michael@0: browser.addEventListener("load", function listener() { michael@0: if (browser.currentURI.spec == "about:blank") michael@0: return; michael@0: info("Page loaded: " + browser.currentURI.spec); michael@0: browser.removeEventListener("load", listener, true); michael@0: michael@0: cb(); michael@0: }, true); michael@0: }