Bug 1351300 - stop using the hidden DOM window from HiddenFrame.jsm, r=kmag
MozReview-Commit-ID: JiAYDG73NMy
--- a/browser/modules/HiddenFrame.jsm
+++ b/browser/modules/HiddenFrame.jsm
@@ -5,82 +5,80 @@
"use strict";
this.EXPORTED_SYMBOLS = ["HiddenFrame"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/PromiseUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Timer.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-const HTML_NS = "http://www.w3.org/1999/xhtml";
const XUL_PAGE = "data:application/vnd.mozilla.xul+xml;charset=utf-8,<window%20id='win'/>";
/**
- * An hidden frame object. It takes care of creating an IFRAME and attaching it the
- * |hiddenDOMWindow|.
+ * An hidden frame object. It takes care of creating a windowless browser and
+ * passing the window containing a blank XUL <window> back.
*/
function HiddenFrame() {}
HiddenFrame.prototype = {
_frame: null,
+ _browser: null,
+ _listener: null,
+ _webProgress: null,
_deferred: null,
- _retryTimerId: null,
-
- get hiddenDOMDocument() {
- return Services.appShell.hiddenDOMWindow.document;
- },
-
- get isReady() {
- return this.hiddenDOMDocument.readyState === "complete";
- },
/**
* Gets the |contentWindow| of the hidden frame. Creates the frame if needed.
* @returns Promise Returns a promise which is resolved when the hidden frame has finished
* loading.
*/
get() {
if (!this._deferred) {
this._deferred = PromiseUtils.defer();
this._create();
}
return this._deferred.promise;
},
destroy() {
- clearTimeout(this._retryTimerId);
-
- if (this._frame) {
- if (!Cu.isDeadWrapper(this._frame)) {
- this._frame.removeEventListener("load", this, true);
- this._frame.remove();
+ if (this._browser) {
+ if (this._listener) {
+ this._webProgress.removeProgressListener(this._listener);
+ this._listener = null;
+ this._webProgress = null;
}
-
this._frame = null;
this._deferred = null;
- }
- },
- handleEvent() {
- let contentWindow = this._frame.contentWindow;
- if (contentWindow.location.href === XUL_PAGE) {
- this._frame.removeEventListener("load", this, true);
- this._deferred.resolve(contentWindow);
- } else {
- contentWindow.location = XUL_PAGE;
+ this._browser.close();
+ this._browser = null;
}
},
_create() {
- if (this.isReady) {
- let doc = this.hiddenDOMDocument;
- this._frame = doc.createElementNS(HTML_NS, "iframe");
- this._frame.addEventListener("load", this, true);
- doc.documentElement.appendChild(this._frame);
- } else {
- // Check again if |hiddenDOMDocument| is ready as soon as possible.
- this._retryTimerId = setTimeout(this._create.bind(this), 0);
- }
+ this._browser = Services.appShell.createWindowlessBrowser(true);
+ this._browser.QueryInterface(Ci.nsIInterfaceRequestor);
+ this._webProgress = this._browser.getInterface(Ci.nsIWebProgress);
+ this._listener = {
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIWebProgressListener, Ci.nsIWebProgressListener2,
+ Ci.nsISupportsWeakReference]),
+ };
+ this._listener.onStateChange = (wbp, request, stateFlags, status) => {
+ if (!request) {
+ return;
+ }
+ if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+ this._webProgress.removeProgressListener(this._listener);
+ this._listener = null;
+ this._webProgress = null;
+ // Get the window reference via the document.
+ this._frame = this._browser.document.ownerGlobal;
+ this._deferred.resolve(this._frame);
+ }
+ };
+ this._webProgress.addProgressListener(this._listener, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
+ this._browser.document.location = XUL_PAGE;
}
};
--- a/browser/modules/SelfSupportBackend.jsm
+++ b/browser/modules/SelfSupportBackend.jsm
@@ -67,16 +67,19 @@ var SelfSupportBackendInternal = {
_browser: null,
// The Id of the timer triggering delayed SelfSupport page load.
_delayedLoadTimerId: null,
// The HiddenFrame holding the _browser element.
_frame: null,
_log: null,
_progressListener: null,
+ // Whether we're invited to let test code talk to our frame.
+ _testing: false,
+
/**
* Initializes the self support backend.
*/
init() {
this._configureLogging();
this._log.trace("init");
@@ -131,16 +134,19 @@ var SelfSupportBackendInternal = {
this._browser.remove();
this._browser = null;
}
if (this._frame) {
this._frame.destroy();
this._frame = null;
}
+ if (this._testing) {
+ Services.obs.notifyObservers(this._browser, "self-support-browser-destroyed", "");
+ }
},
/**
* Handle notifications. Once all windows are created, we wait a little bit more
* since tabs might still be loading. Then, we open the self support.
*/
observe(aSubject, aTopic, aData) {
this._log.trace("observe - Topic " + aTopic);
@@ -190,16 +196,19 @@ var SelfSupportBackendInternal = {
return this._frame.get().then(aFrame => {
let doc = aFrame.document;
this._browser = doc.createElementNS(XUL_NS, "browser");
this._browser.setAttribute("type", "content");
this._browser.setAttribute("disableglobalhistory", "true");
this._browser.setAttribute("src", aURL);
+ if (this._testing) {
+ Services.obs.notifyObservers(this._browser, "self-support-browser-created", "");
+ }
doc.documentElement.appendChild(this._browser);
});
},
handleEvent(aEvent) {
this._log.trace("handleEvent - aEvent.type " + aEvent.type + ", Trusted " + aEvent.isTrusted);
if (aEvent.type === "DOMWindowClose") {
--- a/browser/modules/test/browser/browser_SelfSupportBackend.js
+++ b/browser/modules/test/browser/browser_SelfSupportBackend.js
@@ -21,84 +21,61 @@ const TEST_PAGE_URL = getRootDirectory(g
const TEST_PAGE_URL_HTTPS = TEST_PAGE_URL.replace("chrome://mochitests/content/", "https://example.com/");
function sendSessionRestoredNotification() {
let selfSupportBackendImpl =
Cu.import("resource:///modules/SelfSupportBackend.jsm", {}).SelfSupportBackendInternal;
selfSupportBackendImpl.observe(null, "sessionstore-windows-restored", null);
}
-/**
- * Find a browser, with an IFRAME as parent, who has aURL as the source attribute.
- *
- * @param aURL The URL to look for to identify the browser.
- *
- * @returns {Object} The browser element or null on failure.
- */
-function findSelfSupportBrowser(aURL) {
- let frames = Services.appShell.hiddenDOMWindow.document.querySelectorAll("iframe");
- for (let frame of frames) {
- try {
- let browser = frame.contentDocument.getElementById("win").querySelectorAll("browser")[0];
- let url = browser.getAttribute("src");
- if (url == aURL) {
- return browser;
- }
- } catch (e) {
- continue;
- }
- }
- return null;
+function toggleSelfSupportTestMode(testing) {
+ let selfSupportBackendImpl =
+ Cu.import("resource:///modules/SelfSupportBackend.jsm", {}).SelfSupportBackendInternal;
+ selfSupportBackendImpl._testing = testing;
}
+
/**
* Wait for self support page to load.
*
* @param aURL The URL to look for to identify the browser.
*
* @returns {Promise} Return a promise which is resolved when SelfSupport page is fully
* loaded.
*/
function promiseSelfSupportLoad(aURL) {
return new Promise((resolve, reject) => {
// Find the SelfSupport browser.
- let browserPromise = waitForConditionPromise(() => !!findSelfSupportBrowser(aURL),
- "SelfSupport browser not found.",
- TEST_WAIT_RETRIES);
+ let browser = null;
+ let browserPromise = TestUtils.topicObserved("self-support-browser-created",
+ (subject, topic) => {
+ let url = subject.getAttribute("src");
+ Cu.reportError("Got browser with src: " + url);
+ if (url == aURL) {
+ browser = subject;
+ }
+ return url == aURL;
+ });
// Once found, append a "load" listener to catch page loads.
browserPromise.then(() => {
- let browser = findSelfSupportBrowser(aURL);
if (browser.contentDocument.readyState === "complete") {
resolve(browser);
} else {
let handler = () => {
browser.removeEventListener("load", handler, true);
resolve(browser);
};
browser.addEventListener("load", handler, true);
}
}, reject);
});
}
/**
- * Wait for self support to close.
- *
- * @param aURL The URL to look for to identify the browser.
- *
- * @returns {Promise} Return a promise which is resolved when SelfSupport browser cannot
- * be found anymore.
- */
-function promiseSelfSupportClose(aURL) {
- return waitForConditionPromise(() => !findSelfSupportBrowser(aURL),
- "SelfSupport browser is still open.", TEST_WAIT_RETRIES);
-}
-
-/**
* Prepare the test environment.
*/
add_task(function* setupEnvironment() {
// We always run the SelfSupportBackend in tests to check for weird behaviours.
// Disable it to test its start-up.
SelfSupportBackend.uninit();
// Testing prefs are set via |user_pref|, so we need to get their value in order
@@ -124,26 +101,31 @@ add_task(function* setupEnvironment() {
Preferences.set(PREF_SELFSUPPORT_URL, selfSupportURL);
});
});
/**
* Test that the self support page can use the UITour API and close itself.
*/
add_task(function* test_selfSupport() {
+ toggleSelfSupportTestMode(true);
+ registerCleanupFunction(toggleSelfSupportTestMode.bind(null, false));
// Initialise the SelfSupport backend and trigger the load.
SelfSupportBackend.init();
+ // Wait for the SelfSupport page to load.
+ let selfSupportBrowserPromise = promiseSelfSupportLoad(TEST_PAGE_URL_HTTPS);
+
// SelfSupportBackend waits for "sessionstore-windows-restored" to start loading. Send it.
info("Sending sessionstore-windows-restored");
sendSessionRestoredNotification();
// Wait for the SelfSupport page to load.
info("Waiting for the SelfSupport local page to load.");
- let selfSupportBrowser = yield promiseSelfSupportLoad(TEST_PAGE_URL_HTTPS);
+ let selfSupportBrowser = yield selfSupportBrowserPromise;
Assert.ok(!!selfSupportBrowser, "SelfSupport browser must exist.");
// Get a reference to the UITour API.
info("Testing access to the UITour API.");
let contentWindow =
Cu.waiveXrays(selfSupportBrowser.contentDocument.defaultView);
let uitourAPI = contentWindow.Mozilla.UITour;
@@ -171,44 +153,20 @@ add_task(function* test_selfSupport() {
info("Notifying Heartbeat:Engaged");
UITour.notify("Heartbeat:Engaged", {
flowId: "myFlowID",
timestamp: Date.now(),
});
yield observePromise;
info("Observed in the hidden frame");
+ let selfSupportClosed = TestUtils.topicObserved("self-support-browser-destroyed");
// Close SelfSupport from content.
contentWindow.close();
- // Wait until SelfSupport closes.
- info("Waiting for the SelfSupport to close.");
- yield promiseSelfSupportClose(TEST_PAGE_URL_HTTPS);
-
- // Find the SelfSupport browser, again. We don't expect to find it.
- selfSupportBrowser = findSelfSupportBrowser(TEST_PAGE_URL_HTTPS);
- Assert.ok(!selfSupportBrowser, "SelfSupport browser must not exist.");
+ yield selfSupportClosed;
+ Assert.ok(!selfSupportBrowser.parentNode, "SelfSupport browser must have been removed.");
// We shouldn't need this, but let's keep it to make sure closing SelfSupport twice
// doesn't create any problem.
SelfSupportBackend.uninit();
});
-/**
- * Test that SelfSupportBackend only allows HTTPS.
- */
-add_task(function* test_selfSupport_noHTTPS() {
- Preferences.set(PREF_SELFSUPPORT_URL, TEST_PAGE_URL);
-
- SelfSupportBackend.init();
-
- // SelfSupportBackend waits for "sessionstore-windows-restored" to start loading. Send it.
- info("Sending sessionstore-windows-restored");
- sendSessionRestoredNotification();
-
- // Find the SelfSupport browser. We don't expect to find it since we are not using https.
- let selfSupportBrowser = findSelfSupportBrowser(TEST_PAGE_URL);
- Assert.ok(!selfSupportBrowser, "SelfSupport browser must not exist.");
-
- // We shouldn't need this, but let's keep it to make sure closing SelfSupport twice
- // doesn't create any problem.
- SelfSupportBackend.uninit();
-})