Bug 1518863: Part 1 - Add ready promise to WebExtensionPolicy. r=aswan
authorKris Maglione <maglione.k@gmail.com>
Wed, 27 Feb 2019 13:26:37 -0800
changeset 519743 1ea765143cded7a55ff89ba5302db2bb63aac068
parent 519742 00c8f747679c38145ed1f4f8eb11253dea0ba068
child 519744 606364d19a36c4f6598b707bdd8fbc646ce88075
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1518863
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1518863: Part 1 - Add ready promise to WebExtensionPolicy. r=aswan This adds a promise member to the stub WebExtensionPolicy objects that we create early during extension initialization which resolves when the real extension instance is ready to load content. This promise will be used by the extension protocol handler to delay loads until the framework is ready to handle them. Differential Revision: https://phabricator.services.mozilla.com/D21445
dom/chrome-webidl/WebExtensionPolicy.webidl
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/WebExtensionPolicy.cpp
toolkit/components/extensions/WebExtensionPolicy.h
--- a/dom/chrome-webidl/WebExtensionPolicy.webidl
+++ b/dom/chrome-webidl/WebExtensionPolicy.webidl
@@ -181,16 +181,30 @@ interface WebExtensionPolicy {
    * for it.
    */
   static WebExtensionPolicy? getByURI(URI uri);
 
   /**
    * Returns true if the URI is restricted for any extension.
    */
   static boolean isRestrictedURI(URI uri);
+
+  /**
+   * When present, the extension is not yet ready to load URLs. In that case,
+   * this policy object is a stub, and the attribute contains a promise which
+   * resolves to a new, non-stub policy object when the extension is ready.
+   *
+   * This may be used to delay operations, such as loading extension pages,
+   * which depend on extensions being fully initialized.
+   *
+   * Note: This will always be either a Promise<WebExtensionPolicy> or null,
+   * but the WebIDL grammar does not allow us to specify a nullable Promise
+   * type.
+   */
+  readonly attribute object? readyPromise;
 };
 
 dictionary WebExtensionInit {
   required DOMString id;
 
   required ByteString mozExtensionHostname;
 
   required DOMString baseURL;
@@ -205,9 +219,11 @@ dictionary WebExtensionInit {
 
   sequence<MatchGlobOrString> webAccessibleResources = [];
 
   sequence<WebExtensionContentScriptInit> contentScripts = [];
 
   DOMString? contentSecurityPolicy = null;
 
   sequence<DOMString>? backgroundScripts = null;
+
+  Promise<WebExtensionPolicy> readyPromise;
 };
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -1849,35 +1849,37 @@ class Extension extends ExtensionData {
       ExtensionPermissions.add(addonData.id, {permissions: [PRIVATE_ALLOWED_PERMISSION], origins: []});
       await StartupCache.clearAddonData(addonData.id);
     }
   }
 
   async startup() {
     this.state = "Startup";
 
+    let resolveReadyPromise;
+    let readyPromise = new Promise(resolve => {
+      resolveReadyPromise = resolve;
+    });
+
     // Create a temporary policy object for the devtools and add-on
     // manager callers that depend on it being available early.
     this.policy = new WebExtensionPolicy({
       id: this.id,
       mozExtensionHostname: this.uuid,
-      baseURL: this.baseURI.spec,
+      baseURL: this.resourceURL,
       allowedOrigins: new MatchPatternSet([]),
       localizeCallback() {},
+      readyPromise,
     });
+
+    this.policy.extension = this;
     if (!WebExtensionPolicy.getByID(this.id)) {
-      // The add-on manager doesn't handle async startup and shutdown,
-      // so during upgrades and add-on restarts, startup() gets called
-      // before the last shutdown has completed, and this fails when
-      // there's another active add-on with the same ID.
       this.policy.active = true;
     }
 
-    this.policy.extension = this;
-
     ExtensionTelemetry.extensionStartup.stopwatchStart(this);
     try {
       this.state = "Startup: Loading manifest";
       await this.loadManifest();
       this.state = "Startup: Loaded manifest";
 
       if (!this.hasShutdown) {
         this.state = "Startup: Init locale";
@@ -1925,16 +1927,18 @@ class Extension extends ExtensionData {
         if (!ExtensionStorageIDB.isBackendEnabled) {
           this.setSharedData("storageIDBBackend", false);
         } else if (ExtensionStorageIDB.isMigratedExtension(this)) {
           this.setSharedData("storageIDBBackend", true);
           this.setSharedData("storageIDBPrincipal", ExtensionStorageIDB.getStoragePrincipal(this));
         }
       }
 
+      resolveReadyPromise(this.policy);
+
       // The "startup" Management event sent on the extension instance itself
       // is emitted just before the Management "startup" event,
       // and it is used to run code that needs to be executed before
       // any of the "startup" listeners.
       this.emit("startup", this);
 
       let state = new Set(["Emit startup", "Run manifest"]);
       this.state = `Startup: ${Array.from(state)}`;
--- a/toolkit/components/extensions/WebExtensionPolicy.cpp
+++ b/toolkit/components/extensions/WebExtensionPolicy.cpp
@@ -172,16 +172,20 @@ WebExtensionPolicy::WebExtensionPolicy(G
     RefPtr<WebExtensionContentScript> contentScript =
         new WebExtensionContentScript(aGlobal, *this, scriptInit, aRv);
     if (aRv.Failed()) {
       return;
     }
     mContentScripts.AppendElement(std::move(contentScript));
   }
 
+  if (aInit.mReadyPromise.WasPassed()) {
+    mReadyPromise = &aInit.mReadyPromise.Value();
+  }
+
   nsresult rv = NS_NewURI(getter_AddRefs(mBaseURI), aInit.mBaseURL);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
   }
 }
 
 already_AddRefed<WebExtensionPolicy> WebExtensionPolicy::Constructor(
     GlobalObject& aGlobal, const WebExtensionInit& aInit, ErrorResult& aRv) {
@@ -454,16 +458,24 @@ bool WebExtensionPolicy::CanAccessWindow
     return true;
   }
   // match browsing mode with policy
   nsIDocShell* docShell = aWindow.get()->GetDocShell();
   nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell);
   return !(loadContext && loadContext->UsePrivateBrowsing());
 }
 
