diff --git a/layout/style/FontFace.cpp b/layout/style/FontFace.cpp index f6db634df564..97def5611845 100644 --- a/layout/style/FontFace.cpp +++ b/layout/style/FontFace.cpp @@ -8,12 +8,122 @@ #include "mozilla/dom/FontFaceBinding.h" #include "mozilla/dom/FontFaceSet.h" #include "mozilla/dom/Promise.h" +#include "mozilla/dom/TypedArray.h" +#include "mozilla/dom/UnionTypes.h" #include "nsCSSParser.h" #include "nsCSSRules.h" #include "nsIDocument.h" #include "nsStyleUtil.h" -using namespace mozilla::dom; +namespace mozilla { +namespace dom { + +// -- FontFaceInitializer ---------------------------------------------------- + +/** + * A task that is dispatched to the event queue to call Initialize() on a + * FontFace object with the source information that was passed to the JS + * constructor. + */ +class FontFaceInitializer : public nsIRunnable +{ +public: + NS_DECL_ISUPPORTS + + FontFaceInitializer(FontFace* aFontFace) + : mFontFace(aFontFace) + , mSourceBuffer(nullptr) + , mSourceBufferLength(0) {} + + void SetSource(const nsAString& aString); + void SetSource(const ArrayBuffer& aArrayBuffer); + void SetSource(const ArrayBufferView& aArrayBufferView); + + NS_IMETHOD Run(); + + nsRefPtr mFontFace; + FontFace::SourceType mSourceType; + nsString mSourceString; + uint8_t* mSourceBuffer; // allocated with NS_Alloc + uint32_t mSourceBufferLength; + +protected: + virtual ~FontFaceInitializer(); +}; + +NS_IMPL_ISUPPORTS(FontFaceInitializer, nsIRunnable) + +FontFaceInitializer::~FontFaceInitializer() +{ + if (mSourceBuffer) { + NS_Free(mSourceBuffer); + } +} + +void +FontFaceInitializer::SetSource(const nsAString& aString) +{ + mSourceType = FontFace::eSourceType_URLs; + mSourceString = aString; +} + +void +FontFaceInitializer::SetSource(const ArrayBuffer& aArrayBuffer) +{ + mSourceType = FontFace::eSourceType_Buffer; + // XXX Do something with the array buffer data. +} + +void +FontFaceInitializer::SetSource(const ArrayBufferView& aArrayBufferView) +{ + mSourceType = FontFace::eSourceType_Buffer; + // XXX Do something with the array buffer data. +} + +NS_IMETHODIMP +FontFaceInitializer::Run() +{ + mFontFace->Initialize(this); + return NS_OK; +} + +// -- FontFaceStatusSetter --------------------------------------------------- + +/** + * A task that is dispatched to the event queue to asynchronously call + * SetStatus() on a FontFace object. + */ +class FontFaceStatusSetter : public nsIRunnable +{ +public: + NS_DECL_ISUPPORTS + + FontFaceStatusSetter(FontFace* aFontFace, + FontFaceLoadStatus aStatus) + : mFontFace(aFontFace) + , mStatus(aStatus) {} + + NS_IMETHOD Run(); + +protected: + virtual ~FontFaceStatusSetter() {} + +private: + nsRefPtr mFontFace; + FontFaceLoadStatus mStatus; +}; + +NS_IMPL_ISUPPORTS(FontFaceStatusSetter, nsIRunnable) + +NS_IMETHODIMP +FontFaceStatusSetter::Run() +{ + mFontFace->SetStatus(mStatus); + return NS_OK; +} + +// -- FontFace --------------------------------------------------------------- NS_IMPL_CYCLE_COLLECTION_CLASS(FontFace) @@ -52,8 +162,13 @@ FontFace::FontFace(nsISupports* aParent, nsPresContext* aPresContext) : mParent(aParent) , mPresContext(aPresContext) , mStatus(FontFaceLoadStatus::Unloaded) + , mSourceType(SourceType(0)) + , mSourceBuffer(nullptr) + , mSourceBufferLength(0) , mFontFaceSet(aPresContext->Fonts()) , mInFontFaceSet(false) + , mInitialized(false) + , mLoadWhenInitialized(false) { MOZ_COUNT_CTOR(FontFace); @@ -76,6 +191,10 @@ FontFace::~FontFace() if (mFontFaceSet && !IsInFontFaceSet()) { mFontFaceSet->RemoveUnavailableFontFace(this); } + + if (mSourceBuffer) { + NS_Free(mSourceBuffer); + } } JSObject* @@ -110,7 +229,9 @@ FontFace::CreateForRule(nsISupports* aGlobal, nsCOMPtr globalObject = do_QueryInterface(aGlobal); nsRefPtr obj = new FontFace(aGlobal, aPresContext); + obj->mInitialized = true; obj->mRule = aRule; + obj->mSourceType = eSourceType_FontFaceRule; obj->mInFontFaceSet = true; obj->SetUserFontEntry(aUserFontEntry); return obj.forget(); @@ -145,9 +266,64 @@ FontFace::Constructor(const GlobalObject& aGlobal, nsRefPtr obj = new FontFace(global, presContext); obj->mFontFaceSet->AddUnavailableFontFace(obj); + if (!obj->SetDescriptors(aFamily, aDescriptors)) { + return obj.forget(); + } + + nsRefPtr task = new FontFaceInitializer(obj); + + if (aSource.IsArrayBuffer()) { + task->SetSource(aSource.GetAsArrayBuffer()); + } else if (aSource.IsArrayBufferView()) { + task->SetSource(aSource.GetAsArrayBufferView()); + } else { + MOZ_ASSERT(aSource.IsString()); + task->SetSource(aSource.GetAsString()); + } + + NS_DispatchToMainThread(task); + return obj.forget(); } +void +FontFace::Initialize(FontFaceInitializer* aInitializer) +{ + MOZ_ASSERT(!IsConnected()); + MOZ_ASSERT(mSourceType == SourceType(0)); + + if (aInitializer->mSourceType == eSourceType_URLs) { + if (!ParseDescriptor(eCSSFontDesc_Src, + aInitializer->mSourceString, + mDescriptors->mSrc)) { + if (mLoaded) { + // The asynchronous SetStatus call we are about to do assumes that for + // FontFace objects with sources other than ArrayBuffer(View)s, that the + // mLoaded Promise is rejected with a network error. We get + // in here beforehand to set it to the required syntax error. + mLoaded->MaybeReject(NS_ERROR_DOM_SYNTAX_ERR); + } + + // Queue a task to set the status to "error". + nsCOMPtr statusSetterTask = + new FontFaceStatusSetter(this, FontFaceLoadStatus::Error); + NS_DispatchToMainThread(statusSetterTask); + return; + } + + mSourceType = eSourceType_URLs; + + // Now that we have parsed the src descriptor, we are initialized. + OnInitialized(); + return; + } + + // We've been given an ArrayBuffer or ArrayBufferView as the source. + MOZ_ASSERT(aInitializer->mSourceType == eSourceType_Buffer); + + // XXX Handle array buffers. +} + void FontFace::GetFamily(nsString& aResult) { @@ -159,7 +335,11 @@ FontFace::GetFamily(nsString& aResult) aResult.Truncate(); nsDependentString family(value.GetStringBufferValue()); - nsStyleUtil::AppendEscapedCSSString(family, aResult); + if (!family.IsEmpty()) { + // The string length can be zero when the author passed an invalid + // family name or an invalid descriptor to the JS FontFace constructor. + nsStyleUtil::AppendEscapedCSSString(family, aResult); + } } void @@ -284,13 +464,31 @@ FontFace::Load(ErrorResult& aRv) return nullptr; } - if (mStatus != FontFaceLoadStatus::Unloaded) { + // Calling Load on a FontFace constructed with an ArrayBuffer data source, + // or on one that is already loading (or has finished loading), has no + // effect. + if (mSourceType == eSourceType_Buffer || + mStatus != FontFaceLoadStatus::Unloaded) { return mLoaded; } + // Calling the user font entry's Load method will end up setting our + // status to Loading, but the spec requires us to set it to Loading + // here. SetStatus(FontFaceLoadStatus::Loading); - mUserFontEntry->Load(); + if (mInitialized) { + // XXX For FontFace objects not in the FontFaceSet, we will need a + // way to create a user font entry. + if (mUserFontEntry) { + mUserFontEntry->Load(); + } + } else { + // We can only load an initialized font; this will cause the font to be + // loaded once it has been initialized. + mLoadWhenInitialized = true; + } + return mLoaded; } @@ -367,8 +565,9 @@ FontFace::SetDescriptor(nsCSSFontDesc aFontDesc, const nsAString& aValue, ErrorResult& aRv) { - NS_ASSERTION(!mRule, "we don't handle rule-connected FontFace objects yet"); - if (mRule) { + NS_ASSERTION(!IsConnected(), + "we don't handle rule-connected FontFace objects yet"); + if (IsConnected()) { return; } @@ -384,10 +583,70 @@ FontFace::SetDescriptor(nsCSSFontDesc aFontDesc, // objects that have started loading or have already been loaded. } +bool +FontFace::SetDescriptors(const nsAString& aFamily, + const FontFaceDescriptors& aDescriptors) +{ + MOZ_ASSERT(!IsConnected()); + MOZ_ASSERT(!mDescriptors); + + mDescriptors = new CSSFontFaceDescriptors; + + // Parse all of the mDescriptors in aInitializer, which are the values + // we got from the JS constructor. + if (!ParseDescriptor(eCSSFontDesc_Family, + aFamily, + mDescriptors->mFamily) || + *mDescriptors->mFamily.GetStringBufferValue() == 0 || + !ParseDescriptor(eCSSFontDesc_Style, + aDescriptors.mStyle, + mDescriptors->mStyle) || + !ParseDescriptor(eCSSFontDesc_Weight, + aDescriptors.mWeight, + mDescriptors->mWeight) || + !ParseDescriptor(eCSSFontDesc_Stretch, + aDescriptors.mStretch, + mDescriptors->mStretch) || + !ParseDescriptor(eCSSFontDesc_UnicodeRange, + aDescriptors.mUnicodeRange, + mDescriptors->mUnicodeRange) || + !ParseDescriptor(eCSSFontDesc_FontFeatureSettings, + aDescriptors.mFeatureSettings, + mDescriptors->mFontFeatureSettings)) { + // XXX Handle font-variant once we support it (bug 1055385). + + // If any of the descriptors failed to parse, none of them should be set + // on the FontFace. + mDescriptors = new CSSFontFaceDescriptors; + + if (mLoaded) { + mLoaded->MaybeReject(NS_ERROR_DOM_SYNTAX_ERR); + } + + SetStatus(FontFaceLoadStatus::Error); + return false; + } + + return true; +} + +void +FontFace::OnInitialized() +{ + MOZ_ASSERT(!mInitialized); + + mInitialized = true; + + if (mInFontFaceSet) { + mFontFaceSet->OnFontFaceInitialized(this); + } +} + void FontFace::GetDesc(nsCSSFontDesc aDescID, nsCSSValue& aResult) const { - if (mRule) { + if (IsConnected()) { + MOZ_ASSERT(mRule); MOZ_ASSERT(!mDescriptors); mRule->GetDesc(aDescID, aResult); } else { @@ -487,3 +746,6 @@ FontFace::Entry::SetLoadState(UserFontLoadState aLoadState) mFontFaces[i]->SetStatus(LoadStateToStatus(aLoadState)); } } + +} // namespace dom +} // namespace mozilla diff --git a/layout/style/FontFace.h b/layout/style/FontFace.h index e135504a92f8..1bb172cd3450 100644 --- a/layout/style/FontFace.h +++ b/layout/style/FontFace.h @@ -20,6 +20,8 @@ struct CSSFontFaceDescriptors; namespace dom { struct FontFaceDescriptors; class FontFaceSet; +class FontFaceInitializer; +class FontFaceStatusSetter; class Promise; class StringOrArrayBufferOrArrayBufferView; } @@ -31,6 +33,8 @@ namespace dom { class FontFace MOZ_FINAL : public nsISupports, public nsWrapperCache { + friend class mozilla::dom::FontFaceInitializer; + friend class mozilla::dom::FontFaceStatusSetter; friend class Entry; public: @@ -79,8 +83,21 @@ public: gfxUserFontEntry* GetUserFontEntry() const { return mUserFontEntry; } void SetUserFontEntry(gfxUserFontEntry* aEntry); + /** + * Returns whether this object is in a FontFaceSet. + */ bool IsInFontFaceSet() { return mInFontFaceSet; } + /** + * Returns whether this FontFace is initialized. A CSS-connected + * FontFace is considered initialized at construction time. For + * FontFace objects created using the FontFace JS constructor, it + * is once all the descriptors have been parsed. + */ + bool IsInitialized() const { return mInitialized; } + + FontFaceSet* GetFontFaceSet() const { return mFontFaceSet; } + /** * Gets the family name of the FontFace as a raw string (such as 'Times', as * opposed to GetFamily, which returns a CSS-escaped string, such as @@ -130,6 +147,14 @@ private: FontFace(nsISupports* aParent, nsPresContext* aPresContext); ~FontFace(); + /** + * Initializes the source and descriptors on this object based on values that + * were passed in to the JS constructor. If the source was specified as + * an ArrayBuffer or ArrayBufferView, parsing of the font data in there + * will be started. + */ + void Initialize(FontFaceInitializer* aInitializer); + /** * Parses a @font-face descriptor value, storing the result in aResult. * Returns whether the parsing was successful. @@ -142,6 +167,19 @@ private: const nsAString& aValue, mozilla::ErrorResult& aRv); + /** + * Sets all of the descriptor values in mDescriptors using values passed + * to the JS constructor. + */ + bool SetDescriptors(const nsAString& aFamily, + const FontFaceDescriptors& aDescriptors); + + /** + * Marks the FontFace as initialized and informs the FontFaceSet it is in, + * if any. + */ + void OnInitialized(); + /** * Sets the current loading status. */ @@ -172,6 +210,21 @@ private: // loop before updating the status, rather than doing it immediately. mozilla::dom::FontFaceLoadStatus mStatus; + // Represents where a FontFace's data is coming from. + enum SourceType { + eSourceType_FontFaceRule = 1, + eSourceType_URLs, + eSourceType_Buffer + }; + + // Where the font data for this FontFace is coming from. + SourceType mSourceType; + + // If the FontFace was constructed with an ArrayBuffer(View), this is a + // copy of the data from it. + uint8_t* mSourceBuffer; + uint32_t mSourceBufferLength; + // The values corresponding to the font face descriptors, if we are not // a CSS-connected FontFace object. For CSS-connected objects, we use // the descriptors stored in mRule. @@ -183,6 +236,16 @@ private: // Whether this FontFace appears in the FontFaceSet. bool mInFontFaceSet; + + // Whether the FontFace has been fully initialized. This takes at least one + // run around the event loop, as the parsing of the src descriptor is done + // off an event queue task. + bool mInitialized; + + // Records whether Load() was called on this FontFace before it was + // initialized. When the FontFace eventually does become initialized, + // mLoadPending is checked and Load() is called if needed. + bool mLoadWhenInitialized; }; } // namespace dom diff --git a/layout/style/FontFaceSet.cpp b/layout/style/FontFaceSet.cpp index 852c9fb165a6..3ada70a0c66d 100644 --- a/layout/style/FontFaceSet.cpp +++ b/layout/style/FontFaceSet.cpp @@ -170,6 +170,13 @@ FontFaceSet::Delete(FontFace& aFontFace, ErrorResult& aRv) return false; } +bool +FontFaceSet::HasAvailableFontFace(FontFace* aFontFace) +{ + return aFontFace->GetFontFaceSet() == this && + aFontFace->IsInFontFaceSet(); +} + bool FontFaceSet::Has(FontFace& aFontFace) { @@ -482,6 +489,14 @@ void FontFaceSet::InsertUnconnectedFontFace(FontFace* aFontFace, bool& aFontSetModified) { + if (!aFontFace->IsInitialized()) { + // The FontFace is still waiting to be initialized, so don't create a + // user font entry for it yet. Once it has been initialized, it will + // call OnFontFaceInitialized on us, which will end rebuild the user + // font set and end up back in here. + return; + } + nsAutoString fontfamily; if (!aFontFace->GetFamilyName(fontfamily)) { // If there is no family name, this rule cannot contribute a @@ -1101,6 +1116,7 @@ FontFaceSet::FontFaceForRule(nsCSSFontFaceRule* aRule) void FontFaceSet::AddUnavailableFontFace(FontFace* aFontFace) { + MOZ_ASSERT(!aFontFace->IsConnected()); MOZ_ASSERT(!aFontFace->IsInFontFaceSet()); MOZ_ASSERT(!mUnavailableFaces.Contains(aFontFace)); @@ -1121,6 +1137,16 @@ FontFaceSet::RemoveUnavailableFontFace(FontFace* aFontFace) MOZ_ASSERT(!mUnavailableFaces.Contains(aFontFace)); } +void +FontFaceSet::OnFontFaceInitialized(FontFace* aFontFace) +{ + MOZ_ASSERT(HasAvailableFontFace(aFontFace)); + MOZ_ASSERT(!aFontFace->IsConnected()); + MOZ_ASSERT(aFontFace->IsInitialized()); + + mPresContext->RebuildUserFontSet(); +} + // -- FontFaceSet::UserFontSet ------------------------------------------------ /* virtual */ nsresult diff --git a/layout/style/FontFaceSet.h b/layout/style/FontFaceSet.h index ad3801a586a3..594dc2123ef8 100644 --- a/layout/style/FontFaceSet.h +++ b/layout/style/FontFaceSet.h @@ -124,6 +124,15 @@ public: */ void RemoveUnavailableFontFace(FontFace* aFontFace); + /** + * Notification method called by a FontFace once it has been initialized. + * + * This is needed for the FontFaceSet to handle a FontFace that was created + * and inserted into the set immediately, before the event loop has spun and + * the FontFace's initialization tasks have run. + */ + void OnFontFaceInitialized(FontFace* aFontFace); + // -- Web IDL -------------------------------------------------------------- IMPL_EVENT_HANDLER(loading) @@ -148,6 +157,11 @@ public: private: ~FontFaceSet(); + /** + * Returns whether the given FontFace is currently "in" the FontFaceSet. + */ + bool HasAvailableFontFace(FontFace* aFontFace); + // Note: if you add new cycle collected objects to FontFaceRecord, // make sure to update FontFaceSet's cycle collection macros // accordingly.