1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/modules/CustomizationTabPreloader.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,217 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +this.EXPORTED_SYMBOLS = ["CustomizationTabPreloader"]; 1.11 + 1.12 +const Cu = Components.utils; 1.13 +const Cc = Components.classes; 1.14 +const Ci = Components.interfaces; 1.15 + 1.16 +Cu.import("resource://gre/modules/Services.jsm"); 1.17 +Cu.import("resource://gre/modules/Promise.jsm"); 1.18 + 1.19 +const HTML_NS = "http://www.w3.org/1999/xhtml"; 1.20 +const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; 1.21 +const XUL_PAGE = "data:application/vnd.mozilla.xul+xml;charset=utf-8,<window%20id='win'/>"; 1.22 +const CUSTOMIZATION_URL = "about:customizing"; 1.23 + 1.24 +// The interval between swapping in a preload docShell and kicking off the 1.25 +// next preload in the background. 1.26 +const PRELOADER_INTERVAL_MS = 600; 1.27 + 1.28 +function createTimer(obj, delay) { 1.29 + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 1.30 + timer.init(obj, delay, Ci.nsITimer.TYPE_ONE_SHOT); 1.31 + return timer; 1.32 +} 1.33 + 1.34 +function clearTimer(timer) { 1.35 + if (timer) { 1.36 + timer.cancel(); 1.37 + } 1.38 + return null; 1.39 +} 1.40 + 1.41 +this.CustomizationTabPreloader = { 1.42 + uninit: function () { 1.43 + CustomizationTabPreloaderInternal.uninit(); 1.44 + }, 1.45 + 1.46 + newTab: function (aTab) { 1.47 + return CustomizationTabPreloaderInternal.newTab(aTab); 1.48 + }, 1.49 + 1.50 + /** 1.51 + * ensurePreloading starts the preloading of the about:customizing 1.52 + * content page. This function is idempotent (until a call to uninit), 1.53 + * so multiple calls to it are fine. 1.54 + */ 1.55 + ensurePreloading: function() { 1.56 + CustomizationTabPreloaderInternal.ensurePreloading(); 1.57 + }, 1.58 +}; 1.59 + 1.60 +Object.freeze(CustomizationTabPreloader); 1.61 + 1.62 +this.CustomizationTabPreloaderInternal = { 1.63 + _browser: null, 1.64 + 1.65 + uninit: function () { 1.66 + HostFrame.destroy(); 1.67 + 1.68 + if (this._browser) { 1.69 + this._browser.destroy(); 1.70 + this._browser = null; 1.71 + } 1.72 + }, 1.73 + 1.74 + newTab: function (aTab) { 1.75 + let win = aTab.ownerDocument.defaultView; 1.76 + if (win.gBrowser && this._browser) { 1.77 + return this._browser.swapWithNewTab(aTab); 1.78 + } 1.79 + 1.80 + return false; 1.81 + }, 1.82 + 1.83 + ensurePreloading: function () { 1.84 + if (!this._browser) { 1.85 + this._browser = new HiddenBrowser(); 1.86 + } 1.87 + } 1.88 +}; 1.89 + 1.90 +function HiddenBrowser() { 1.91 + this._createBrowser(); 1.92 +} 1.93 + 1.94 +HiddenBrowser.prototype = { 1.95 + _timer: null, 1.96 + 1.97 + get isPreloaded() { 1.98 + return this._browser && 1.99 + this._browser.contentDocument && 1.100 + this._browser.contentDocument.readyState === "complete" && 1.101 + this._browser.currentURI.spec === CUSTOMIZATION_URL; 1.102 + }, 1.103 + 1.104 + swapWithNewTab: function (aTab) { 1.105 + if (!this.isPreloaded || this._timer) { 1.106 + return false; 1.107 + } 1.108 + 1.109 + let win = aTab.ownerDocument.defaultView; 1.110 + let tabbrowser = win.gBrowser; 1.111 + 1.112 + if (!tabbrowser) { 1.113 + return false; 1.114 + } 1.115 + 1.116 + // Swap docShells. 1.117 + tabbrowser.swapNewTabWithBrowser(aTab, this._browser); 1.118 + 1.119 + // Load all default frame scripts attached to the target window. 1.120 + let mm = aTab.linkedBrowser.messageManager; 1.121 + let scripts = win.messageManager.getDelayedFrameScripts(); 1.122 + Array.forEach(scripts, ([script, runGlobal]) => mm.loadFrameScript(script, true, runGlobal)); 1.123 + 1.124 + // Remove the browser, it will be recreated by a timer. 1.125 + this._removeBrowser(); 1.126 + 1.127 + // Start a timer that will kick off preloading the next page. 1.128 + this._timer = createTimer(this, PRELOADER_INTERVAL_MS); 1.129 + 1.130 + // Signal that we swapped docShells. 1.131 + return true; 1.132 + }, 1.133 + 1.134 + observe: function () { 1.135 + this._timer = null; 1.136 + 1.137 + // Start pre-loading the customization page. 1.138 + this._createBrowser(); 1.139 + }, 1.140 + 1.141 + destroy: function () { 1.142 + this._removeBrowser(); 1.143 + this._timer = clearTimer(this._timer); 1.144 + }, 1.145 + 1.146 + _createBrowser: function () { 1.147 + HostFrame.get().then(aFrame => { 1.148 + let doc = aFrame.document; 1.149 + this._browser = doc.createElementNS(XUL_NS, "browser"); 1.150 + this._browser.setAttribute("type", "content"); 1.151 + this._browser.setAttribute("src", CUSTOMIZATION_URL); 1.152 + this._browser.style.width = "400px"; 1.153 + this._browser.style.height = "400px"; 1.154 + doc.getElementById("win").appendChild(this._browser); 1.155 + }); 1.156 + }, 1.157 + 1.158 + _removeBrowser: function () { 1.159 + if (this._browser) { 1.160 + this._browser.remove(); 1.161 + this._browser = null; 1.162 + } 1.163 + } 1.164 +}; 1.165 + 1.166 +let HostFrame = { 1.167 + _frame: null, 1.168 + _deferred: null, 1.169 + 1.170 + get hiddenDOMDocument() { 1.171 + return Services.appShell.hiddenDOMWindow.document; 1.172 + }, 1.173 + 1.174 + get isReady() { 1.175 + return this.hiddenDOMDocument.readyState === "complete"; 1.176 + }, 1.177 + 1.178 + get: function () { 1.179 + if (!this._deferred) { 1.180 + this._deferred = Promise.defer(); 1.181 + this._create(); 1.182 + } 1.183 + 1.184 + return this._deferred.promise; 1.185 + }, 1.186 + 1.187 + destroy: function () { 1.188 + if (this._frame) { 1.189 + if (!Cu.isDeadWrapper(this._frame)) { 1.190 + this._frame.removeEventListener("load", this, true); 1.191 + this._frame.remove(); 1.192 + } 1.193 + 1.194 + this._frame = null; 1.195 + this._deferred = null; 1.196 + } 1.197 + }, 1.198 + 1.199 + handleEvent: function () { 1.200 + let contentWindow = this._frame.contentWindow; 1.201 + if (contentWindow.location.href === XUL_PAGE) { 1.202 + this._frame.removeEventListener("load", this, true); 1.203 + this._deferred.resolve(contentWindow); 1.204 + } else { 1.205 + contentWindow.location = XUL_PAGE; 1.206 + } 1.207 + }, 1.208 + 1.209 + _create: function () { 1.210 + if (this.isReady) { 1.211 + let doc = this.hiddenDOMDocument; 1.212 + this._frame = doc.createElementNS(HTML_NS, "iframe"); 1.213 + this._frame.addEventListener("load", this, true); 1.214 + doc.documentElement.appendChild(this._frame); 1.215 + } else { 1.216 + let flags = Ci.nsIThread.DISPATCH_NORMAL; 1.217 + Services.tm.currentThread.dispatch(() => this._create(), flags); 1.218 + } 1.219 + } 1.220 +};