/* eslint-env mozilla/frame-script */ function waitForCondition(condition, nextTest, errorMsg, retryTimes) { retryTimes = typeof retryTimes !== "undefined" ? retryTimes : 30; var tries = 0; var interval = setInterval(function() { if (tries >= retryTimes) { ok(false, errorMsg); moveOn(); } var conditionPassed; try { conditionPassed = condition(); } catch (e) { ok(false, e + "\n" + e.stack); conditionPassed = false; } if (conditionPassed) { moveOn(); } tries++; }, 100); var moveOn = function() { clearInterval(interval); nextTest(); }; } function promiseWaitForCondition(aConditionFn) { return new Promise(resolve => { waitForCondition(aConditionFn, resolve, "Condition didn't pass."); }); } function whenTabLoaded(aTab, aCallback) { promiseTabLoadEvent(aTab).then(aCallback); } function promiseTabLoaded(aTab) { return new Promise(resolve => { whenTabLoaded(aTab, resolve); }); } /** * Waits for a load (or custom) event to finish in a given tab. If provided * load an uri into the tab. * * @param tab * The tab to load into. * @param [optional] url * The url to load, or the current url. * @return {Promise} resolved when the event is handled. * @resolves to the received event * @rejects if a valid load event is not received within a meaningful interval */ function promiseTabLoadEvent(tab, url) { info("Wait tab event: load"); function handle(loadedUrl) { if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) { info(`Skipping spurious load event for ${loadedUrl}`); return false; } info("Tab event received: load"); return true; } let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle); if (url) BrowserTestUtils.loadURI(tab.linkedBrowser, url); return loaded; } /** * Waits for the next top-level document load in the current browser. The URI * of the document is compared against aExpectedURL. The load is then stopped * before it actually starts. * * @param aExpectedURL * The URL of the document that is expected to load. * @param aStopFromProgressListener * Whether to cancel the load directly from the progress listener. Defaults to true. * If you're using this method to avoid hitting the network, you want the default (true). * However, the browser UI will behave differently for loads stopped directly from * the progress listener (effectively in the middle of a call to loadURI) and so there * are cases where you may want to avoid stopping the load directly from within the * progress listener callback. * @return promise */ function waitForDocLoadAndStopIt(aExpectedURL, aBrowser = gBrowser.selectedBrowser, aStopFromProgressListener = true) { function content_script(contentStopFromProgressListener) { let { interfaces: Ci, utils: Cu } = Components; Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); let wp = docShell.QueryInterface(Ci.nsIWebProgress); function stopContent(now, uri) { if (now) { /* Hammer time. */ content.stop(); /* Let the parent know we're done. */ sendAsyncMessage("Test:WaitForDocLoadAndStopIt", { uri }); } else { setTimeout(stopContent.bind(null, true, uri), 0); } } let progressListener = { onStateChange(webProgress, req, flags, status) { dump("waitForDocLoadAndStopIt: onStateChange " + flags.toString(16) + ": " + req.name + "\n"); if (webProgress.isTopLevel && flags & Ci.nsIWebProgressListener.STATE_START) { wp.removeProgressListener(progressListener); let chan = req.QueryInterface(Ci.nsIChannel); dump(`waitForDocLoadAndStopIt: Document start: ${chan.URI.spec}\n`); stopContent(contentStopFromProgressListener, chan.originalURI.spec); } }, QueryInterface: XPCOMUtils.generateQI(["nsISupportsWeakReference"]) }; wp.addProgressListener(progressListener, wp.NOTIFY_STATE_WINDOW); /** * As |this| is undefined and we can't extend |docShell|, adding an unload * event handler is the easiest way to ensure the weakly referenced * progress listener is kept alive as long as necessary. */ addEventListener("unload", function() { try { wp.removeProgressListener(progressListener); } catch (e) { /* Will most likely fail. */ } }); } return new Promise((resolve, reject) => { function complete({ data }) { is(data.uri, aExpectedURL, "waitForDocLoadAndStopIt: The expected URL was loaded"); mm.removeMessageListener("Test:WaitForDocLoadAndStopIt", complete); resolve(); } let mm = aBrowser.messageManager; mm.loadFrameScript("data:,(" + content_script.toString() + ")(" + aStopFromProgressListener + ");", true); mm.addMessageListener("Test:WaitForDocLoadAndStopIt", complete); info("waitForDocLoadAndStopIt: Waiting for URL: " + aExpectedURL); }); } /** * Wait for the search engine to change. */ function promiseContentSearchChange(browser, newEngineName) { return ContentTask.spawn(browser, { newEngineName }, async function(args) { return new Promise(resolve => { content.addEventListener("ContentSearchService", function listener(aEvent) { if (aEvent.detail.type == "CurrentState" && content.wrappedJSObject.gContentSearchController.defaultEngine.name == args.newEngineName) { content.removeEventListener("ContentSearchService", listener); resolve(); } }); }); }); } /** * Wait for the search engine to be added. */ function promiseNewEngine(basename) { info("Waiting for engine to be added: " + basename); return new Promise((resolve, reject) => { let url = getRootDirectory(gTestPath) + basename; Services.search.addEngine(url, null, "", false, { onSuccess(engine) { info("Search engine added: " + basename); registerCleanupFunction(() => { try { Services.search.removeEngine(engine); } catch (ex) { /* Can't remove the engine more than once */ } }); resolve(engine); }, onError(errCode) { ok(false, "addEngine failed with error code " + errCode); reject(); }, }); }); }