Bug 1518863: Part 2 - Delay extension protocol requests until extension is ready. r=aswan, a=lizzard
☠☠ backed out by e1652912e1d5 ☠ ☠
authorKris Maglione <maglione.k@gmail.com>
Wed, 27 Feb 2019 11:54:31 -0800
changeset 516326 4d96aa8bbf572eba63bc7d0d6ccfee494869702b
parent 516325 b7e78201e26c6bcfc297f63b89454cde29a09bce
child 516327 d94de0e08f52bbfb54a140462b0a4d2fb5e84477
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan, lizzard
bugs1518863
milestone66.0
Bug 1518863: Part 2 - Delay extension protocol requests until extension is ready. r=aswan, a=lizzard We don't want extension protocol load requests to begin loading until the extension is far enough initialized to run code. If we load it before then, the extension framework will either fail to recognize the extension entirely, or may begin running its scripts in an incomplete environment. This patch adds a slow path which adds a promise handler and creats a stub channel only in the case when the extension is not ready. In the normal, already-initialized case, we take the more direct path. Differential Revision: https://phabricator.services.mozilla.com/D21447
dom/promise/Promise.cpp
netwerk/protocol/res/ExtensionProtocolHandler.cpp
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -229,17 +229,21 @@ void Promise::Then(JSContext* aCx,
   }
 
   aRetval.setObject(*retval);
 }
 
 void PromiseNativeThenHandlerBase::ResolvedCallback(
     JSContext* aCx, JS::Handle<JS::Value> aValue) {
   RefPtr<Promise> promise = CallResolveCallback(aCx, aValue);
-  mPromise->MaybeResolve(promise);
+  if (promise) {
+    mPromise->MaybeResolve(promise);
+  } else {
+    mPromise->MaybeResolve(JS::UndefinedHandleValue);
+  }
 }
 
 void PromiseNativeThenHandlerBase::RejectedCallback(
     JSContext* aCx, JS::Handle<JS::Value> aValue) {
   mPromise->MaybeReject(aCx, aValue);
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(PromiseNativeThenHandlerBase)
--- a/netwerk/protocol/res/ExtensionProtocolHandler.cpp
+++ b/netwerk/protocol/res/ExtensionProtocolHandler.cpp
@@ -3,16 +3,18 @@
 /* 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/. */
 
 #include "ExtensionProtocolHandler.h"
 
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Promise-inl.h"
 #include "mozilla/ExtensionPolicyService.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/ipc/IPCStreamUtils.h"
 #include "mozilla/ipc/URIParams.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "mozilla/net/NeckoChild.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/ResultExtensions.h"
@@ -43,16 +45,17 @@
 #include "SimpleChannel.h"
 
 #if defined(XP_WIN)
 #  include "nsILocalFileWin.h"
 #  include "WinUtils.h"
 #endif
 
 #define EXTENSION_SCHEME "moz-extension"
+using mozilla::dom::Promise;
 using mozilla::ipc::FileDescriptor;
 using OptionalIPCStream = mozilla::ipc::OptionalIPCStream;
 
 namespace mozilla {
 
 namespace net {
 
 using extensions::URLInfo;
@@ -408,72 +411,125 @@ Result<Ok, nsresult> ExtensionProtocolHa
     return SubstituteRemoteJarChannel(aURI, aLoadInfo, resolvedSpec, aRetVal);
   }
 
   // Only unpacked resource files and JAR files are remoted.
   // No other moz-extension loads should be reading from the filesystem.
   return Ok();
 }
 
+void OpenWhenReady(
+    Promise* aPromise, nsIStreamListener* aListener, nsIChannel* aChannel,
+    const std::function<nsresult(nsIStreamListener*, nsIChannel*)>& aCallback) {
+  nsCOMPtr<nsIStreamListener> listener(aListener);
+  nsCOMPtr<nsIChannel> channel(aChannel);
+
+  Unused << aPromise->ThenWithCycleCollectedArgs(
+      [channel, aCallback](
+          JSContext* aCx, JS::HandleValue aValue,
+          nsIStreamListener* aListener) -> already_AddRefed<Promise> {
+        nsresult rv = aCallback(aListener, channel);
+        if (NS_FAILED(rv)) {
+          CancelRequest(aListener, channel, rv);
+        }
+        return nullptr;
+      },
+      listener);
+}
+
 nsresult ExtensionProtocolHandler::SubstituteChannel(nsIURI* aURI,
                                                      nsILoadInfo* aLoadInfo,
                                                      nsIChannel** result) {
   if (mUseRemoteFileChannels) {
     MOZ_TRY(SubstituteRemoteChannel(aURI, aLoadInfo, result));
   }
 
+  auto* policy = EPS().GetByURL(aURI);
+  NS_ENSURE_TRUE(policy, NS_ERROR_UNEXPECTED);
+
+  RefPtr<dom::Promise> readyPromise(policy->ReadyPromise());
+
   nsresult rv;
   nsCOMPtr<nsIURL> url = do_QueryInterface(aURI, &rv);
   MOZ_TRY(rv);
 
   nsAutoCString ext;
   MOZ_TRY(url->GetFileExtension(ext));
-  if (!ext.LowerCaseEqualsLiteral("css")) {
+
+  nsCOMPtr<nsIChannel> channel;
+  bool haveLoadInfo = aLoadInfo;
+  if (ext.LowerCaseEqualsLiteral("css")) {
+    // Filter CSS files to replace locale message tokens with localized strings.
+    static const auto convert = [haveLoadInfo](nsIStreamListener* listener,
+                                               nsIChannel* channel,
+                                               nsIChannel* origChannel) -> nsresult {
+      nsresult rv;
+      nsCOMPtr<nsIStreamConverterService> convService =
+          do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
+      MOZ_TRY(rv);
+
+      nsCOMPtr<nsIURI> uri;
+      MOZ_TRY(channel->GetURI(getter_AddRefs(uri)));
+
+      const char* kFromType = "application/vnd.mozilla.webext.unlocalized";
+      const char* kToType = "text/css";
+
+      nsCOMPtr<nsIStreamListener> converter;
+      MOZ_TRY(convService->AsyncConvertData(kFromType, kToType, listener, uri,
+                                            getter_AddRefs(converter)));
+
+      if (haveLoadInfo) {
+        return origChannel->AsyncOpen2(converter);
+      }
+      return origChannel->AsyncOpen(converter, nullptr);
+    };
+
+    channel = NS_NewSimpleChannel(
+        aURI, aLoadInfo, *result,
+        [readyPromise](nsIStreamListener* listener, nsIChannel* channel,
+                       nsIChannel* origChannel) -> RequestOrReason {
+          if (readyPromise) {
+            nsCOMPtr<nsIChannel> chan(channel);
+            OpenWhenReady(
+                readyPromise, listener, origChannel,
+                [chan](nsIStreamListener* aListener, nsIChannel* aChannel) {
+                  return convert(aListener, chan, aChannel);
+                });
+          } else {
+            MOZ_TRY(convert(listener, channel, origChannel));
+          }
+          return RequestOrReason(origChannel);
+        });
+  } else if (readyPromise) {
+    channel = NS_NewSimpleChannel(
+        aURI, aLoadInfo, *result,
+        [readyPromise, haveLoadInfo](nsIStreamListener* listener, nsIChannel* channel,
+                                     nsIChannel* origChannel) -> RequestOrReason {
+          OpenWhenReady(readyPromise, listener, origChannel,
+                        [haveLoadInfo](nsIStreamListener* aListener, nsIChannel* aChannel) {
+                          if (haveLoadInfo) {
+                            return aChannel->AsyncOpen2(aListener);
+                          }
+                          return aChannel->AsyncOpen(aListener, nullptr);
+                        });
+
+          return RequestOrReason(origChannel);
+        });
+  } else {
     return NS_OK;
   }
 
-  // Filter CSS files to replace locale message tokens with localized strings.
-
-  bool haveLoadInfo = aLoadInfo;
-  nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel(
-      aURI, aLoadInfo, *result,
-      [haveLoadInfo](nsIStreamListener* listener, nsIChannel* channel,
-                     nsIChannel* origChannel) -> RequestOrReason {
-        nsresult rv;
-        nsCOMPtr<nsIStreamConverterService> convService =
-            do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
-        MOZ_TRY(rv);
-
-        nsCOMPtr<nsIURI> uri;
-        MOZ_TRY(channel->GetURI(getter_AddRefs(uri)));
-
-        const char* kFromType = "application/vnd.mozilla.webext.unlocalized";
-        const char* kToType = "text/css";
-
-        nsCOMPtr<nsIStreamListener> converter;
-        MOZ_TRY(convService->AsyncConvertData(kFromType, kToType, listener, uri,
-                                              getter_AddRefs(converter)));
-        if (haveLoadInfo) {
-          MOZ_TRY(origChannel->AsyncOpen2(converter));
-        } else {
-          MOZ_TRY(origChannel->AsyncOpen(converter, nullptr));
-        }
-
-        return RequestOrReason(origChannel);
-      });
   NS_ENSURE_TRUE(channel, NS_ERROR_OUT_OF_MEMORY);
-
   if (aLoadInfo) {
     nsCOMPtr<nsILoadInfo> loadInfo =
         static_cast<LoadInfo*>(aLoadInfo)->CloneForNewRequest();
     (*result)->SetLoadInfo(loadInfo);
   }
 
   channel.swap(*result);
-
   return NS_OK;
 }
 
 Result<Ok, nsresult> ExtensionProtocolHandler::AllowExternalResource(
     nsIFile* aExtensionDir, nsIFile* aRequestedFile, bool* aResult) {
   MOZ_ASSERT(!IsNeckoChild());
   MOZ_ASSERT(aResult);
   *aResult = false;