From 8eee8c8dc9fc7f1acc2bb634e11d5a6f1a31388b Mon Sep 17 00:00:00 2001 From: Cameron McCormack Date: Thu, 6 Jun 2019 19:08:52 +0000 Subject: [PATCH] Bug 1490792 - Part 4: Perform OpenType sanitization OMT. r=jfkthame Differential Revision: https://phabricator.services.mozilla.com/D33897 --- gfx/thebes/gfxUserFontSet.cpp | 109 +++++++++++++++++---- gfx/thebes/gfxUserFontSet.h | 37 ++++++- gfx/thebes/moz.build | 6 ++ gfx/thebes/nsIFontLoadCompleteCallback.idl | 13 +++ layout/build/nsLayoutStatics.cpp | 3 + layout/style/nsFontFaceLoader.cpp | 53 ++++++---- layout/style/nsFontFaceLoader.h | 8 +- 7 files changed, 184 insertions(+), 45 deletions(-) create mode 100644 gfx/thebes/nsIFontLoadCompleteCallback.idl diff --git a/gfx/thebes/gfxUserFontSet.cpp b/gfx/thebes/gfxUserFontSet.cpp index 8201bf396a8c..bb787909552a 100644 --- a/gfx/thebes/gfxUserFontSet.cpp +++ b/gfx/thebes/gfxUserFontSet.cpp @@ -688,6 +688,27 @@ bool gfxUserFontEntry::LoadPlatformFontSync(const uint8_t* aFontData, return LoadPlatformFont(aFontData, aLength, fontType, saneData, saneLen); } +void gfxUserFontEntry::StartPlatformFontLoadOnWorkerThread( + const uint8_t* aFontData, uint32_t aLength, + nsMainThreadPtrHandle aCallback) { + MOZ_ASSERT(sFontLoadingThread); + MOZ_ASSERT(sFontLoadingThread->IsOnCurrentThread()); + + uint32_t saneLen; + gfxUserFontType fontType; + const uint8_t* saneData = + SanitizeOpenTypeData(aFontData, aLength, saneLen, fontType); + + nsCOMPtr event = + NewRunnableMethod>( + "gfxUserFontEntry::ContinuePlatformFontLoadOnMainThread", this, + &gfxUserFontEntry::ContinuePlatformFontLoadOnMainThread, aFontData, + aLength, fontType, saneData, saneLen, aCallback); + NS_DispatchToMainThread(event.forget()); +} + bool gfxUserFontEntry::LoadPlatformFont(const uint8_t* aOriginalFontData, uint32_t aOriginalLength, gfxUserFontType aFontType, @@ -829,9 +850,11 @@ void gfxUserFontEntry::IncrementGeneration() { // This is called when a font download finishes. // Ownership of aFontData passes in here, and the font set must // ensure that it is eventually deleted via free(). -bool gfxUserFontEntry::FontDataDownloadComplete(const uint8_t* aFontData, - uint32_t aLength, - nsresult aDownloadStatus) { +void gfxUserFontEntry::FontDataDownloadComplete( + const uint8_t* aFontData, uint32_t aLength, nsresult aDownloadStatus, + nsIFontLoadCompleteCallback* aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + // forget about the loader, as we no longer potentially need to cancel it // if the entry is obsoleted mLoader = nullptr; @@ -839,27 +862,73 @@ bool gfxUserFontEntry::FontDataDownloadComplete(const uint8_t* aFontData, // download successful, make platform font using font data if (NS_SUCCEEDED(aDownloadStatus) && mFontDataLoadingState != LOADING_TIMED_OUT) { - bool loaded = LoadPlatformFontSync(aFontData, aLength); - aFontData = nullptr; - - if (loaded) { - IncrementGeneration(); - return true; - } - - } else { - // download failed - mFontSet->LogMessage( - this, - (mFontDataLoadingState != LOADING_TIMED_OUT ? "download failed" - : "download timed out"), - nsIScriptError::errorFlag, aDownloadStatus); + LoadPlatformFontAsync(aFontData, aLength, aCallback); + return; } + // download failed + mFontSet->LogMessage( + this, + (mFontDataLoadingState != LOADING_TIMED_OUT ? "download failed" + : "download timed out"), + nsIScriptError::errorFlag, aDownloadStatus); + if (aFontData) { free((void*)aFontData); } + FontLoadFailed(aCallback); +} + +void gfxUserFontEntry::LoadPlatformFontAsync( + const uint8_t* aFontData, uint32_t aLength, + nsIFontLoadCompleteCallback* aCallback) { + // Ensure the font loading thread is available. + if (!sFontLoadingThread) { + sFontLoadingThread = + new LazyIdleThread(5000, NS_LITERAL_CSTRING("FontLoader")); + } + + nsMainThreadPtrHandle cb( + new nsMainThreadPtrHolder("FontLoader", + aCallback)); + + // Do the OpenType sanitization over on the font loading thread. Once that is + // complete, we'll continue in ContinuePlatformFontLoadOnMainThread. + nsCOMPtr event = + NewRunnableMethod>( + "gfxUserFontEntry::StartPlatformFontLoadOnWorkerThread", this, + &gfxUserFontEntry::StartPlatformFontLoadOnWorkerThread, aFontData, + aLength, cb); + MOZ_ALWAYS_SUCCEEDS( + sFontLoadingThread->Dispatch(event.forget(), NS_DISPATCH_NORMAL)); +} + +void gfxUserFontEntry::ContinuePlatformFontLoadOnMainThread( + const uint8_t* aOriginalFontData, uint32_t aOriginalLength, + gfxUserFontType aFontType, const uint8_t* aSanitizedFontData, + uint32_t aSanitizedLength, + nsMainThreadPtrHandle aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + + bool loaded = LoadPlatformFont(aOriginalFontData, aOriginalLength, aFontType, + aSanitizedFontData, aSanitizedLength); + aOriginalFontData = nullptr; + aSanitizedFontData = nullptr; + + if (loaded) { + IncrementGeneration(); + aCallback->FontLoadComplete(); + return; + } + + FontLoadFailed(aCallback); +} + +void gfxUserFontEntry::FontLoadFailed(nsIFontLoadCompleteCallback* aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + // Error occurred. Make sure the FontFace's promise is rejected if the // load timed out, or else load the next src. if (mFontDataLoadingState == LOADING_TIMED_OUT) { @@ -874,7 +943,7 @@ bool gfxUserFontEntry::FontDataDownloadComplete(const uint8_t* aFontData, // and return true in order to trigger reflow, so that fallback // will be used where the text was "masked" by the pending download IncrementGeneration(); - return true; + aCallback->FontLoadComplete(); } void gfxUserFontEntry::GetUserFontSets(nsTArray& aResult) { @@ -1295,6 +1364,8 @@ gfxUserFontSet::UserFontCache::MemoryReporter::CollectReports( return NS_OK; } +StaticRefPtr gfxUserFontEntry::sFontLoadingThread; + #ifdef DEBUG_USERFONT_CACHE void gfxUserFontSet::UserFontCache::Entry::Dump() { diff --git a/gfx/thebes/gfxUserFontSet.h b/gfx/thebes/gfxUserFontSet.h index 84438cf5414a..68a490b43232 100644 --- a/gfx/thebes/gfxUserFontSet.h +++ b/gfx/thebes/gfxUserFontSet.h @@ -10,8 +10,10 @@ #include "gfxFontFamilyList.h" #include "gfxFontSrcPrincipal.h" #include "gfxFontSrcURI.h" +#include "nsProxyRelease.h" #include "nsRefPtrHashtable.h" #include "nsCOMPtr.h" +#include "nsIFontLoadCompleteCallback.h" #include "nsIMemoryReporter.h" #include "nsIPrincipal.h" #include "nsIRunnable.h" @@ -22,6 +24,8 @@ #include "mozilla/ServoStyleConsts.h" #include "mozilla/net/ReferrerPolicy.h" #include "gfxFontConstants.h" +#include "mozilla/LazyIdleThread.h" +#include "mozilla/StaticPtr.h" class gfxFont; @@ -635,6 +639,8 @@ class gfxUserFontEntry : public gfxFontEntry { MOZ_ASSERT_UNREACHABLE("not meaningful for a userfont placeholder"); } + static void Shutdown() { sFontLoadingThread = nullptr; } + protected: const uint8_t* SanitizeOpenTypeData(const uint8_t* aData, uint32_t aLength, uint32_t& aSaneLength, @@ -650,12 +656,11 @@ class gfxUserFontEntry : public gfxFontEntry { // when download has been completed, pass back data here // aDownloadStatus == NS_OK ==> download succeeded, error otherwise - // returns true if platform font creation sucessful (or local() - // reference was next in line) // Ownership of aFontData is passed in here; the font set must // ensure that it is eventually deleted with free(). - bool FontDataDownloadComplete(const uint8_t* aFontData, uint32_t aLength, - nsresult aDownloadStatus); + void FontDataDownloadComplete(const uint8_t* aFontData, uint32_t aLength, + nsresult aDownloadStatus, + nsIFontLoadCompleteCallback* aCallback); // helper method for creating a platform font // returns true if platform font creation successful @@ -663,12 +668,32 @@ class gfxUserFontEntry : public gfxFontEntry { // ensure that it is eventually deleted with free(). bool LoadPlatformFontSync(const uint8_t* aFontData, uint32_t aLength); - // helper method for LoadPlatformFontSync + void LoadPlatformFontAsync(const uint8_t* aFontData, uint32_t aLength, + nsIFontLoadCompleteCallback* aCallback); + + // helper method for LoadPlatformFontAsync; runs on the FontLoader thread + void StartPlatformFontLoadOnWorkerThread( + const uint8_t* aFontData, uint32_t aLength, + nsMainThreadPtrHandle aCallback); + + // helper method for LoadPlatformFontAsync; runs on the main thread + void ContinuePlatformFontLoadOnMainThread( + const uint8_t* aOriginalFontData, uint32_t aOriginalLength, + gfxUserFontType aFontType, const uint8_t* aSanitizedFontData, + uint32_t aSanitizedLength, + nsMainThreadPtrHandle aCallback); + + // helper method for LoadPlatformFontSync and + // ContinuePlatformFontLoadOnMainThread; runs on the main thread bool LoadPlatformFont(const uint8_t* aOriginalFontData, uint32_t aOriginalLength, gfxUserFontType aFontType, const uint8_t* aSanitizedFontData, uint32_t aSanitizedLength); + // helper method for FontDataDownloadComplete and + // ContinuePlatformFontLoadOnMainThread; runs on the main thread + void FontLoadFailed(nsIFontLoadCompleteCallback* aCallback); + // store metadata and src details for current src into aFontEntry void StoreUserFontData(gfxFontEntry* aFontEntry, bool aPrivate, const nsACString& aOriginalName, @@ -715,6 +740,8 @@ class gfxUserFontEntry : public gfxFontEntry { gfxUserFontSet* MOZ_NON_OWNING_REF mFontSet; // font-set which owns this userfont entry RefPtr mPrincipal; + + static mozilla::StaticRefPtr sFontLoadingThread; }; #endif /* GFX_USER_FONT_SET_H */ diff --git a/gfx/thebes/moz.build b/gfx/thebes/moz.build index 19cfb2fde0e4..b12e6a50d481 100644 --- a/gfx/thebes/moz.build +++ b/gfx/thebes/moz.build @@ -10,6 +10,12 @@ with Files('*Text*'): with Files('*DWrite*'): BUG_COMPONENT = ('Core', 'Graphics: Text') +XPIDL_SOURCES += [ + 'nsIFontLoadCompleteCallback.idl', +] + +XPIDL_MODULE = 'gfx' + EXPORTS += [ 'DrawMode.h', 'gfx2DGlue.h', diff --git a/gfx/thebes/nsIFontLoadCompleteCallback.idl b/gfx/thebes/nsIFontLoadCompleteCallback.idl new file mode 100644 index 000000000000..4f1df74a7984 --- /dev/null +++ b/gfx/thebes/nsIFontLoadCompleteCallback.idl @@ -0,0 +1,13 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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 "nsISupports.idl" + +[uuid(302dbf09-079b-4648-8a06-a0486c1749c0)] +interface nsIFontLoadCompleteCallback : nsISupports +{ + void fontLoadComplete(); +}; diff --git a/layout/build/nsLayoutStatics.cpp b/layout/build/nsLayoutStatics.cpp index bab31a228c9b..edf0bb20df4e 100644 --- a/layout/build/nsLayoutStatics.cpp +++ b/layout/build/nsLayoutStatics.cpp @@ -129,6 +129,7 @@ #include "mozilla/net/UrlClassifierFeatureFactory.h" #include "nsThreadManager.h" #include "mozilla/css/ImageLoader.h" +#include "gfxUserFontSet.h" using namespace mozilla; using namespace mozilla::net; @@ -433,4 +434,6 @@ void nsLayoutStatics::Shutdown() { css::ImageLoader::Shutdown(); mozilla::net::UrlClassifierFeatureFactory::Shutdown(); + + gfxUserFontEntry::Shutdown(); } diff --git a/layout/style/nsFontFaceLoader.cpp b/layout/style/nsFontFaceLoader.cpp index e3adaf54dded..569e9bff8199 100644 --- a/layout/style/nsFontFaceLoader.cpp +++ b/layout/style/nsFontFaceLoader.cpp @@ -57,6 +57,11 @@ nsFontFaceLoader::nsFontFaceLoader(gfxUserFontEntry* aUserFontEntry, MOZ_ASSERT(mFontFaceSet, "We should get a valid FontFaceSet from the caller!"); mStartTime = TimeStamp::Now(); + + // We add an explicit load block rather than just rely on the network + // request's block, since we need to do some OMT work after the load + // is finished before we unblock load. + mFontFaceSet->Document()->BlockOnload(); } nsFontFaceLoader::~nsFontFaceLoader() { @@ -71,6 +76,7 @@ nsFontFaceLoader::~nsFontFaceLoader() { } if (mFontFaceSet) { mFontFaceSet->RemoveLoader(this); + mFontFaceSet->Document()->UnblockOnload(false); } } @@ -206,6 +212,11 @@ nsFontFaceLoader::OnStreamComplete(nsIStreamLoader* aLoader, DropChannel(); + if (mLoadTimer) { + mLoadTimer->Cancel(); + mLoadTimer = nullptr; + } + if (!mFontFaceSet) { // We've been canceled return aStatus; @@ -257,42 +268,43 @@ nsFontFaceLoader::OnStreamComplete(nsIStreamLoader* aLoader, } } + mFontFaceSet->GetUserFontSet()->RecordFontLoadDone(aStringLen, doneTime); + // The userFontEntry is responsible for freeing the downloaded data // (aString) when finished with it; the pointer is no longer valid // after FontDataDownloadComplete returns. // This is called even in the case of a failed download (HTTP 404, etc), // as there may still be data to be freed (e.g. an error page), // and we need to load the next source. - bool fontUpdate = - mUserFontEntry->FontDataDownloadComplete(aString, aStringLen, aStatus); - mFontFaceSet->GetUserFontSet()->RecordFontLoadDone(aStringLen, doneTime); + // FontDataDownloadComplete will load the platform font on a worker thread, + // and will call FontLoadComplete when it has finished its work. + mUserFontEntry->FontDataDownloadComplete(aString, aStringLen, aStatus, this); + return NS_SUCCESS_ADOPTED_DATA; +} + +nsresult nsFontFaceLoader::FontLoadComplete() { + MOZ_ASSERT(NS_IsMainThread()); // when new font loaded, need to reflow - if (fontUpdate) { - nsTArray fontSets; - mUserFontEntry->GetUserFontSets(fontSets); - for (gfxUserFontSet* fontSet : fontSets) { - nsPresContext* ctx = FontFaceSet::GetPresContextFor(fontSet); - if (ctx) { - // Update layout for the presence of the new font. Since this is - // asynchronous, reflows will coalesce. - ctx->UserFontSetUpdated(mUserFontEntry); - LOG(("userfonts (%p) reflow for pres context %p\n", this, ctx)); - } + nsTArray fontSets; + mUserFontEntry->GetUserFontSets(fontSets); + for (gfxUserFontSet* fontSet : fontSets) { + nsPresContext* ctx = FontFaceSet::GetPresContextFor(fontSet); + if (ctx) { + // Update layout for the presence of the new font. Since this is + // asynchronous, reflows will coalesce. + ctx->UserFontSetUpdated(mUserFontEntry); + LOG(("userfonts (%p) reflow for pres context %p\n", this, ctx)); } } MOZ_DIAGNOSTIC_ASSERT(mFontFaceSet); mFontFaceSet->RemoveLoader(this); - // done with font set + mFontFaceSet->Document()->UnblockOnload(false); mFontFaceSet = nullptr; - if (mLoadTimer) { - mLoadTimer->Cancel(); - mLoadTimer = nullptr; - } - return NS_SUCCESS_ADOPTED_DATA; + return NS_OK; } // nsIRequestObserver @@ -323,6 +335,7 @@ void nsFontFaceLoader::Cancel() { mUserFontEntry->LoadCanceled(); mUserFontEntry = nullptr; + mFontFaceSet->Document()->UnblockOnload(false); mFontFaceSet = nullptr; if (mLoadTimer) { mLoadTimer->Cancel(); diff --git a/layout/style/nsFontFaceLoader.h b/layout/style/nsFontFaceLoader.h index cd399509c519..27257fce40b6 100644 --- a/layout/style/nsFontFaceLoader.h +++ b/layout/style/nsFontFaceLoader.h @@ -13,6 +13,7 @@ #include "mozilla/TimeStamp.h" #include "mozilla/dom/FontFaceSet.h" #include "nsCOMPtr.h" +#include "nsIFontLoadCompleteCallback.h" #include "nsIStreamLoader.h" #include "nsIChannel.h" #include "nsIRequestObserver.h" @@ -23,7 +24,8 @@ class nsIPrincipal; class nsFontFaceLoader final : public nsIStreamLoaderObserver, - public nsIRequestObserver { + public nsIRequestObserver, + public nsIFontLoadCompleteCallback { public: nsFontFaceLoader(gfxUserFontEntry* aFontToLoad, nsIURI* aFontURI, mozilla::dom::FontFaceSet* aFontFaceSet, @@ -46,6 +48,10 @@ class nsFontFaceLoader final : public nsIStreamLoaderObserver, gfxUserFontEntry* GetUserFontEntry() const { return mUserFontEntry; } + // Called by the gfxUserFontEntry once it has finished the platform font + // loading. + NS_IMETHODIMP FontLoadComplete() final; + protected: virtual ~nsFontFaceLoader();