Bug 1518863: Part 2 - Delay extension protocol requests until extension is ready. r=aswan, a=lizzard
authorKris Maglione <maglione.k@gmail.com>
Wed, 27 Feb 2019 11:54:31 -0800
changeset 516336 f754bdfd46155e680afee58b230a1e08cf2c3b14
parent 516335 29e17fe8593d1c8953e82e243767f0c0d9addad5
child 516337 24f6b74fe41c781c47c81eb69e737f60e0d0ca8f
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;