Bug 1348442: Part 1 - Allow loading preloaded stylesheets asynchronously. r=heycam draft
authorKris Maglione <maglione.k@gmail.com>
Sun, 19 Mar 2017 12:03:54 -0700
changeset 501329 4c019869b9eaa5d8d4a7a1518c172a346df20e79
parent 500864 d70f85b48971f442dd65c3016be6867b577903ef
child 501330 e75396bb9ec071396dbef3c0e6b66f6a52f329fe
push id49931
push usermaglione.k@gmail.com
push dateSun, 19 Mar 2017 23:22:27 +0000
reviewersheycam
bugs1348442
milestone55.0a1
Bug 1348442: Part 1 - Allow loading preloaded stylesheets asynchronously. r=heycam These changes allow us to asynchronously load pre-loaded stylesheets, in a way that's similar to ChromeUtils.compileScript. The new method returns a Promise which resolves to the preloaded wheet once it's finished loading. This will allow us to remove the last remaining use of synchronous channels in moz-extension: URLs. MozReview-Commit-ID: 7J52ff93YKT *** amend-1 MozReview-Commit-ID: Aq9YYZPTj1Z
layout/base/nsIStyleSheetService.idl
layout/base/nsStyleSheetService.cpp
layout/style/Loader.cpp
layout/style/Loader.h
layout/style/PreloadedStyleSheet.cpp
layout/style/PreloadedStyleSheet.h
--- a/layout/base/nsIStyleSheetService.idl
+++ b/layout/base/nsIStyleSheetService.idl
@@ -50,14 +50,22 @@ interface nsIStyleSheetService : nsISupp
   /**
    * Synchronously loads a style sheet from |sheetURI| and returns the
    * new style sheet object. Can be used with nsIDOMWindowUtils.addSheet.
    */
   nsIPreloadedStyleSheet preloadSheet(in nsIURI sheetURI,
                                       in unsigned long type);
 
   /**
+   * Asynchronously loads a style sheet from |sheetURI| and returns a Promise
+   * which resolves to the new style sheet object, which can be used with
+   * nsIDOMWindowUtils.addSheet, when it has completed loading.
+   */
+  [implicit_jscontext]
+  jsval preloadSheetAsync(in nsIURI sheetURI, in unsigned long type);
+
+  /**
    * Remove the style sheet at |sheetURI| from the list of style sheets
    * specified by |type|.  The removal takes effect immediately, even for
    * already-loaded documents.
    */
   void unregisterSheet(in nsIURI sheetURI, in unsigned long type);
 };
--- a/layout/base/nsStyleSheetService.cpp
+++ b/layout/base/nsStyleSheetService.cpp
@@ -10,16 +10,17 @@
 #include "mozilla/CSSStyleSheet.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/PreloadedStyleSheet.h"
 #include "mozilla/StyleSheet.h"
 #include "mozilla/StyleSheetInlines.h"
 #include "mozilla/Unused.h"
 #include "mozilla/css/Loader.h"
 #include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Promise.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "nsIURI.h"
 #include "nsCOMPtr.h"
 #include "nsICategoryManager.h"
 #include "nsISupportsPrimitives.h"
 #include "nsISimpleEnumerator.h"
 #include "nsNetUtil.h"
 #include "nsIConsoleService.h"
@@ -278,50 +279,92 @@ nsStyleSheetService::SheetRegistered(nsI
   NS_ENSURE_ARG_POINTER(sheetURI);
   NS_PRECONDITION(_retval, "Null out param");
 
   *_retval = (FindSheetByURI(aSheetType, sheetURI) >= 0);
 
   return NS_OK;
 }
 
