Bug 1145506 - Make FontFace constructor fail on invalid src strings but otherwise create user font entries immediately. r=jdaggett

This commit is contained in:
Cameron McCormack 2015-03-27 21:13:21 +11:00
parent 8c4d516d80
commit 1366fc2485
5 changed files with 47 additions and 277 deletions

View File

@ -42,7 +42,7 @@ FontFaceBufferSource::TakeBuffer(uint8_t*& aBuffer, uint32_t& aLength)
mFontFace->TakeBuffer(aBuffer, aLength);
}
// -- FontFaceInitializer ----------------------------------------------------
// -- Utility functions ------------------------------------------------------
template<typename T>
static void
@ -61,120 +61,6 @@ GetDataFrom(const T& aObject, uint8_t*& aBuffer, uint32_t& aLength)
aLength = aObject.Length();
}
/**
* 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
explicit 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() override;
void TakeBuffer(uint8_t*& aBuffer, uint32_t& aLength);
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;
GetDataFrom(aArrayBuffer, mSourceBuffer, mSourceBufferLength);
}
void
FontFaceInitializer::SetSource(const ArrayBufferView& aArrayBufferView)
{
mSourceType = FontFace::eSourceType_Buffer;
GetDataFrom(aArrayBufferView, mSourceBuffer, mSourceBufferLength);
}
NS_IMETHODIMP
FontFaceInitializer::Run()
{
mFontFace->Initialize(this);
return NS_OK;
}
void
FontFaceInitializer::TakeBuffer(uint8_t*& aBuffer, uint32_t& aLength)
{
aBuffer = mSourceBuffer;
aLength = mSourceBufferLength;
mSourceBuffer = nullptr;
mSourceBufferLength = 0;
}
// -- 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() override;
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)
@ -219,8 +105,6 @@ FontFace::FontFace(nsISupports* aParent, nsPresContext* aPresContext)
, mSourceBufferLength(0)
, mFontFaceSet(aPresContext->Fonts())
, mInFontFaceSet(false)
, mInitialized(false)
, mLoadWhenInitialized(false)
{
MOZ_COUNT_CTOR(FontFace);
@ -281,7 +165,6 @@ 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;
@ -321,74 +204,46 @@ FontFace::Constructor(const GlobalObject& aGlobal,
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);
obj->InitializeSource(aSource);
return obj.forget();
}
void
FontFace::Initialize(FontFaceInitializer* aInitializer)
FontFace::InitializeSource(const StringOrArrayBufferOrArrayBufferView& aSource)
{
MOZ_ASSERT(!HasRule());
MOZ_ASSERT(mSourceType == SourceType(0));
if (aInitializer->mSourceType == eSourceType_URLs) {
if (aSource.IsString()) {
if (!ParseDescriptor(eCSSFontDesc_Src,
aInitializer->mSourceString,
aSource.GetAsString(),
mDescriptors->mSrc)) {
if (mLoaded) {
// The asynchronous SetStatus call we are about to do assumes that for
// The 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);
SetStatus(FontFaceLoadStatus::Error);
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);
mSourceType = FontFace::eSourceType_Buffer;
mSourceType = aInitializer->mSourceType;
aInitializer->TakeBuffer(mSourceBuffer, mSourceBufferLength);
if (aSource.IsArrayBuffer()) {
GetDataFrom(aSource.GetAsArrayBuffer(),
mSourceBuffer, mSourceBufferLength);
} else {
MOZ_ASSERT(aSource.IsArrayBufferView());
GetDataFrom(aSource.GetAsArrayBufferView(),
mSourceBuffer, mSourceBufferLength);
}
// Queue a task to set the status to "loading".
nsCOMPtr<nsIRunnable> statusSetterTask =
new FontFaceStatusSetter(this, FontFaceLoadStatus::Loading);
NS_DispatchToMainThread(statusSetterTask);
// We are initialized.
OnInitialized();
// ArrayBuffer(View)-backed FontFace objects are loaded on construction,
// but we need to do this after going through the event loop so that the
// FontFaceStatusSetter runs before us.
nsCOMPtr<nsIRunnable> loaderTask =
NS_NewRunnableMethod(this, &FontFace::DoLoad);
NS_DispatchToMainThread(loaderTask);
SetStatus(FontFaceLoadStatus::Loading);
DoLoad();
}
void
@ -544,13 +399,7 @@ FontFace::Load(ErrorResult& aRv)
// here.
SetStatus(FontFaceLoadStatus::Loading);
if (mInitialized) {
DoLoad();
} 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;
}
@ -558,8 +407,6 @@ FontFace::Load(ErrorResult& aRv)
void
FontFace::DoLoad()
{
MOZ_ASSERT(mInitialized);
if (!mUserFontEntry) {
MOZ_ASSERT(!HasRule(),
"Rule backed FontFace objects should already have a user font "
@ -722,25 +569,6 @@ FontFace::SetDescriptors(const nsAString& aFamily,
return true;
}
void
FontFace::OnInitialized()
{
MOZ_ASSERT(!mInitialized);
mInitialized = true;
// For a FontFace that was created and immediately had Load() called on
// it, before it had a chance to be initialized, we kick off its load now.
if (mLoadWhenInitialized) {
mLoadWhenInitialized = false;
DoLoad();
}
if (mInFontFaceSet) {
mFontFaceSet->OnFontFaceInitialized(this);
}
}
void
FontFace::GetDesc(nsCSSFontDesc aDescID, nsCSSValue& aResult) const
{

View File

@ -22,8 +22,6 @@ namespace dom {
class FontFaceBufferSource;
struct FontFaceDescriptors;
class FontFaceSet;
class FontFaceInitializer;
class FontFaceStatusSetter;
class Promise;
class StringOrArrayBufferOrArrayBufferView;
}
@ -36,8 +34,6 @@ class FontFace final : public nsISupports,
public nsWrapperCache
{
friend class mozilla::dom::FontFaceBufferSource;
friend class mozilla::dom::FontFaceInitializer;
friend class mozilla::dom::FontFaceStatusSetter;
friend class Entry;
public:
@ -99,14 +95,6 @@ public:
mInFontFaceSet = aInFontFaceSet;
}
/**
* Returns whether this FontFace is initialized. A rule backed
* 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; }
/**
@ -176,13 +164,7 @@ 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);
void InitializeSource(const StringOrArrayBufferOrArrayBufferView& aSource);
// Helper function for Load.
void DoLoad();
@ -206,12 +188,6 @@ private:
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.
*/
@ -273,16 +249,6 @@ 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

