Bug 1028497 - Part 21: Implement the FontFace constructor's parsing of descriptors. r=bzbarsky,jdaggett

This implements the bulk of the FontFace JS constructor, which parses
the descriptors passed in. We need a notion now of whether a FontFace is
"initialized", since the spec requires us to go through the event loop
before parsing the 'src' descriptor. So a couple of places now have to
check whether the FontFace is fully initialized, and we have a method to
inform the FontFaceSet when a FontFace becomes initialized, in case we
added it to the FontFaceSet before it was initialized (easy to do with
|document.fonts.add(new FontFace(...))|.
This commit is contained in:
Cameron McCormack 2014-10-02 12:32:09 +10:00
parent bd49942314
commit e101dab5b7
4 changed files with 372 additions and 7 deletions

View File

@ -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<FontFace> 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<FontFace> 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<nsIGlobalObject> globalObject = do_QueryInterface(aGlobal);
nsRefPtr<FontFace> 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<FontFace> obj = new FontFace(global, presContext);
obj->mFontFaceSet->AddUnavailableFontFace(obj);
if (!obj->SetDescriptors(aFamily, aDescriptors)) {
return obj.forget();
}
nsRefPtr<FontFaceInitializer> 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<nsIRunnable> 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

View File

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

View File

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

View File

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