+static nsresult
+GetParsingMode(uint32_t aSheetType, css::SheetParsingMode* aParsingMode)
+{
+  switch (aSheetType) {
+    case nsStyleSheetService::AGENT_SHEET:
+      *aParsingMode = css::eAgentSheetFeatures;
+      return NS_OK;
+
+    case nsStyleSheetService::USER_SHEET:
+      *aParsingMode = css::eUserSheetFeatures;
+      return NS_OK;
+
+    case nsStyleSheetService::AUTHOR_SHEET:
+      *aParsingMode = css::eAuthorSheetFeatures;
+      return NS_OK;
+
+    default:
+      NS_WARNING("invalid sheet type argument");
+      return NS_ERROR_INVALID_ARG;
+  }
+}
+
 NS_IMETHODIMP
 nsStyleSheetService::PreloadSheet(nsIURI* aSheetURI, uint32_t aSheetType,
                                   nsIPreloadedStyleSheet** aSheet)
 {
   NS_PRECONDITION(aSheet, "Null out param");
   NS_ENSURE_ARG_POINTER(aSheetURI);
 
-  *aSheet = nullptr;
+  css::SheetParsingMode parsingMode;
+  nsresult rv = GetParsingMode(aSheetType, &parsingMode);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  RefPtr<PreloadedStyleSheet> sheet;
+  rv = PreloadedStyleSheet::Create(aSheetURI, parsingMode,
+                                   getter_AddRefs(sheet));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = sheet->Preload();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  sheet.forget(aSheet);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStyleSheetService::PreloadSheetAsync(nsIURI* aSheetURI, uint32_t aSheetType,
+                                       JSContext* aCx,
+                                       JS::MutableHandleValue aRval)
+{
+  NS_ENSURE_ARG_POINTER(aSheetURI);
 
   css::SheetParsingMode parsingMode;
-  switch (aSheetType) {
-    case AGENT_SHEET:
-      parsingMode = css::eAgentSheetFeatures;
-      break;
+  nsresult rv = GetParsingMode(aSheetType, &parsingMode);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-    case USER_SHEET:
-      parsingMode = css::eUserSheetFeatures;
-      break;
+  nsCOMPtr<nsIGlobalObject> global =
+    xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
+  NS_ENSURE_TRUE(global, NS_ERROR_UNEXPECTED);
 
-    case AUTHOR_SHEET:
-      parsingMode = css::eAuthorSheetFeatures;
-      break;
-
-    default:
-      NS_WARNING("invalid sheet type argument");
-      return NS_ERROR_INVALID_ARG;
+  ErrorResult errv;
+  RefPtr<dom::Promise> promise = dom::Promise::Create(global, errv);
+  if (errv.Failed()) {
+    return errv.StealNSResult();
   }
 
   RefPtr<PreloadedStyleSheet> sheet;
-  nsresult rv = PreloadedStyleSheet::Create(aSheetURI, parsingMode,
-                                            getter_AddRefs(sheet));
+  rv = PreloadedStyleSheet::Create(aSheetURI, parsingMode,
+                                   getter_AddRefs(sheet));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  sheet.forget(aSheet);
+  sheet->PreloadAsync(WrapNotNull(promise));
+
+  if (!ToJSValue(aCx, promise, aRval)) {
+    return NS_ERROR_FAILURE;
+  }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsStyleSheetService::UnregisterSheet(nsIURI *aSheetURI, uint32_t aSheetType)
 {
   NS_ENSURE_ARG(aSheetType == AGENT_SHEET ||
                 aSheetType == USER_SHEET ||
--- a/layout/style/Loader.cpp
+++ b/layout/style/Loader.cpp
@@ -2338,16 +2338,30 @@ Loader::LoadSheetSync(nsIURI* aURL,
   return InternalLoadNonDocumentSheet(aURL,
                                       false, aParsingMode, aUseSystemPrincipal,
                                       nullptr, EmptyCString(),
                                       aSheet, nullptr);
 }
 
 nsresult
 Loader::LoadSheet(nsIURI* aURL,
+                  SheetParsingMode aParsingMode,
+                  bool aUseSystemPrincipal,
+                  nsICSSLoaderObserver* aObserver,
+                  RefPtr<StyleSheet>* aSheet)
+{
+  LOG(("css::Loader::LoadSheet(aURL, aParsingMode, aUseSystemPrincipal, aObserver, aSheet)"));
+  return InternalLoadNonDocumentSheet(aURL,
+                                      false, aParsingMode, aUseSystemPrincipal,
+                                      nullptr, EmptyCString(),
+                                      aSheet, aObserver);
+}
+
+nsresult
+Loader::LoadSheet(nsIURI* aURL,
                   nsIPrincipal* aOriginPrincipal,
                   const nsCString& aCharset,
                   nsICSSLoaderObserver* aObserver,
                   RefPtr<StyleSheet>* aSheet)
 {
   LOG(("css::Loader::LoadSheet(aURL, aObserver, aSheet) api call"));
   NS_PRECONDITION(aSheet, "aSheet is null");
   return InternalLoadNonDocumentSheet(aURL,
--- a/layout/style/Loader.h
+++ b/layout/style/Loader.h
@@ -334,16 +334,42 @@ public:
   nsresult LoadSheetSync(nsIURI* aURL, RefPtr<StyleSheet>* aSheet) {
     return LoadSheetSync(aURL, eAuthorSheetFeatures, false, aSheet);
   }
 
   /**
    * Asynchronously load the stylesheet at aURL.  If a successful result is
    * returned, aObserver is guaranteed to be notified asynchronously once the
    * sheet is loaded and marked complete.  This method can be used to load
+   * sheets not associated with a document.
+   *
+   * @param aURL the URL of the sheet to load
+   * @param aParsingMode the mode in which to parse the sheet
+   *        (see comments at enum SheetParsingMode, above).
+   * @param aUseSystemPrincipal if true, give the resulting sheet the system
+   * principal no matter where it's being loaded from.
+   * @param aObserver the observer to notify when the load completes.
+   *                  Must not be null.
+   * @param [out] aSheet the sheet to load. Note that the sheet may well
+   *              not be loaded by the time this method returns.
+   *
+   * NOTE: At the moment, this method assumes the sheet will be UTF-8, but
+   * ideally it would allow arbitrary encodings.  Callers should NOT depend on
+   * non-UTF8 sheets being treated as UTF-8 by this method.
+   */
+  nsresult LoadSheet(nsIURI* aURL,
+                     SheetParsingMode aParsingMode,
+                     bool aUseSystemPrincipal,
+                     nsICSSLoaderObserver* aObserver,
+                     RefPtr<StyleSheet>* aSheet);
+
+  /**
+   * Asynchronously load the stylesheet at aURL.  If a successful result is
+   * returned, aObserver is guaranteed to be notified asynchronously once the
+   * sheet is loaded and marked complete.  This method can be used to load
    * sheets not associated with a document.  This method cannot be used to
    * load user or agent sheets.
    *
    * @param aURL the URL of the sheet to load
    * @param aOriginPrincipal the principal to use for security checks.  This
    *                         can be null to indicate that these checks should
    *                         be skipped.
    * @param aCharset the encoding to use for converting the sheet data
--- a/layout/style/PreloadedStyleSheet.cpp
+++ b/layout/style/PreloadedStyleSheet.cpp
@@ -4,54 +4,39 @@
  * 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/. */
 
 /* a CSS style sheet returned from nsIStyleSheetService.preloadSheet */
 
 #include "PreloadedStyleSheet.h"
 
 #include "mozilla/css/Loader.h"
+#include "mozilla/dom/Promise.h"
+#include "nsICSSLoaderObserver.h"
 #include "nsLayoutUtils.h"
 
 namespace mozilla {
 
 PreloadedStyleSheet::PreloadedStyleSheet(nsIURI* aURI,
                                          css::SheetParsingMode aParsingMode)
-  : mURI(aURI)
+  : mLoaded(false)
+  , mURI(aURI)
   , mParsingMode(aParsingMode)
 {
 }
 
 /* static */ nsresult
 PreloadedStyleSheet::Create(nsIURI* aURI,
                             css::SheetParsingMode aParsingMode,
                             PreloadedStyleSheet** aResult)
 {
   *aResult = nullptr;
 
-  // The nsIStyleSheetService.preloadSheet API doesn't tell us which backend
-  // the sheet will be used with, and it seems wasteful to eagerly create
-  // both a CSSStyleSheet and a ServoStyleSheet.  So instead, we guess that
-  // the sheet type we will want matches the current value of the stylo pref,
-  // and preload a sheet of that type.
-  //
-  // If we guess wrong, we will re-load the sheet later with the requested type,
-  // and we won't really have front loaded the loading time as the name
-  // "preload" might suggest.  Also, in theory we could get different data from
-  // fetching the URL again, but for the usage patterns of this API this is
-  // unlikely, and it doesn't seem worth trying to store the contents of the URL
-  // and duplicating a bunch of css::Loader's logic.
-
   RefPtr<PreloadedStyleSheet> preloadedSheet =
     new PreloadedStyleSheet(aURI, aParsingMode);
-  auto type = nsLayoutUtils::StyloEnabled() ? StyleBackendType::Servo
-                                            : StyleBackendType::Gecko;
-  StyleSheet* sheet;
-  nsresult rv = preloadedSheet->GetSheet(type, &sheet);
-  NS_ENSURE_SUCCESS(rv, rv);
 
   preloadedSheet.forget(aResult);
   return NS_OK;
 }
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PreloadedStyleSheet)
   NS_INTERFACE_MAP_ENTRY(nsIPreloadedStyleSheet)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
@@ -62,23 +47,95 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(Preload
 
 NS_IMPL_CYCLE_COLLECTION(PreloadedStyleSheet, mGecko, mServo)
 
 nsresult
 PreloadedStyleSheet::GetSheet(StyleBackendType aType, StyleSheet** aResult)
 {
   *aResult = nullptr;
 
+  MOZ_DIAGNOSTIC_ASSERT(mLoaded);
+
   RefPtr<StyleSheet>& sheet =
     aType == StyleBackendType::Gecko ? mGecko : mServo;
 
   if (!sheet) {
     RefPtr<css::Loader> loader = new css::Loader(aType);
     nsresult rv = loader->LoadSheetSync(mURI, mParsingMode, true, &sheet);
     NS_ENSURE_SUCCESS(rv, rv);
     MOZ_ASSERT(sheet);
   }
 
   *aResult = sheet;
   return NS_OK;
 }
 
+nsresult
+PreloadedStyleSheet::Preload()
+{
+  MOZ_DIAGNOSTIC_ASSERT(!mLoaded);
+
+  // The nsIStyleSheetService.preloadSheet API doesn't tell us which backend
+  // the sheet will be used with, and it seems wasteful to eagerly create
+  // both a CSSStyleSheet and a ServoStyleSheet.  So instead, we guess that
+  // the sheet type we will want matches the current value of the stylo pref,
+  // and preload a sheet of that type.
+  //
+  // If we guess wrong, we will re-load the sheet later with the requested type,
+  // and we won't really have front loaded the loading time as the name
+  // "preload" might suggest.  Also, in theory we could get different data from
+  // fetching the URL again, but for the usage patterns of this API this is
+  // unlikely, and it doesn't seem worth trying to store the contents of the URL
+  // and duplicating a bunch of css::Loader's logic.
+  auto type = nsLayoutUtils::StyloEnabled() ? StyleBackendType::Servo
+                                            : StyleBackendType::Gecko;
+
+  mLoaded = true;
+
+  StyleSheet* sheet;
+  return GetSheet(type, &sheet);
+}
+
+NS_IMPL_ISUPPORTS(PreloadedStyleSheet::StylesheetPreloadObserver,
+                  nsICSSLoaderObserver)
+
+NS_IMETHODIMP
+PreloadedStyleSheet::StylesheetPreloadObserver::StyleSheetLoaded(
+  StyleSheet* aSheet, bool aWasAlternate, nsresult aStatus)
+{
+  MOZ_DIAGNOSTIC_ASSERT(!mPreloadedSheet->mLoaded);
+  mPreloadedSheet->mLoaded = true;
+
+  if (NS_FAILED(aStatus)) {
+    mPromise->MaybeReject(aStatus);
+  } else {
+    mPromise->MaybeResolve(mPreloadedSheet);
+  }
+
+  return NS_OK;
+}
+
+// Note: After calling this method, the preloaded sheet *must not* be used
+// until the observer is notified that the sheet has finished loading.
+nsresult
+PreloadedStyleSheet::PreloadAsync(NotNull<dom::Promise*> aPromise)
+{
+  MOZ_DIAGNOSTIC_ASSERT(!mLoaded);
+
+  // As with the Preload() method, we can't be sure that the sheet will only be
+  // used with the backend that we're preloading it for now. If it's used with
+  // a different backend later, it will be synchronously loaded for that
+  // backend the first time it's used.
+  auto type = nsLayoutUtils::StyloEnabled() ? StyleBackendType::Servo
+                                            : StyleBackendType::Gecko;
+
+  RefPtr<StyleSheet>& sheet =
+    type == StyleBackendType::Gecko ? mGecko : mServo;
+
+  RefPtr<css::Loader> loader = new css::Loader(type);
+
+  RefPtr<StylesheetPreloadObserver> obs =
+    new StylesheetPreloadObserver(aPromise, this);
+
+  return loader->LoadSheet(mURI, mParsingMode, true, obs, &sheet);
+}
+
 } // namespace mozilla
--- a/layout/style/PreloadedStyleSheet.h
+++ b/layout/style/PreloadedStyleSheet.h
@@ -5,49 +5,82 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* a CSS style sheet returned from nsIStyleSheetService.preloadSheet */
 
 #ifndef mozilla_PreloadedStyleSheet_h
 #define mozilla_PreloadedStyleSheet_h
 
 #include "mozilla/css/SheetParsingMode.h"
+#include "mozilla/NotNull.h"
 #include "mozilla/Result.h"
 #include "mozilla/StyleBackendType.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
+#include "nsICSSLoaderObserver.h"
 #include "nsIPreloadedStyleSheet.h"
 
 class nsIURI;
 
 namespace mozilla {
+namespace dom {
+  class Promise;
+}
+
 class StyleSheet;
 
 class PreloadedStyleSheet : public nsIPreloadedStyleSheet
 {
 public:
   // *aResult is addrefed.
   static nsresult Create(nsIURI* aURI, css::SheetParsingMode aParsingMode,
                          PreloadedStyleSheet** aResult);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(PreloadedStyleSheet)
 
   // *aResult is not addrefed, since the PreloadedStyleSheet holds a strong
   // reference to the sheet.
   nsresult GetSheet(StyleBackendType aType, StyleSheet** aResult);
 
+  nsresult Preload();
+  nsresult PreloadAsync(NotNull<dom::Promise*> aPromise);
+
 protected:
   virtual ~PreloadedStyleSheet() {}
 
 private:
   PreloadedStyleSheet(nsIURI* aURI, css::SheetParsingMode aParsingMode);
 
+  class StylesheetPreloadObserver final : public nsICSSLoaderObserver
+  {
+  public:
+    NS_DECL_ISUPPORTS
+
+    explicit StylesheetPreloadObserver(NotNull<dom::Promise*> aPromise,
+                                       PreloadedStyleSheet* aSheet)
+      : mPromise(aPromise)
+      , mPreloadedSheet(aSheet)
+    {}
+
+    NS_IMETHOD StyleSheetLoaded(StyleSheet* aSheet,
+                                bool aWasAlternate,
+                                nsresult aStatus) override;
+
+  protected:
+    virtual ~StylesheetPreloadObserver() {}
+
+  private:
+    RefPtr<dom::Promise> mPromise;
+    RefPtr<PreloadedStyleSheet> mPreloadedSheet;
+  };
+
   RefPtr<StyleSheet> mGecko;
   RefPtr<StyleSheet> mServo;
 
+  bool mLoaded;
   nsCOMPtr<nsIURI> mURI;
   css::SheetParsingMode mParsingMode;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_PreloadedStyleSheet_h