@ -664,14 +664,6 @@ void
FontFaceSet::InsertNonRuleFontFace(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
@ -817,7 +809,7 @@ FontFaceSet::FindOrCreateUserFontEntryFromFontFace(const nsAString& aFamilyName,
uint8_t aSheetType)
{
nsCSSValue val;
uint32_t unit;
nsCSSUnit unit;
uint32_t weight = NS_STYLE_FONT_WEIGHT_NORMAL;
int32_t stretch = NS_STYLE_FONT_STRETCH_NORMAL;
@ -1324,16 +1316,6 @@ FontFaceSet::RemoveUnavailableFontFace(FontFace* aFontFace)
MOZ_ASSERT(!mUnavailableFaces.Contains(aFontFace));
}
void
FontFaceSet::OnFontFaceInitialized(FontFace* aFontFace)
{
MOZ_ASSERT(HasAvailableFontFace(aFontFace));
MOZ_ASSERT(!aFontFace->HasRule());
MOZ_ASSERT(aFontFace->IsInitialized());
mPresContext->RebuildUserFontSet();
}
void
FontFaceSet::OnFontFaceStatusChanged(FontFace* aFontFace)
{

View File

@ -135,15 +135,6 @@ public:
already_AddRefed<gfxUserFontEntry>
FindOrCreateUserFontEntryFromFontFace(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);
/**
* Notification method called by a FontFace to indicate that its loading
* status has changed.

View File

@ -192,16 +192,21 @@ function runTest() {
is(new FontFace("test", "url(x)").status, "unloaded", "initial value of FontFace.status when a url() source is used (TEST 6)");
// (TEST 7) Test initial value of FontFace.status when an invalid
// ArrayBuffer source is used.
is(new FontFace("test", new ArrayBuffer(0)).status, "unloaded", "initial value of FontFace.status when an invalid ArrayBuffer source is used (TEST 7)");
// ArrayBuffer source is used. Because it has an implicit initial
// load() call, it should either be "loading" if the browser is
// asynchronously parsing the font data, or "error" if it parsed
// it immediately.
var status = new FontFace("test", new ArrayBuffer(0)).status;
ok(status == "loading" || status == "error", "initial value of FontFace.status when an invalid ArrayBuffer source is used (TEST 7)");
// (TEST 8) Test initial value of FontFace.status when a valid ArrayBuffer
// source is used.
is(new FontFace("test", fontData).status, "unloaded", "initial value of FontFace.status when a valid ArrayBuffer source is used (TEST 8)");
// source is used. Because it has an implicit initial load() call, it
// should either be "loading" if the browser is asynchronously parsing the
// font data, or "loaded" if it parsed it immediately.
status = new FontFace("test", fontData).status;
ok(status == "loading" || status == "loaded", "initial value of FontFace.status when a valid ArrayBuffer source is used (TEST 8)");
// (TEST 9) Test initial value of FontFace.loaded when an invalid url()
// source is used.
return is_pending(new FontFace("test", "").loaded, "initial value of FontFace.loaded when an invalid url() source is used", "(TEST 9)");
// (TEST 9) (old test became redundant with TEST 19)
}).then(function() {
@ -211,15 +216,11 @@ function runTest() {
}).then(function() {
// (TEST 11) Test initial value of FontFace.loaded when an invalid
// ArrayBuffer source is used.
return is_pending(new FontFace("test", new ArrayBuffer(0)).loaded, "initial value of FontFace.loaded when an invalid ArrayBuffer source is used", "(TEST 11)");
// (TEST 11) (old test became redundant with TEST 21)
}).then(function() {
// (TEST 12) Test initial value of FontFace.loaded when a valid ArrayBuffer
// source is used.
return is_pending(new FontFace("test", fontData).loaded, "initial value of FontFace.loaded when a valid ArrayBuffer source is used", "(TEST 12)");
// (TEST 12) (old test became redundant with TEST 20)
}).then(function() {
@ -329,10 +330,11 @@ function runTest() {
gCSSFontFaceDescriptors.src.invalid_values.forEach(function(aSrc) {
srcTests = srcTests.then(function() {
var face = new FontFace("test", aSrc);
return face.load().then(function() {
is(face.status, "error", "FontFace.status should be \"error\" when constructed with an invalid url() src " + aSrc + " (TEST 19");
return face.loaded.then(function() {
ok(false, "FontFace should not load with invalid url() src " + aSrc + " (TEST 19)");
}, function(aError) {
is(aError.name, "SyntaxError", "FontFace.ready should have been rejected with a SyntaxError when loaded with an invalid url() src " + aSrc + " (TEST 19)");
is(aError.name, "SyntaxError", "FontFace.ready should have been rejected with a SyntaxError when constructed with an invalid url() src " + aSrc + " (TEST 19)");
});
});
});
@ -343,8 +345,9 @@ function runTest() {
// (TEST 20) Test that the status of a FontFace constructed with a valid
// ArrayBuffer source eventually becomes "loaded".
var face = new FontFace("test", fontData);
return face.loaded.then(function() {
return face.loaded.then(function(aFace) {
is(face.status, "loaded", "status of FontFace constructed with a valid ArrayBuffer source should eventually be \"loaded\" (TEST 20)");
is(face, aFace, "FontFace.loaded was resolved with the FontFace object once loaded (TEST 20)");
}, function(aError) {
ok(false, "FontFace constructed with a valid ArrayBuffer should eventually load (TEST 20)");
});
@ -702,7 +705,7 @@ function runTest() {
};
});
face = new FontFace("test", "url(BitPattern.woff)");
face = new FontFace("test", "url(BitPattern.woff?test30)");
face.load();
is(face.status, "loading", "FontFace should have status \"loading\" (TEST 30)");
document.fonts.add(face);
@ -746,7 +749,7 @@ function runTest() {
};
});
face = new FontFace("test", "url(BitPattern.woff)");
face = new FontFace("test", "url(BitPattern.woff?test31)");
is(face.status, "unloaded", "FontFace should have status \"unloaded\" (TEST 31)");
document.fonts.add(face);
@ -940,7 +943,7 @@ function runTest() {
is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 37)");
face = new FontFace("test", "url(BitPattern.woff)");
face = new FontFace("test", "url(BitPattern.woff?test37a)");
face.load();
document.fonts.add(face);
is(document.fonts.status, "loading", "document.fonts.status should have status \"loading\" after first font added (TEST 37)");
@ -950,7 +953,7 @@ function runTest() {
is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after first font loaded (TEST 37)");
is(face.status, "loaded", "first FontFace should have status \"loaded\" (TEST 37)");
face2 = new FontFace("test2", "url(BitPattern.woff)");
face2 = new FontFace("test2", "url(BitPattern.woff?test37b)");
face2.load();
document.fonts.add(face2);
is(document.fonts.status, "loading", "document.fonts.status should have status \"loading\" after second font added (TEST 37)");
@ -1040,8 +1043,8 @@ function runTest() {
// loadingdone event dispatched at the FontFaceSet containing it.
var style = document.querySelector("style");
var ruleText = "@font-face { font-family: test; src: url(BitPattern.woff); } " +
"@font-face { font-family: test2; src: url(BitPattern.woff?2); }";
var ruleText = "@font-face { font-family: test; src: url(BitPattern.woff?test39a); } " +
"@font-face { font-family: test2; src: url(BitPattern.woff?test39b); }";
style.textContent = ruleText;
@ -1096,7 +1099,7 @@ function runTest() {
// document.fonts.status from being set to "loaded".
// First, add a FontFace to document.fonts that will load soon.
var face = new FontFace("test", "url(BitPattern.woff)");
var face = new FontFace("test", "url(BitPattern.woff?testlast)");
face.load();
document.fonts.add(face);