From 55774536686f9a45c5910768d0de5e2dc0e3303e Mon Sep 17 00:00:00 2001 From: Dave Townsend Date: Fri, 13 Dec 2019 00:52:27 +0000 Subject: [PATCH] Bug 1602173: Create a SiteSpecificBrowser class and expose data about it in content processes. r=Gijs Needed for providing synchronous responses to load requests. Differential Revision: https://phabricator.services.mozilla.com/D56285 --HG-- extra : moz-landing-system : lando --- .../ssb/SiteSpecificBrowserService.jsm | 204 ++++++++++++++++-- browser/components/ssb/content/ssb.js | 11 +- 2 files changed, 200 insertions(+), 15 deletions(-) diff --git a/browser/components/ssb/SiteSpecificBrowserService.jsm b/browser/components/ssb/SiteSpecificBrowserService.jsm index 9935b9a187a8..6ff29fa5068e 100644 --- a/browser/components/ssb/SiteSpecificBrowserService.jsm +++ b/browser/components/ssb/SiteSpecificBrowserService.jsm @@ -2,13 +2,200 @@ * 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/. */ -var EXPORTED_SYMBOLS = ["SiteSpecificBrowserService", "SSBCommandLineHandler"]; +/** + * A Site Specific Browser intends to allow the user to navigate through the + * chosen site in the SSB UI. Any attempt to load something outside the site + * should be loaded in a normal browser. In order to achieve this we have to use + * various APIs to listen for attempts to load new content and take appropriate + * action. Often this requires returning synchronous responses to method calls + * in content processes and will require data about the SSB in order to respond + * correctly. Here we implement an architecture to support that: + * + * In the main process the SiteSpecificBrowser class implements all the + * functionality involved with managing an SSB. All content processes can + * synchronously retrieve a matching SiteSpecificBrowserBase that has enough + * data about the SSB in order to be able to respond to load requests + * synchronously. To support this we give every SSB a unique ID (UUID based) + * and the appropriate data is shared via sharedData. Once created the ID can be + * used to retrieve the SiteSpecificBrowser instance in the main process or + * SiteSpecificBrowserBase instance in any content process. + */ + +var EXPORTED_SYMBOLS = [ + "SiteSpecificBrowserService", + "SiteSpecificBrowserBase", + "SiteSpecificBrowser", + "SSBCommandLineHandler", +]; const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" ); +function uuid() { + return Cc["@mozilla.org/uuid-generator;1"] + .getService(Ci.nsIUUIDGenerator) + .generateUUID() + .toString(); +} + +const sharedDataKey = id => `SiteSpecificBrowserBase:${id}`; + +const IS_MAIN_PROCESS = + Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT; + +/** + * Maintains an ID -> SSB mapping in the main process. Content processes should + * use sharedData to get a SiteSpecificBrowserBase. + * + * We do not currently expire data from here so once created an SSB instance + * lives for the lifetime of the application. The expectation is that the + * numbers of different SSBs used will be low and the memory use will also + * be low. + */ +const SSBMap = new Map(); + +/** + * The base contains the data about an SSB instance needed in content processes. + * + * The only data needed currently is the URI used to launch the SSB. + */ +class SiteSpecificBrowserBase { + /** + * Creates a new SiteSpecificBrowserBase. Generally should only be called by + * code within this module. + * + * @param {nsIURI} uri the base URI for the SSB. + */ + constructor(uri) { + this._uri = uri; + } + + /** + * Gets the SiteSpecifcBrowserBase for an ID. If this is the main process this + * will instead return the SiteSpecificBrowser instance itself but generally + * don't call this from the main process. + * + * The returned object is not "live" and will not be updated with any + * configuration changes from the main process so do not cache this, get it + * when needed and then discard. + * + * @param {string} id the SSB ID. + * @return {SiteSpecificBrowserBase|null} the instance if it exists. + */ + static get(id) { + if (IS_MAIN_PROCESS) { + return SiteSpecificBrowser.get(id); + } + + let key = sharedDataKey(id); + if (!Services.cpmm.sharedData.has(key)) { + return null; + } + + let uri = Services.io.newURI(Services.cpmm.sharedData.get(key)); + return new SiteSpecificBrowserBase(uri); + } + + /** + * Checks whether the given URI is considered to be a part of this SSB or not. + * Any URIs that return false should be loaded in a normal browser. + * + * @param {nsIURI} uri the URI to check. + * @return {boolean} whether this SSB can load the URI. + */ + canLoad(uri) { + // Always allow loading about:blank as it is the initial page for iframes. + if (uri.spec == "about:blank") { + return true; + } + + // A simplistic check. Is this a uri from the same origin. + return uri.prePath == this._uri.prePath; + } +} + +/** + * The SSB instance used in the main process. + */ +class SiteSpecificBrowser extends SiteSpecificBrowserBase { + /** + * Creates a new SiteSpecificBrowser. Generally should only be called by + * code within this module. + * + * @param {string} id the SSB's unique ID. + * @param {nsIURI} uri the base URI for the SSB. + */ + constructor(id, uri) { + if (!IS_MAIN_PROCESS) { + throw new Error( + "SiteSpecificBrowser instances are only available in the main process." + ); + } + + super(uri); + this._id = id; + + // Cache the SSB for retrieval. + SSBMap.set(id, this); + + // Cache the data that the content processes need. + Services.ppmm.sharedData.set(sharedDataKey(id), this._uri.spec); + Services.ppmm.sharedData.flush(); + } + + /** + * Gets the SiteSpecifcBrowser for an ID. Can only be called from the main + * process. + * + * @param {string} id the SSB ID. + * @return {SiteSpecificBrowser|null} the instance if it exists. + */ + static get(id) { + if (!IS_MAIN_PROCESS) { + throw new Error( + "SiteSpecificBrowser instances are only available in the main process." + ); + } + + return SSBMap.get(id); + } + + /** + * The SSB's ID. + */ + get id() { + return this._id; + } + + /** + * The default URI to load. + */ + get startURI() { + return this._uri; + } + + /** + * Launches a SSB by opening the necessary UI. + */ + launch() { + let sa = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); + let idstr = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString + ); + idstr.data = this.id; + sa.appendElement(idstr); + Services.ww.openWindow( + null, + "chrome://browser/content/ssb/ssb.html", + "_blank", + "chrome,dialog=no,all", + sa + ); + } +} + const SiteSpecificBrowserService = { /** * Given a URI launches the SSB UI to display it. @@ -26,19 +213,8 @@ const SiteSpecificBrowserService = { ); } - let sa = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); - let uristr = Cc["@mozilla.org/supports-string;1"].createInstance( - Ci.nsISupportsString - ); - uristr.data = uri.spec; - sa.appendElement(uristr); - Services.ww.openWindow( - null, - "chrome://browser/content/ssb/ssb.html", - "_blank", - "chrome,dialog=no,all", - sa - ); + let ssb = new SiteSpecificBrowser(uuid(), uri); + ssb.launch(); }, }; diff --git a/browser/components/ssb/content/ssb.js b/browser/components/ssb/content/ssb.js index 5abbd568d510..2d63adcfcb6b 100644 --- a/browser/components/ssb/content/ssb.js +++ b/browser/components/ssb/content/ssb.js @@ -2,15 +2,24 @@ * 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/. */ +ChromeUtils.defineModuleGetter( + this, + "SiteSpecificBrowser", + "resource:///modules/SiteSpecificBrowserService.jsm" +); + let gSSBBrowser = null; +let gSSB = null; function init() { + gSSB = SiteSpecificBrowser.get(window.arguments[0]); + gSSBBrowser = document.createXULElement("browser"); gSSBBrowser.setAttribute("id", "browser"); gSSBBrowser.setAttribute("type", "content"); gSSBBrowser.setAttribute("remote", "true"); document.getElementById("browser-container").appendChild(gSSBBrowser); - gSSBBrowser.src = window.arguments[0]; + gSSBBrowser.src = gSSB.startURI.spec; } window.addEventListener("load", init, true);