1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/base/content/test/general/browser_aboutHome.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,574 @@ 1.4 +/* Any copyright is dedicated to the Public Domain. 1.5 + * http://creativecommons.org/publicdomain/zero/1.0/ 1.6 + */ 1.7 + 1.8 +XPCOMUtils.defineLazyModuleGetter(this, "Promise", 1.9 + "resource://gre/modules/Promise.jsm"); 1.10 +XPCOMUtils.defineLazyModuleGetter(this, "Task", 1.11 + "resource://gre/modules/Task.jsm"); 1.12 +XPCOMUtils.defineLazyModuleGetter(this, "AboutHomeUtils", 1.13 + "resource:///modules/AboutHome.jsm"); 1.14 + 1.15 +let gRightsVersion = Services.prefs.getIntPref("browser.rights.version"); 1.16 + 1.17 +registerCleanupFunction(function() { 1.18 + // Ensure we don't pollute prefs for next tests. 1.19 + Services.prefs.clearUserPref("network.cookies.cookieBehavior"); 1.20 + Services.prefs.clearUserPref("network.cookie.lifetimePolicy"); 1.21 + Services.prefs.clearUserPref("browser.rights.override"); 1.22 + Services.prefs.clearUserPref("browser.rights." + gRightsVersion + ".shown"); 1.23 +}); 1.24 + 1.25 +let gTests = [ 1.26 + 1.27 +{ 1.28 + desc: "Check that clearing cookies does not clear storage", 1.29 + setup: function () 1.30 + { 1.31 + Cc["@mozilla.org/observer-service;1"] 1.32 + .getService(Ci.nsIObserverService) 1.33 + .notifyObservers(null, "cookie-changed", "cleared"); 1.34 + }, 1.35 + run: function (aSnippetsMap) 1.36 + { 1.37 + isnot(aSnippetsMap.get("snippets-last-update"), null, 1.38 + "snippets-last-update should have a value"); 1.39 + } 1.40 +}, 1.41 + 1.42 +{ 1.43 + desc: "Check default snippets are shown", 1.44 + setup: function () { }, 1.45 + run: function () 1.46 + { 1.47 + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; 1.48 + let snippetsElt = doc.getElementById("snippets"); 1.49 + ok(snippetsElt, "Found snippets element") 1.50 + is(snippetsElt.getElementsByTagName("span").length, 1, 1.51 + "A default snippet is present."); 1.52 + } 1.53 +}, 1.54 + 1.55 +{ 1.56 + desc: "Check default snippets are shown if snippets are invalid xml", 1.57 + setup: function (aSnippetsMap) 1.58 + { 1.59 + // This must be some incorrect xhtml code. 1.60 + aSnippetsMap.set("snippets", "<p><b></p></b>"); 1.61 + }, 1.62 + run: function (aSnippetsMap) 1.63 + { 1.64 + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; 1.65 + 1.66 + let snippetsElt = doc.getElementById("snippets"); 1.67 + ok(snippetsElt, "Found snippets element"); 1.68 + is(snippetsElt.getElementsByTagName("span").length, 1, 1.69 + "A default snippet is present."); 1.70 + 1.71 + aSnippetsMap.delete("snippets"); 1.72 + } 1.73 +}, 1.74 + 1.75 +{ 1.76 + desc: "Check that search engine logo has alt text", 1.77 + setup: function () { }, 1.78 + run: function () 1.79 + { 1.80 + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; 1.81 + 1.82 + let searchEngineLogoElt = doc.getElementById("searchEngineLogo"); 1.83 + ok(searchEngineLogoElt, "Found search engine logo"); 1.84 + 1.85 + let altText = searchEngineLogoElt.alt; 1.86 + ok(typeof altText == "string" && altText.length > 0, 1.87 + "Search engine logo's alt text is a nonempty string"); 1.88 + 1.89 + isnot(altText, "undefined", 1.90 + "Search engine logo's alt text shouldn't be the string 'undefined'"); 1.91 + } 1.92 +}, 1.93 + 1.94 +// Disabled on Linux for intermittent issues with FHR, see Bug 945667. 1.95 +{ 1.96 + desc: "Check that performing a search fires a search event and records to " + 1.97 + "Firefox Health Report.", 1.98 + setup: function () { }, 1.99 + run: function () { 1.100 + // Skip this test on Linux. 1.101 + if (navigator.platform.indexOf("Linux") == 0) { return; } 1.102 + 1.103 + try { 1.104 + let cm = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager); 1.105 + cm.getCategoryEntry("healthreport-js-provider-default", "SearchesProvider"); 1.106 + } catch (ex) { 1.107 + // Health Report disabled, or no SearchesProvider. 1.108 + return Promise.resolve(); 1.109 + } 1.110 + 1.111 + let numSearchesBefore = 0; 1.112 + let searchEventDeferred = Promise.defer(); 1.113 + let doc = gBrowser.contentDocument; 1.114 + let engineName = doc.documentElement.getAttribute("searchEngineName"); 1.115 + 1.116 + doc.addEventListener("AboutHomeSearchEvent", function onSearch(e) { 1.117 + let data = JSON.parse(e.detail); 1.118 + is(data.engineName, engineName, "Detail is search engine name"); 1.119 + 1.120 + // We use executeSoon() to ensure that this code runs after the 1.121 + // count has been updated in browser.js, since it uses the same 1.122 + // event. 1.123 + executeSoon(function () { 1.124 + getNumberOfSearches(engineName).then(num => { 1.125 + is(num, numSearchesBefore + 1, "One more search recorded."); 1.126 + searchEventDeferred.resolve(); 1.127 + }); 1.128 + }); 1.129 + }, true, true); 1.130 + 1.131 + // Get the current number of recorded searches. 1.132 + let searchStr = "a search"; 1.133 + getNumberOfSearches(engineName).then(num => { 1.134 + numSearchesBefore = num; 1.135 + 1.136 + info("Perform a search."); 1.137 + doc.getElementById("searchText").value = searchStr; 1.138 + doc.getElementById("searchSubmit").click(); 1.139 + }); 1.140 + 1.141 + let expectedURL = Services.search.currentEngine. 1.142 + getSubmission(searchStr, null, "homepage"). 1.143 + uri.spec; 1.144 + let loadPromise = waitForDocLoadAndStopIt(expectedURL); 1.145 + 1.146 + return Promise.all([searchEventDeferred.promise, loadPromise]); 1.147 + } 1.148 +}, 1.149 + 1.150 +{ 1.151 + desc: "Check snippets map is cleared if cached version is old", 1.152 + setup: function (aSnippetsMap) 1.153 + { 1.154 + aSnippetsMap.set("snippets", "test"); 1.155 + aSnippetsMap.set("snippets-cached-version", 0); 1.156 + }, 1.157 + run: function (aSnippetsMap) 1.158 + { 1.159 + ok(!aSnippetsMap.has("snippets"), "snippets have been properly cleared"); 1.160 + ok(!aSnippetsMap.has("snippets-cached-version"), 1.161 + "cached-version has been properly cleared"); 1.162 + } 1.163 +}, 1.164 + 1.165 +{ 1.166 + desc: "Check cached snippets are shown if cached version is current", 1.167 + setup: function (aSnippetsMap) 1.168 + { 1.169 + aSnippetsMap.set("snippets", "test"); 1.170 + }, 1.171 + run: function (aSnippetsMap) 1.172 + { 1.173 + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; 1.174 + 1.175 + let snippetsElt = doc.getElementById("snippets"); 1.176 + ok(snippetsElt, "Found snippets element"); 1.177 + is(snippetsElt.innerHTML, "test", "Cached snippet is present."); 1.178 + 1.179 + is(aSnippetsMap.get("snippets"), "test", "snippets still cached"); 1.180 + is(aSnippetsMap.get("snippets-cached-version"), 1.181 + AboutHomeUtils.snippetsVersion, 1.182 + "cached-version is correct"); 1.183 + ok(aSnippetsMap.has("snippets-last-update"), "last-update still exists"); 1.184 + } 1.185 +}, 1.186 + 1.187 +{ 1.188 + desc: "Check if the 'Know Your Rights default snippet is shown when 'browser.rights.override' pref is set", 1.189 + beforeRun: function () 1.190 + { 1.191 + Services.prefs.setBoolPref("browser.rights.override", false); 1.192 + }, 1.193 + setup: function () { }, 1.194 + run: function (aSnippetsMap) 1.195 + { 1.196 + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; 1.197 + let showRights = AboutHomeUtils.showKnowYourRights; 1.198 + 1.199 + ok(showRights, "AboutHomeUtils.showKnowYourRights should be TRUE"); 1.200 + 1.201 + let snippetsElt = doc.getElementById("snippets"); 1.202 + ok(snippetsElt, "Found snippets element"); 1.203 + is(snippetsElt.getElementsByTagName("a")[0].href, "about:rights", "Snippet link is present."); 1.204 + 1.205 + Services.prefs.clearUserPref("browser.rights.override"); 1.206 + } 1.207 +}, 1.208 + 1.209 +{ 1.210 + desc: "Check if the 'Know Your Rights default snippet is NOT shown when 'browser.rights.override' pref is NOT set", 1.211 + beforeRun: function () 1.212 + { 1.213 + Services.prefs.setBoolPref("browser.rights.override", true); 1.214 + }, 1.215 + setup: function () { }, 1.216 + run: function (aSnippetsMap) 1.217 + { 1.218 + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; 1.219 + let rightsData = AboutHomeUtils.knowYourRightsData; 1.220 + 1.221 + ok(!rightsData, "AboutHomeUtils.knowYourRightsData should be FALSE"); 1.222 + 1.223 + let snippetsElt = doc.getElementById("snippets"); 1.224 + ok(snippetsElt, "Found snippets element"); 1.225 + ok(snippetsElt.getElementsByTagName("a")[0].href != "about:rights", "Snippet link should not point to about:rights."); 1.226 + 1.227 + Services.prefs.clearUserPref("browser.rights.override"); 1.228 + } 1.229 +}, 1.230 + 1.231 +{ 1.232 + desc: "Check that the search UI/ action is updated when the search engine is changed", 1.233 + setup: function() {}, 1.234 + run: function() 1.235 + { 1.236 + let currEngine = Services.search.currentEngine; 1.237 + let unusedEngines = [].concat(Services.search.getVisibleEngines()).filter(x => x != currEngine); 1.238 + let searchbar = document.getElementById("searchbar"); 1.239 + 1.240 + function checkSearchUI(engine) { 1.241 + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; 1.242 + let searchText = doc.getElementById("searchText"); 1.243 + let logoElt = doc.getElementById("searchEngineLogo"); 1.244 + let engineName = doc.documentElement.getAttribute("searchEngineName"); 1.245 + 1.246 + is(engineName, engine.name, "Engine name should've been updated"); 1.247 + 1.248 + if (!logoElt.parentNode.hidden) { 1.249 + is(logoElt.alt, engineName, "Alt text of logo image should match search engine name") 1.250 + } else { 1.251 + is(searchText.placeholder, engineName, "Placeholder text should match search engine name"); 1.252 + } 1.253 + } 1.254 + // Do a sanity check that all attributes are correctly set to begin with 1.255 + checkSearchUI(currEngine); 1.256 + 1.257 + let deferred = Promise.defer(); 1.258 + promiseBrowserAttributes(gBrowser.selectedTab).then(function() { 1.259 + // Test if the update propagated 1.260 + checkSearchUI(unusedEngines[0]); 1.261 + searchbar.currentEngine = currEngine; 1.262 + deferred.resolve(); 1.263 + }); 1.264 + 1.265 + // The following cleanup function will set currentEngine back to the previous 1.266 + // engine if we fail to do so above. 1.267 + registerCleanupFunction(function() { 1.268 + searchbar.currentEngine = currEngine; 1.269 + }); 1.270 + // Set the current search engine to an unused one 1.271 + searchbar.currentEngine = unusedEngines[0]; 1.272 + searchbar.select(); 1.273 + return deferred.promise; 1.274 + } 1.275 +}, 1.276 + 1.277 +{ 1.278 + desc: "Check POST search engine support", 1.279 + setup: function() {}, 1.280 + run: function() 1.281 + { 1.282 + let deferred = Promise.defer(); 1.283 + let currEngine = Services.search.defaultEngine; 1.284 + let searchObserver = function search_observer(aSubject, aTopic, aData) { 1.285 + let engine = aSubject.QueryInterface(Ci.nsISearchEngine); 1.286 + info("Observer: " + aData + " for " + engine.name); 1.287 + 1.288 + if (aData != "engine-added") 1.289 + return; 1.290 + 1.291 + if (engine.name != "POST Search") 1.292 + return; 1.293 + 1.294 + // Ready to execute the tests! 1.295 + let needle = "Search for something awesome."; 1.296 + let document = gBrowser.selectedTab.linkedBrowser.contentDocument; 1.297 + let searchText = document.getElementById("searchText"); 1.298 + 1.299 + // We're about to change the search engine. Once the change has 1.300 + // propagated to the about:home content, we want to perform a search. 1.301 + let mutationObserver = new MutationObserver(function (mutations) { 1.302 + for (let mutation of mutations) { 1.303 + if (mutation.attributeName == "searchEngineName") { 1.304 + searchText.value = needle; 1.305 + searchText.focus(); 1.306 + EventUtils.synthesizeKey("VK_RETURN", {}); 1.307 + } 1.308 + } 1.309 + }); 1.310 + mutationObserver.observe(document.documentElement, { attributes: true }); 1.311 + 1.312 + // Change the search engine, triggering the observer above. 1.313 + Services.search.defaultEngine = engine; 1.314 + 1.315 + registerCleanupFunction(function() { 1.316 + mutationObserver.disconnect(); 1.317 + Services.search.removeEngine(engine); 1.318 + Services.search.defaultEngine = currEngine; 1.319 + }); 1.320 + 1.321 + 1.322 + // When the search results load, check them for correctness. 1.323 + waitForLoad(function() { 1.324 + let loadedText = gBrowser.contentDocument.body.textContent; 1.325 + ok(loadedText, "search page loaded"); 1.326 + is(loadedText, "searchterms=" + escape(needle.replace(/\s/g, "+")), 1.327 + "Search text should arrive correctly"); 1.328 + deferred.resolve(); 1.329 + }); 1.330 + }; 1.331 + Services.obs.addObserver(searchObserver, "browser-search-engine-modified", false); 1.332 + registerCleanupFunction(function () { 1.333 + Services.obs.removeObserver(searchObserver, "browser-search-engine-modified"); 1.334 + }); 1.335 + Services.search.addEngine("http://test:80/browser/browser/base/content/test/general/POSTSearchEngine.xml", 1.336 + Ci.nsISearchEngine.DATA_XML, null, false); 1.337 + return deferred.promise; 1.338 + } 1.339 +}, 1.340 + 1.341 +{ 1.342 + desc: "Make sure that a page can't imitate about:home", 1.343 + setup: function () { }, 1.344 + run: function (aSnippetsMap) 1.345 + { 1.346 + let deferred = Promise.defer(); 1.347 + 1.348 + let browser = gBrowser.selectedTab.linkedBrowser; 1.349 + waitForLoad(() => { 1.350 + let button = browser.contentDocument.getElementById("settings"); 1.351 + ok(button, "Found settings button in test page"); 1.352 + button.click(); 1.353 + 1.354 + // It may take a few turns of the event loop before the window 1.355 + // is displayed, so we wait. 1.356 + function check(n) { 1.357 + let win = Services.wm.getMostRecentWindow("Browser:Preferences"); 1.358 + ok(!win, "Preferences window not showing"); 1.359 + if (win) { 1.360 + win.close(); 1.361 + } 1.362 + 1.363 + if (n > 0) { 1.364 + executeSoon(() => check(n-1)); 1.365 + } else { 1.366 + deferred.resolve(); 1.367 + } 1.368 + } 1.369 + 1.370 + check(5); 1.371 + }); 1.372 + 1.373 + browser.loadURI("https://example.com/browser/browser/base/content/test/general/test_bug959531.html"); 1.374 + return deferred.promise; 1.375 + } 1.376 +}, 1.377 + 1.378 +]; 1.379 + 1.380 +function test() 1.381 +{ 1.382 + waitForExplicitFinish(); 1.383 + requestLongerTimeout(2); 1.384 + ignoreAllUncaughtExceptions(); 1.385 + 1.386 + Task.spawn(function () { 1.387 + for (let test of gTests) { 1.388 + info(test.desc); 1.389 + 1.390 + if (test.beforeRun) 1.391 + yield test.beforeRun(); 1.392 + 1.393 + // Create a tab to run the test. 1.394 + let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank"); 1.395 + 1.396 + // Add an event handler to modify the snippets map once it's ready. 1.397 + let snippetsPromise = promiseSetupSnippetsMap(tab, test.setup); 1.398 + 1.399 + // Start loading about:home and wait for it to complete. 1.400 + yield promiseTabLoadEvent(tab, "about:home", "AboutHomeLoadSnippetsSucceeded"); 1.401 + 1.402 + // This promise should already be resolved since the page is done, 1.403 + // but we still want to get the snippets map out of it. 1.404 + let snippetsMap = yield snippetsPromise; 1.405 + 1.406 + info("Running test"); 1.407 + yield test.run(snippetsMap); 1.408 + info("Cleanup"); 1.409 + gBrowser.removeCurrentTab(); 1.410 + } 1.411 + }).then(finish, ex => { 1.412 + ok(false, "Unexpected Exception: " + ex); 1.413 + finish(); 1.414 + }); 1.415 +} 1.416 + 1.417 +/** 1.418 + * Starts a load in an existing tab and waits for it to finish (via some event). 1.419 + * 1.420 + * @param aTab 1.421 + * The tab to load into. 1.422 + * @param aUrl 1.423 + * The url to load. 1.424 + * @param aEvent 1.425 + * The load event type to wait for. Defaults to "load". 1.426 + * @return {Promise} resolved when the event is handled. 1.427 + */ 1.428 +function promiseTabLoadEvent(aTab, aURL, aEventType="load") 1.429 +{ 1.430 + let deferred = Promise.defer(); 1.431 + info("Wait tab event: " + aEventType); 1.432 + aTab.linkedBrowser.addEventListener(aEventType, function load(event) { 1.433 + if (event.originalTarget != aTab.linkedBrowser.contentDocument || 1.434 + event.target.location.href == "about:blank") { 1.435 + info("skipping spurious load event"); 1.436 + return; 1.437 + } 1.438 + aTab.linkedBrowser.removeEventListener(aEventType, load, true); 1.439 + info("Tab event received: " + aEventType); 1.440 + deferred.resolve(); 1.441 + }, true, true); 1.442 + aTab.linkedBrowser.loadURI(aURL); 1.443 + return deferred.promise; 1.444 +} 1.445 + 1.446 +/** 1.447 + * Cleans up snippets and ensures that by default we don't try to check for 1.448 + * remote snippets since that may cause network bustage or slowness. 1.449 + * 1.450 + * @param aTab 1.451 + * The tab containing about:home. 1.452 + * @param aSetupFn 1.453 + * The setup function to be run. 1.454 + * @return {Promise} resolved when the snippets are ready. Gets the snippets map. 1.455 + */ 1.456 +function promiseSetupSnippetsMap(aTab, aSetupFn) 1.457 +{ 1.458 + let deferred = Promise.defer(); 1.459 + info("Waiting for snippets map"); 1.460 + aTab.linkedBrowser.addEventListener("AboutHomeLoadSnippets", function load(event) { 1.461 + aTab.linkedBrowser.removeEventListener("AboutHomeLoadSnippets", load, true); 1.462 + 1.463 + let cw = aTab.linkedBrowser.contentWindow.wrappedJSObject; 1.464 + // The snippets should already be ready by this point. Here we're 1.465 + // just obtaining a reference to the snippets map. 1.466 + cw.ensureSnippetsMapThen(function (aSnippetsMap) { 1.467 + info("Got snippets map: " + 1.468 + "{ last-update: " + aSnippetsMap.get("snippets-last-update") + 1.469 + ", cached-version: " + aSnippetsMap.get("snippets-cached-version") + 1.470 + " }"); 1.471 + // Don't try to update. 1.472 + aSnippetsMap.set("snippets-last-update", Date.now()); 1.473 + aSnippetsMap.set("snippets-cached-version", AboutHomeUtils.snippetsVersion); 1.474 + // Clear snippets. 1.475 + aSnippetsMap.delete("snippets"); 1.476 + aSetupFn(aSnippetsMap); 1.477 + deferred.resolve(aSnippetsMap); 1.478 + }); 1.479 + }, true, true); 1.480 + return deferred.promise; 1.481 +} 1.482 + 1.483 +/** 1.484 + * Waits for the attributes being set by browser.js. 1.485 + * 1.486 + * @param aTab 1.487 + * The tab containing about:home. 1.488 + * @return {Promise} resolved when the attributes are ready. 1.489 + */ 1.490 +function promiseBrowserAttributes(aTab) 1.491 +{ 1.492 + let deferred = Promise.defer(); 1.493 + 1.494 + let docElt = aTab.linkedBrowser.contentDocument.documentElement; 1.495 + let observer = new MutationObserver(function (mutations) { 1.496 + for (let mutation of mutations) { 1.497 + info("Got attribute mutation: " + mutation.attributeName + 1.498 + " from " + mutation.oldValue); 1.499 + // Now we just have to wait for the last attribute. 1.500 + if (mutation.attributeName == "searchEngineName") { 1.501 + info("Remove attributes observer"); 1.502 + observer.disconnect(); 1.503 + // Must be sure to continue after the page mutation observer. 1.504 + executeSoon(function() deferred.resolve()); 1.505 + break; 1.506 + } 1.507 + } 1.508 + }); 1.509 + info("Add attributes observer"); 1.510 + observer.observe(docElt, { attributes: true }); 1.511 + 1.512 + return deferred.promise; 1.513 +} 1.514 + 1.515 +/** 1.516 + * Retrieves the number of about:home searches recorded for the current day. 1.517 + * 1.518 + * @param aEngineName 1.519 + * name of the setup search engine. 1.520 + * 1.521 + * @return {Promise} Returns a promise resolving to the number of searches. 1.522 + */ 1.523 +function getNumberOfSearches(aEngineName) { 1.524 + let reporter = Components.classes["@mozilla.org/datareporting/service;1"] 1.525 + .getService() 1.526 + .wrappedJSObject 1.527 + .healthReporter; 1.528 + ok(reporter, "Health Reporter instance available."); 1.529 + 1.530 + return reporter.onInit().then(function onInit() { 1.531 + let provider = reporter.getProvider("org.mozilla.searches"); 1.532 + ok(provider, "Searches provider is available."); 1.533 + 1.534 + let m = provider.getMeasurement("counts", 3); 1.535 + return m.getValues().then(data => { 1.536 + let now = new Date(); 1.537 + let yday = new Date(now); 1.538 + yday.setDate(yday.getDate() - 1); 1.539 + 1.540 + // Add the number of searches recorded yesterday to the number of searches 1.541 + // recorded today. This makes the test not fail intermittently when it is 1.542 + // run at midnight and we accidentally compare the number of searches from 1.543 + // different days. Tests are always run with an empty profile so there 1.544 + // are no searches from yesterday, normally. Should the test happen to run 1.545 + // past midnight we make sure to count them in as well. 1.546 + return getNumberOfSearchesByDate(aEngineName, data, now) + 1.547 + getNumberOfSearchesByDate(aEngineName, data, yday); 1.548 + }); 1.549 + }); 1.550 +} 1.551 + 1.552 +function getNumberOfSearchesByDate(aEngineName, aData, aDate) { 1.553 + if (aData.days.hasDay(aDate)) { 1.554 + let id = Services.search.getEngineByName(aEngineName).identifier; 1.555 + 1.556 + let day = aData.days.getDay(aDate); 1.557 + let field = id + ".abouthome"; 1.558 + 1.559 + if (day.has(field)) { 1.560 + return day.get(field) || 0; 1.561 + } 1.562 + } 1.563 + 1.564 + return 0; // No records found. 1.565 +} 1.566 + 1.567 +function waitForLoad(cb) { 1.568 + let browser = gBrowser.selectedBrowser; 1.569 + browser.addEventListener("load", function listener() { 1.570 + if (browser.currentURI.spec == "about:blank") 1.571 + return; 1.572 + info("Page loaded: " + browser.currentURI.spec); 1.573 + browser.removeEventListener("load", listener, true); 1.574 + 1.575 + cb(); 1.576 + }, true); 1.577 +}