+void WebExtensionPolicy::GetReadyPromise(JSContext* aCx, JS::MutableHandleObject aResult) const {
+  if (mReadyPromise) {
+    aResult.set(mReadyPromise->PromiseObj());
+  } else {
+    aResult.set(nullptr);
+  }
+}
+
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebExtensionPolicy, mParent,
                                       mLocalizeCallback, mHostPermissions,
                                       mWebAccessiblePaths, mContentScripts)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebExtensionPolicy)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
--- a/toolkit/components/extensions/WebExtensionPolicy.h
+++ b/toolkit/components/extensions/WebExtensionPolicy.h
@@ -17,16 +17,20 @@
 #include "mozilla/Result.h"
 #include "mozilla/WeakPtr.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsISupports.h"
 #include "nsWrapperCache.h"
 
 namespace mozilla {
+namespace dom {
+class Promise;
+}  // namespace dom
+
 namespace extensions {
 
 using dom::WebExtensionInit;
 using dom::WebExtensionLocalizeCallback;
 
 class DocInfo;
 class WebExtensionContentScript;
 
@@ -126,16 +130,19 @@ class WebExtensionPolicy final : public 
     return mAllowPrivateBrowsingByDefault ||
            HasPermission(nsGkAtoms::privateBrowsingAllowedPermission);
   }
 
   bool CanAccessContext(nsILoadContext* aContext) const;
 
   bool CanAccessWindow(const dom::WindowProxyHolder& aWindow) const;
 
+  void GetReadyPromise(JSContext* aCx, JS::MutableHandleObject aResult) const;
+  dom::Promise* ReadyPromise() const { return mReadyPromise; }
+
   static void GetActiveExtensions(
       dom::GlobalObject& aGlobal,
       nsTArray<RefPtr<WebExtensionPolicy>>& aResults);
 
   static already_AddRefed<WebExtensionPolicy> GetByID(
       dom::GlobalObject& aGlobal, const nsAString& aID);
 
   static already_AddRefed<WebExtensionPolicy> GetByHostname(
@@ -182,14 +189,16 @@ class WebExtensionPolicy final : public 
 
   RefPtr<AtomSet> mPermissions;
   RefPtr<MatchPatternSet> mHostPermissions;
   MatchGlobSet mWebAccessiblePaths;
 
   dom::Nullable<nsTArray<nsString>> mBackgroundScripts;
 
   nsTArray<RefPtr<WebExtensionContentScript>> mContentScripts;
+
+  RefPtr<dom::Promise> mReadyPromise;
 };
 
 }  // namespace extensions
 }  // namespace mozilla
 
 #endif  // mozilla_extensions_WebExtensionPolicy_h