browser/modules/CustomizationTabPreloader.jsm

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 this.EXPORTED_SYMBOLS = ["CustomizationTabPreloader"];
     9 const Cu = Components.utils;
    10 const Cc = Components.classes;
    11 const Ci = Components.interfaces;
    13 Cu.import("resource://gre/modules/Services.jsm");
    14 Cu.import("resource://gre/modules/Promise.jsm");
    16 const HTML_NS = "http://www.w3.org/1999/xhtml";
    17 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
    18 const XUL_PAGE = "data:application/vnd.mozilla.xul+xml;charset=utf-8,<window%20id='win'/>";
    19 const CUSTOMIZATION_URL = "about:customizing";
    21 // The interval between swapping in a preload docShell and kicking off the
    22 // next preload in the background.
    23 const PRELOADER_INTERVAL_MS = 600;
    25 function createTimer(obj, delay) {
    26   let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    27   timer.init(obj, delay, Ci.nsITimer.TYPE_ONE_SHOT);
    28   return timer;
    29 }
    31 function clearTimer(timer) {
    32   if (timer) {
    33     timer.cancel();
    34   }
    35   return null;
    36 }
    38 this.CustomizationTabPreloader = {
    39   uninit: function () {
    40     CustomizationTabPreloaderInternal.uninit();
    41   },
    43   newTab: function (aTab) {
    44     return CustomizationTabPreloaderInternal.newTab(aTab);
    45   },
    47   /**
    48    * ensurePreloading starts the preloading of the about:customizing
    49    * content page. This function is idempotent (until a call to uninit),
    50    * so multiple calls to it are fine.
    51    */
    52   ensurePreloading: function() {
    53     CustomizationTabPreloaderInternal.ensurePreloading();
    54   },
    55 };
    57 Object.freeze(CustomizationTabPreloader);
    59 this.CustomizationTabPreloaderInternal = {
    60   _browser: null,
    62   uninit: function () {
    63     HostFrame.destroy();
    65     if (this._browser) {
    66       this._browser.destroy();
    67       this._browser = null;
    68     }
    69   },
    71   newTab: function (aTab) {
    72     let win = aTab.ownerDocument.defaultView;
    73     if (win.gBrowser && this._browser) {
    74       return this._browser.swapWithNewTab(aTab);
    75     }
    77     return false;
    78   },
    80   ensurePreloading: function () {
    81     if (!this._browser) {
    82       this._browser = new HiddenBrowser();
    83     }
    84   }
    85 };
    87 function HiddenBrowser() {
    88   this._createBrowser();
    89 }
    91 HiddenBrowser.prototype = {
    92   _timer: null,
    94   get isPreloaded() {
    95     return this._browser &&
    96            this._browser.contentDocument &&
    97            this._browser.contentDocument.readyState === "complete" &&
    98            this._browser.currentURI.spec === CUSTOMIZATION_URL;
    99   },
   101   swapWithNewTab: function (aTab) {
   102     if (!this.isPreloaded || this._timer) {
   103       return false;
   104     }
   106     let win = aTab.ownerDocument.defaultView;
   107     let tabbrowser = win.gBrowser;
   109     if (!tabbrowser) {
   110       return false;
   111     }
   113     // Swap docShells.
   114     tabbrowser.swapNewTabWithBrowser(aTab, this._browser);
   116     // Load all default frame scripts attached to the target window.
   117     let mm = aTab.linkedBrowser.messageManager;
   118     let scripts = win.messageManager.getDelayedFrameScripts();
   119     Array.forEach(scripts, ([script, runGlobal]) => mm.loadFrameScript(script, true, runGlobal));
   121     // Remove the browser, it will be recreated by a timer.
   122     this._removeBrowser();
   124     // Start a timer that will kick off preloading the next page.
   125     this._timer = createTimer(this, PRELOADER_INTERVAL_MS);
   127     // Signal that we swapped docShells.
   128     return true;
   129   },
   131   observe: function () {
   132     this._timer = null;
   134     // Start pre-loading the customization page.
   135     this._createBrowser();
   136   },
   138   destroy: function () {
   139     this._removeBrowser();
   140     this._timer = clearTimer(this._timer);
   141   },
   143   _createBrowser: function () {
   144     HostFrame.get().then(aFrame => {
   145       let doc = aFrame.document;
   146       this._browser = doc.createElementNS(XUL_NS, "browser");
   147       this._browser.setAttribute("type", "content");
   148       this._browser.setAttribute("src", CUSTOMIZATION_URL);
   149       this._browser.style.width = "400px";
   150       this._browser.style.height = "400px";
   151       doc.getElementById("win").appendChild(this._browser);
   152     });
   153   },
   155   _removeBrowser: function () {
   156     if (this._browser) {
   157       this._browser.remove();
   158       this._browser = null;
   159     }
   160   }
   161 };
   163 let HostFrame = {
   164   _frame: null,
   165   _deferred: null,
   167   get hiddenDOMDocument() {
   168     return Services.appShell.hiddenDOMWindow.document;
   169   },
   171   get isReady() {
   172     return this.hiddenDOMDocument.readyState === "complete";
   173   },
   175   get: function () {
   176     if (!this._deferred) {
   177       this._deferred = Promise.defer();
   178       this._create();
   179     }
   181     return this._deferred.promise;
   182   },
   184   destroy: function () {
   185     if (this._frame) {
   186       if (!Cu.isDeadWrapper(this._frame)) {
   187         this._frame.removeEventListener("load", this, true);
   188         this._frame.remove();
   189       }
   191       this._frame = null;
   192       this._deferred = null;
   193     }
   194   },
   196   handleEvent: function () {
   197     let contentWindow = this._frame.contentWindow;
   198     if (contentWindow.location.href === XUL_PAGE) {
   199       this._frame.removeEventListener("load", this, true);
   200       this._deferred.resolve(contentWindow);
   201     } else {
   202       contentWindow.location = XUL_PAGE;
   203     }
   204   },
   206   _create: function () {
   207     if (this.isReady) {
   208       let doc = this.hiddenDOMDocument;
   209       this._frame = doc.createElementNS(HTML_NS, "iframe");
   210       this._frame.addEventListener("load", this, true);
   211       doc.documentElement.appendChild(this._frame);
   212     } else {
   213       let flags = Ci.nsIThread.DISPATCH_NORMAL;
   214       Services.tm.currentThread.dispatch(() => this._create(), flags);
   215     }
   216   }
   217 };

mercurial