gecko-dev/browser/modules/BrowserNewTabPreloader.jsm

271 lines
7.2 KiB
JavaScript

/* 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/. */
"use strict";
let EXPORTED_SYMBOLS = ["BrowserNewTabPreloader"];
const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const PREF_BRANCH = "browser.newtab.";
const TOPIC_DELAYED_STARTUP = "browser-delayed-startup-finished";
const PRELOADER_INIT_DELAY_MS = 5000;
let BrowserNewTabPreloader = {
init: function Preloader_init() {
Initializer.start();
},
uninit: function Preloader_uninit() {
Initializer.stop();
HostFrame.destroy();
Preferences.uninit();
HiddenBrowser.destroy();
},
newTab: function Preloader_newTab(aTab) {
HiddenBrowser.swapWithNewTab(aTab);
}
};
Object.freeze(BrowserNewTabPreloader);
let Initializer = {
_timer: null,
_observing: false,
start: function Initializer_start() {
Services.obs.addObserver(this, TOPIC_DELAYED_STARTUP, false);
this._observing = true;
},
stop: function Initializer_stop() {
if (this._timer) {
this._timer.cancel();
this._timer = null;
}
if (this._observing) {
Services.obs.removeObserver(this, TOPIC_DELAYED_STARTUP);
this._observing = false;
}
},
observe: function Initializer_observe(aSubject, aTopic, aData) {
if (aTopic == TOPIC_DELAYED_STARTUP) {
Services.obs.removeObserver(this, TOPIC_DELAYED_STARTUP);
this._observing = false;
this._startTimer();
} else if (aTopic == "timer-callback") {
this._timer = null;
this._startPreloader();
}
},
_startTimer: function Initializer_startTimer() {
this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._timer.init(this, PRELOADER_INIT_DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT);
},
_startPreloader: function Initializer_startPreloader() {
Preferences.init();
if (Preferences.enabled) {
HiddenBrowser.create();
}
}
};
let Preferences = {
_enabled: null,
_branch: null,
_url: null,
get enabled() {
if (this._enabled === null) {
this._enabled = this._branch.getBoolPref("preload") &&
!this._branch.prefHasUserValue("url") &&
this.url && this.url != "about:blank";
}
return this._enabled;
},
get url() {
if (this._url === null) {
this._url = this._branch.getCharPref("url");
}
return this._url;
},
init: function Preferences_init() {
this._branch = Services.prefs.getBranch(PREF_BRANCH);
this._branch.addObserver("", this, false);
},
uninit: function Preferences_uninit() {
if (this._branch) {
this._branch.removeObserver("", this);
this._branch = null;
}
},
observe: function Preferences_observe(aSubject, aTopic, aData) {
let {url, enabled} = this;
this._url = this._enabled = null;
if (enabled && !this.enabled) {
HiddenBrowser.destroy();
} else if (!enabled && this.enabled) {
HiddenBrowser.create();
} else if (this._browser && url != this.url) {
HiddenBrowser.update(this.url);
}
},
};
let HiddenBrowser = {
get isPreloaded() {
return this._browser &&
this._browser.contentDocument &&
this._browser.contentDocument.readyState == "complete" &&
this._browser.currentURI.spec == Preferences.url;
},
swapWithNewTab: function HiddenBrowser_swapWithNewTab(aTab) {
if (this.isPreloaded) {
let tabbrowser = aTab.ownerDocument.defaultView.gBrowser;
if (tabbrowser) {
tabbrowser.swapNewTabWithBrowser(aTab, this._browser);
}
}
},
create: function HiddenBrowser_create() {
HostFrame.getFrame(function (aFrame) {
let doc = aFrame.document;
this._browser = doc.createElementNS(XUL_NS, "browser");
this._browser.setAttribute("type", "content");
this._browser.setAttribute("src", Preferences.url);
doc.documentElement.appendChild(this._browser);
}.bind(this));
},
update: function HiddenBrowser_update(aURL) {
this._browser.setAttribute("src", aURL);
},
destroy: function HiddenBrowser_destroy() {
if (this._browser) {
this._browser.parentNode.removeChild(this._browser);
this._browser = null;
}
}
};
let HostFrame = {
_listener: null,
_privilegedFrame: null,
_privilegedContentTypes: {
"application/vnd.mozilla.xul+xml": true,
"application/xhtml+xml": true
},
get _frame() {
delete this._frame;
return this._frame = Services.appShell.hiddenDOMWindow;
},
get _isReady() {
let readyState = this._frame.document.readyState;
return (readyState == "complete" || readyState == "interactive");
},
get _isPrivileged() {
return (this._frame.location.protocol == "chrome:" &&
this._frame.document.contentType in this._privilegedContentTypes);
},
getFrame: function HostFrame_getFrame(aCallback) {
if (this._isReady && !this._isPrivileged) {
this._createPrivilegedFrame();
}
if (this._isReady) {
aCallback(this._frame);
} else {
this._waitUntilLoaded(aCallback);
}
},
destroy: function HostFrame_destroy() {
delete this._frame;
this._listener = null;
},
_createPrivilegedFrame: function HostFrame_createPrivilegedFrame() {
let doc = this._frame.document;
let iframe = doc.createElement("iframe");
iframe.setAttribute("src", "chrome://browser/content/newtab/preload.xhtml");
doc.documentElement.appendChild(iframe);
this._frame = iframe.contentWindow;
},
_waitUntilLoaded: function HostFrame_waitUntilLoaded(aCallback) {
this._listener = new HiddenWindowLoadListener(this._frame, function () {
HostFrame.getFrame(aCallback);
});
}
};
function HiddenWindowLoadListener(aWindow, aCallback) {
this._window = aWindow;
this._callback = aCallback;
let docShell = Services.appShell.hiddenWindow.docShell;
this._webProgress = docShell.QueryInterface(Ci.nsIWebProgress);
this._webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_ALL);
}
HiddenWindowLoadListener.prototype = {
_window: null,
_callback: null,
_webProgress: null,
_destroy: function HiddenWindowLoadListener_destroy() {
this._webProgress.removeProgressListener(this);
this._window = null;
this._callback = null;
this._webProgress = null;
},
onStateChange:
function HiddenWindowLoadListener_onStateChange(aWebProgress, aRequest,
aStateFlags, aStatus) {
if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW &&
this._window == aWebProgress.DOMWindow) {
this._callback();
this._destroy();
}
},
onStatusChange: function () {},
onLocationChange: function () {},
onProgressChange: function () {},
onSecurityChange: function () {},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference])
};