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
--- 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