Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Remove the 2 different font stacks. This was not fixing the webkit issue... #38

Closed
wants to merge 12 commits into from

3 participants

jeremiele Sean McBride Ryan Carver
jeremiele
Collaborator

....

What seems to be happening is that webkit is changing the font to the
default browser font when downloading a font and not respecting the font
stack. The new code requests a bogus font (sans-serif with quotes to avoid
the css generic font name keyword). This has the effect of falling back
to the default font for the default size. The word 'sans-serif' was chosen
because it also seems to match what Chrome linux requests by default (the
underlaying system library like pango, must match on sans-serif, whereas
it doesn't match anythin on something like 'foo' and would fail for most
cases).

jeremiele jeremiele Remove the 2 different font stacks. This was not fixing the webkit is…
…sue.

What seems to be happening is that webkit is changing the font to the
default browser font when downloading a font and not respecting the font
stack. The new code requests a bogus font (sans-serif with quotes to avoid
the css generic font name keyword). This has the effect of falling back
to the default font for the default size. The word 'sans-serif' was chosen
because it also seems to match what Chrome linux requests by default (the
underlaying system library like pango, must match on sans-serif, whereas
it doesn't match anythin on something like 'foo' and would fail for most
cases).
5dfe67f
Sean McBride

Jeremie: I need to take a deeper look at this, but please don't merge it yet.

The double font stacks weren't meant to solve the webkit issue of fonts changing size more than once. They were meant to prevent all browsers from giving a false negative when the font that's loaded has identical metrics to a single fallback font (such as Liberation Sans and Arial), which means that the width won't change at all. So, you should back out the change to remove the double font stacks, as those are still necessary (and necessary in more browsers than just webkit).

It's good that you've gotten some more insight into the webkit issue, though. The part of webfontloader that's currently working around the webkit issue of size changing before the fonts are loaded is that we wait until we see a new width twice before saying that the fonts are active. That's the part that we would want to change in order to make a better workaround for the webkit early-positive issue.

jeremiele
Collaborator
Sean McBride

Yeah, I'm pretty against removing the double stack, as it will cause a regression in all browsers for the case of loading a font with identical metrics to the fallback. I promise to look into this more deeply later today and see if we can suggest an alternative.

It's good to know exactly what is causing Webkit's strange behavior (that it's ignoring the fallback stack while the web font is loading or rendering). The "wait to see the same size twice" thing was always a bit of a hack since it depends on timing. Now that we actually know what's going on, I think we can remove that "wait to see the size twice" thing and implement a better workaround.

At the moment, though, I can't think of a way to workaround the case where the font we're loading has the same metrics as the browser's default font. Essentially, we need to ignore an observed width that matches the browser's default font in webkit, but if that's actually the final width of the successfully rendered font as well, then we'll never know the difference between the font loading and the browser's default font being rendered.

I'm going to think about this some more and I'll get back to you today since it sounds like you guys would like to get a fix out soon. In the meantime, if you have any other ideas, chime in so we can brainstorm.

jeremiele
Collaborator
Sean McBride

Jeremie,

Alright, I thought about it a bit more, and here's an outline of what I think we should do:

  • We should back out this change, as we need to keep the two font stacks for the case where the metrics are the same as one of the fallbacks.
  • We should get rid of the code that I added to wait to see the if the new width was consistent for two measurements. That code was intended to work around this same webkit issue, and it's not good enough.
  • We should add the following behavior, but only for Webkit:
    • When FontWatchRunner is initialized, in addition to originalSizeA_ and originalSizeB_, we should capture a browserDefaultSize_ using getDefaultFontSize_. This is the size that we'll expect to see when Webkit is ignoring the fallbacks during font loading.
    • When either sizeA or sizeB changes in check_ for the first time, we check if the new size is equal to browserDefaultSize_. If it is, we don't mark it as active and assume that one of the following will happen:
      • If the font is going to be active and has different metrics from the browser default, then either sizeA or sizeB will quickly change again to be different from the original size and different from browserDefaultSize_, so we just wait for that to happen before marking as active.
      • If the font is going to be inactive, then both sizeA and sizeB will change (how quickly?) back to the originally measured fallback sizes. We continue to wait, and when we hit the timeout, if the sizes are back to the original sizes, we mark the font as inactive.
      • If the font is going to be active and has identical metrics to the browser default, then sizeA and sizeB will never change again. They'll stay the same as browserDefaultSize_. In this case, we have to decide how long we want to wait before we mark the font as active (assuming that the font has the same metrics). The length of this waiting period depends on how long an inactive font will keep the browserDefaultSize_ while it attempts to load before going back to the original sizes when it fails. (Or does an inactive font ever change sizes during the loading process?) It might be short or we might have to wait all the way until the inactive timeout. We should experiment here.

This means that we'll get no modifications for other browsers that don't need them, and in Webkit we'll usually get active notifications as quickly as possible, unless the font happens to have the same metrics as the browser default, which is relatively rare. Even in that case, we can tune the waiting period based on the inactive behavior so that we get an accurate active as quickly as possible.

Does that all make sense? Do you want to alter this pull request to try to implement that approach and do some testing to see what the inactive behavior is like so we can tune how long to wait before marking a font that changes to browserDefaultSize_ and stays there as active in webkit?

jeremiele
Collaborator
Sean McBride

Nice, I think this approach will definitely work, but there are a few things we can do to optimize it further:

  • We don't need to keep the lastObservedSizeA_ and lastObservedSizeB_ waiting bit around for any other browsers, since we know this issue only affects webkit. Removing the extra check iteration that these other browsers have to wait would speed things up for them in getting to active just that little bit sooner.
  • Waiting until the timeout in order to mark active when the sizes are equal to webKitFallbackFontSize_ is definitely safe (as you say), but it means that they have to wait quite a while to get to active in cases where the metrics match this fallback. On the other hand, I'm not sure if there's anything foolproof that we can do to speed that up, so maybe it's best for now.

What about something like this within _check?

if ((this.originalSizeA_ != sizeA || this.originalSizeB_ != sizeB) &&
    (!this.webKitFallbackFontSize_ || (this.webKitFallbackFontSize_ != sizeA && this.webKitFallbackFontSize_ != sizeB))) {
  this.finish_(this.activeCallback_);
} else if (this.getTime_() - this.started_ >= 5000) {
  this.finish_(this.inactiveCallback_);
} else {
  this.asyncCheck_();
}

That way we can get rid of lastObservedSizeA_ and lastObservedSizeB_ entirely.

jeremiele
Collaborator
jeremiele
Collaborator
jeremiele jeremiele Added a set of possible last resort fallback font for webkit.
We now check the size against that set to make sure that we do not
report the font has loaded while the font is actually loading (since
webkit changes the font to a last resort fallback font and is not
respecting the CSS font stack). We're also marking the font as active
when reaching the timeout (instead of marking it as inactive). This is
needed in the case of a web font having the same size as any font in the
last resort fallback font set.
5d36bcb
jeremiele
Collaborator
Ryan Carver

Looks like some tabs for indent slipped into these lines.

Collaborator

fixed

jeremiele
Collaborator
src/core/fontwatchrunner.js
((8 lines not shown))
this.finish_(this.activeCallback_);
} else if (this.getTime_() - this.started_ >= 5000) {
- this.finish_(this.inactiveCallback_);
+
+ // In order to handle the fact that a font could be the same size as the
+ // default browser font on a webkit browser, mark the font as active
+ // after 5 seconds.
+ this.finish_(this.webKitLastResortFontSizes_ ?
+ this.activeCallback_ : this.inactiveCallback_);
Sean McBride
seanami added a note

This now means that the fonts can never be inactive on webkit browsers, which isn't right. If the sizeA and sizeB are equal to originalSizeA and originalSizeB, then we know for sure that the font is inactive, and we should mark it as such.

I think we only want to mark active if sizeA and sizeB are true in the webKitLastResortFontSizes map.

jeremiele Collaborator
Sean McBride
seanami added a note

Thanks, that part looks good now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
src/core/fontwatchrunner.js
((13 lines not shown))
+ "'Lucida Sans Unicode'", "'Courier New'", "Tahoma", "Arial",
+ "'Microsoft Sans Serif'", "Times", "'Lucida Console'", "Sans", "Serif",
+ "Monospace"];
+ var lastResortFontSizes = lastResortFonts.length;
+ var webKitLastResortFontSizes = {};
+ var element = this.createHiddenElementWithFont_(lastResortFonts[0], true);
+
+ webKitLastResortFontSizes[this.fontSizer_.getWidth(element)] = true;
+ for (var i = 1; i < lastResortFontSizes; i++) {
+ var font = lastResortFonts[i];
+ this.domHelper_.setStyle(element, this.computeStyleString_(font, true));
+ webKitLastResortFontSizes[this.fontSizer_.getWidth(element)] = true;
+ }
+ this.domHelper_.removeElement(element);
+ return webKitLastResortFontSizes;
+};
Sean McBride
seanami added a note

Here's the file that you reference in the comment: http://trac.webkit.org/browser/trunk/Source/WebCore/css/CSSFontFaceSource.cpp I couldn't find this list of fonts anywhere in that file. Is it referenced from a different one, or how did you track this down?

I guess I'm a little bit worried about this new approach because it means that if the font we're loading happens to have the same width as any of these fonts listed, then we have to wait 5 seconds in order to mark it as inactive. I'm OK with doing it for a single "last resort" fallback on a given platform, but doing it for a list of 12 fonts seems excessive.

Is there any way for us to know which last resort fallback is in use on a given UA? I thought that's what the "__not_a_font__" was supposed to accomplish. On a given UA, having only an unknown value (such as that one) in the font stack will cause that UA's last resort fallback to be rendered instead. Which means that's the only width we should care about on that UA, right? I'm still not convinced that we need to check all 12 of these in every UA, and it seems like the downsides and added complexity are definitely non-ideal.

jeremiele Collaborator
Sean McBride
seanami added a note

Ah, I see. So, in Windows for example, the strategy for finding the last resort fallback font to use starts here: http://trac.webkit.org/browser/trunk/Source/WebCore/platform/graphics/win/FontCacheWin.cpp#L313

It looks like first tries the given fallback font, if one is defined that the browser knows. If not, it tries an ordered list of commonly installed fonts ("Times New Roman", etc.). Then it tries some other default GUI fonts.

The thing that I'm confused about is that the process in that function should always be deterministic. It should return the same last resort fallback for a given UA each time it's invoked. So, using an unknown font name with no fallbacks like __not_a_font should get you a consistently chosen last resort fallback via that function. As long as we're adding the font-width and font-style when measuring the width, then this should exactly match what we get as a last resort fallback anywhere else on the page for the same font-width/font-style. Right? Or is there something off about that reasoning.

I guess I'd like to better understand why an unknown font name isn't a reliable way to measure the last resort fallback font before going with a more complicated fix like this one. As you can see from the comments in their code, webkit is considering changing the strategy for choosing last resort fallbacks in the future, so I don't want to do something here that we're going to have to keep up-to-date with webkit in the future.

jeremiele Collaborator
Sean McBride
seanami added a note

I still don't understand how, on a given platform and browser, in a single page load, getting the width of a span in __not_a_font with the proper font-weight and font-style won't always give you the same last resort fallback as another unknown web font during loading for that same single page load. Is the browser using different last resort fallback fonts within a single page load? Is that determined by what the unknown font / web font is actually called? Or is it determined by analyzing the other fallback fonts in the font-family stack (if any)?

It just seems strange to me that we'd get different last resort fallbacks during the course of a single page load on a single browser/OS combo. If we could figure out what else is used to differentiate, then we should be able to consistently get the right width with only a single measure. Otherwise, I guess we'll have to go with a solution like this.

jeremiele Collaborator
Sean McBride
seanami added a note

Alright, I've been racking my brain and I just can't come up with a better idea than this, but I'm really unhappy about the consequences of this:

Before this change, in webkit browsers, you'd often get an "active" event slightly before the font was actually rendering. This caused problems for people who were trying to to precisely timed things like measuring a font on active, but it worked for people who were just trying to hide the FOUT until the font was available.

After this change, in webkit browsers, you should never get an "active" event early (if the change works). Instead, if the font you're loading happens to match the width of any of these "last resort fallbacks" for your test string, you'll get a five second delay before active is triggered. If you're trying to do something simple like hiding the FOUT, this will result in a consistent, long delay before your content shows up for certain fonts in webkit browsers, which will really piss some of our users off.

That's why I'm really nervous about this change. It trades a tiny error that's relatively common for a big error that's more rare, but also a lot more onerous. Before the change, you could always delay more to workaround the issue. After the change, there'll be nothing you can do to avoid a five second delay with certain fonts. I'm not sure how common this issue will be, but for the people that encounter it, it's going to really suck and they won't be able to work around it.

jeremiele Collaborator
Sean McBride
seanami added a note

Yeah, it sounds like we just have a different set of goals. For us, getting to active as soon as possible is more important than a completely accurate active because most of our customers use font events to hide the fonts until they're loaded, and they want the fonts to appear as quickly as possible. We also serve fonts in data uris within the CSS to webkit browsers, so there's much less of a delay between the browser knowing it's a web font and being able to render it.

For you guys, it sounds like having an accurate active is more important that getting to active quickly, because having an accurate measurement or not adding the font to the stack until it's active are your use cases. That's fine.

A completely accurate solution should be able to do both, but since we can't have complete accuracy, our requirements lead us in opposite directions.

I think the best approach is as you say: make the FontWatch modular and allow the Google module to request a different FontWatchRunner with the webkit last resort strategy added. Typekit will continue to use the existing FontWatchRunner unchanged, and other modules can opt into the webkit FontWatchRunner if they want.

This structure might actually make the code a bit nicer, since you'll be able to make a FontWatchRunner specifically for webkit and only trigger the use of that watcher when the browser is webkit. That means you'll be able to pull all the user agent stuff out of your new FontWatchRunner itself.

It's unfortunate to add more complexity with modularity, but I think it's the only way we'll both be able to get the effects that we want in the interim before we can fix this bug or come up with a more creative solution.

An eventual solution might be to take a totally different approach for font detection. For example, instead of measuring width, perhaps we could use canvas and make a fingerprint of the pixel data (in browsers that support canvas). We'll have to think about it more. But making it modular for now gets you guys a solution without adversely changing things for us, and may also provide the framework to try other font detection strategies like this in the future.

jeremiele Collaborator
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
src-test/core/fontwatchrunnertest.js
((37 lines not shown))
+ assertEquals(1, this.fontActiveCalled_);
+ assertEquals(0, this.fontInactiveCalled_);
+ assertEquals(true, this.fontActive_['fontFamily1 n4']);
+};
+
+FontWatchRunnerTest.prototype.testWatchFontWebKitBrowserIgnoreLastResort = function() {
Sean McBride
seanami added a note

Should this test have the same name as the last test? I think that means that only one of them will run...

Sean McBride
seanami added a note

Looks like you fixed this as well in 0e771df. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Sean McBride

Jeremie, sorry, I was out of town unexpectedly for a funeral. Back now. I left a few more comments for you. I'll keep watching for your responses so we can turn it around quickly and hopefully get a new version out by Thanksgiving.

jeremiele
Collaborator
Sean McBride

General question: Currently, your code finds all of the last resort fallback widths for each FontWatchRunner that gets instantiated, and one of these is instantiated for each font that we're loading. If we're loading a lot of fonts at once, this will be a lot of extra width measurements. Have you looked at how much this slows things down when loading fonts? I'm not sure how expensive taking all of these measurements repeatedly will be.

jeremiele
Collaborator
jeremiele
Collaborator
jeremiele
Collaborator
Sean McBride

Hey Jeremie,

I think I prefer the style of your other webkit_fix_inheritance branch that uses straight inheritance on the FontWatchRunner.

There are definitely some nice aspects to the way that the different pieces are separated in this branch, but I feel like it adds a little bit too much structure around the current way that we do font watching when we may not do that in the future.

The webkit_fix_inheritance branch is a smaller, more straightforward change, and I think it puts us in a better place for exploring FontWatchRunners with totally different internals in the future.

I think if you just move the comment over about the Webkit code near your font list (as you have in the other branch) and update the tests, then that other branch would be pretty much ready to go.

What do you think?

jeremiele
Collaborator
Sean McBride

This pull request is being closed in favor of the new work in #37.

Sean McBride seanami closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 16, 2011
  1. jeremiele

    Remove the 2 different font stacks. This was not fixing the webkit is…

    jeremiele authored
    …sue.
    
    What seems to be happening is that webkit is changing the font to the
    default browser font when downloading a font and not respecting the font
    stack. The new code requests a bogus font (sans-serif with quotes to avoid
    the css generic font name keyword). This has the effect of falling back
    to the default font for the default size. The word 'sans-serif' was chosen
    because it also seems to match what Chrome linux requests by default (the
    underlaying system library like pango, must match on sans-serif, whereas
    it doesn't match anythin on something like 'foo' and would fail for most
    cases).
Commits on Nov 17, 2011
  1. jeremiele

    Revert "Remove the 2 different font stacks. This was not fixing the w…

    jeremiele authored
    …ebkit issue."
    
    This reverts commit 5dfe67f.
  2. jeremiele

    For webkit browsers do not update the lastObserved if the font is the…

    jeremiele authored
    … same as the default font browser.
Commits on Nov 18, 2011
  1. jeremiele
Commits on Nov 19, 2011
  1. jeremiele

    Added a set of possible last resort fallback font for webkit.

    jeremiele authored
    We now check the size against that set to make sure that we do not
    report the font has loaded while the font is actually loading (since
    webkit changes the font to a last resort fallback font and is not
    respecting the CSS font stack). We're also marking the font as active
    when reaching the timeout (instead of marking it as inactive). This is
    needed in the case of a web font having the same size as any font in the
    last resort fallback font set.
Commits on Nov 22, 2011
  1. jeremiele
  2. jeremiele
  3. jeremiele

    Removed __not_a_font__.

    jeremiele authored
Commits on Nov 29, 2011
  1. jeremiele

    Add the ability for a module to choose a strategy to check if a font …

    jeremiele authored
    …has loaded.
    
    For now this is used by the Google Web Fonts module to use a specific strategy
    when on a webkit user agent.
  2. jeremiele
  3. jeremiele
  4. jeremiele
This page is out of date. Refresh to see the latest.
10 src-test/core/domhelpertest.js
View
@@ -91,3 +91,13 @@ DomHelperTest.prototype.testHasClassName = function() {
assertFalse(this.domHelper_.hasClassName(div, 'meuh'));
assertFalse(this.domHelper_.hasClassName(div, 'missingClassName'));
}
+
+DomHelperTest.prototype.testSetStyleString = function() {
+ var div = this.domHelper_.createElement('div');
+
+ this.domHelper_.setStyle(div, 'font-family: Arial');
+ assertTrue(div.style.cssText.indexOf('Arial') != -1);
+ this.domHelper_.setStyle(div, "font-family: 'Times New Roman'");
+ assertTrue(div.style.cssText.indexOf('Arial') == -1);
+ assertTrue(div.style.cssText.indexOf("Times New Roman") != -1);
+};
32 src-test/core/fontwatchertest.js
View
@@ -49,13 +49,17 @@ FontWatcherTest.prototype.setUp = function() {
fail('Fake getTime should not be called.');
};
+ this.fakeUserAgent_ = new webfont.UserAgent('Firefox', '3.6', 'Gecko',
+ '1.9.2', 'Macintosh', '10.6', undefined, true);
+
// Mock out FontWatchRunner to return active/inactive for families we give it
this.originalFontWatchRunner_ = webfont.FontWatchRunner;
this.fontWatchRunnerActiveFamilies_ = [];
this.testStringCount_ = 0;
this.testStrings_ = {};
- webfont.FontWatchRunner = function(activeCallback, inactiveCallback, domHelper,
- fontSizer, asyncCall, getTime, fontFamily, fontDescription, opt_fontTestString) {
+ webfont.FontWatchRunner = function(activeCallback, inactiveCallback, userAgent,
+ domHelper, fontSizer, asyncCall, getTime, fontFamily, fontDescription,
+ opt_fontTestString) {
if (opt_fontTestString) {
self.testStringCount_++;
self.testStrings_[fontFamily] = opt_fontTestString;
@@ -80,7 +84,8 @@ FontWatcherTest.prototype.testWatchOneFontNotLast = function() {
var fontFamilies = [ 'fontFamily1' ];
this.fontWatchRunnerActiveFamilies_ = [ 'fontFamily1' ];
- var fontWatcher = new webfont.FontWatcher(this.fakeDomHelper_, this.fakeEventDispatcher_,
+ var fontWatcher = new webfont.FontWatcher(this.fakeUserAgent_,
+ this.fakeDomHelper_, this.fakeEventDispatcher_,
this.fakeFontSizer_, this.fakeAsyncCall_, this.fakeGetTime_);
fontWatcher.watch(fontFamilies, {}, {}, false);
@@ -94,7 +99,8 @@ FontWatcherTest.prototype.testWatchOneFontActive = function() {
var fontFamilies = [ 'fontFamily1' ];
this.fontWatchRunnerActiveFamilies_ = [ 'fontFamily1' ];
- var fontWatcher = new webfont.FontWatcher(this.fakeDomHelper_, this.fakeEventDispatcher_,
+ var fontWatcher = new webfont.FontWatcher(this.fakeUserAgent_,
+ this.fakeDomHelper_, this.fakeEventDispatcher_,
this.fakeFontSizer_, this.fakeAsyncCall_, this.fakeGetTime_);
fontWatcher.watch(fontFamilies, {}, {}, true);
@@ -112,7 +118,8 @@ FontWatcherTest.prototype.testWatchOneFontInactive = function() {
var fontFamilies = [ 'fontFamily1' ];
this.fontWatchRunnerActiveFamilies_ = [];
- var fontWatcher = new webfont.FontWatcher(this.fakeDomHelper_, this.fakeEventDispatcher_,
+ var fontWatcher = new webfont.FontWatcher(this.fakeUserAgent_,
+ this.fakeDomHelper_, this.fakeEventDispatcher_,
this.fakeFontSizer_, this.fakeAsyncCall_, this.fakeGetTime_);
fontWatcher.watch(fontFamilies, {}, {}, true);
@@ -130,7 +137,8 @@ FontWatcherTest.prototype.testWatchMultipleFontsActive = function() {
var fontFamilies = [ 'fontFamily1', 'fontFamily2', 'fontFamily3' ];
this.fontWatchRunnerActiveFamilies_ = [ 'fontFamily1', 'fontFamily2', 'fontFamily3' ];
- var fontWatcher = new webfont.FontWatcher(this.fakeDomHelper_, this.fakeEventDispatcher_,
+ var fontWatcher = new webfont.FontWatcher(this.fakeUserAgent_,
+ this.fakeDomHelper_, this.fakeEventDispatcher_,
this.fakeFontSizer_, this.fakeAsyncCall_, this.fakeGetTime_);
fontWatcher.watch(fontFamilies, {}, {}, true);
@@ -152,7 +160,8 @@ FontWatcherTest.prototype.testWatchMultipleFontsInactive = function() {
var fontFamilies = [ 'fontFamily1', 'fontFamily2', 'fontFamily3' ];
this.fontWatchRunnerActiveFamilies_ = [];
- var fontWatcher = new webfont.FontWatcher(this.fakeDomHelper_, this.fakeEventDispatcher_,
+ var fontWatcher = new webfont.FontWatcher(this.fakeUserAgent_,
+ this.fakeDomHelper_, this.fakeEventDispatcher_,
this.fakeFontSizer_, this.fakeAsyncCall_, this.fakeGetTime_);
fontWatcher.watch(fontFamilies, {}, {}, true);
@@ -174,7 +183,8 @@ FontWatcherTest.prototype.testWatchMultipleFontsMixed = function() {
var fontFamilies = [ 'fontFamily1', 'fontFamily2', 'fontFamily3' ];
this.fontWatchRunnerActiveFamilies_ = [ 'fontFamily1', 'fontFamily3' ];
- var fontWatcher = new webfont.FontWatcher(this.fakeDomHelper_, this.fakeEventDispatcher_,
+ var fontWatcher = new webfont.FontWatcher(this.fakeUserAgent_,
+ this.fakeDomHelper_, this.fakeEventDispatcher_,
this.fakeFontSizer_, this.fakeAsyncCall_, this.fakeGetTime_);
fontWatcher.watch(fontFamilies, {}, {}, true);
@@ -202,7 +212,8 @@ FontWatcherTest.prototype.testWatchMultipleFontsWithDescriptions = function() {
'fontFamily3': ['n4', 'i4', 'n7']
};
- var fontWatcher = new webfont.FontWatcher(this.fakeDomHelper_, this.fakeEventDispatcher_,
+ var fontWatcher = new webfont.FontWatcher(this.fakeUserAgent_,
+ this.fakeDomHelper_, this.fakeEventDispatcher_,
this.fakeFontSizer_, this.fakeAsyncCall_, this.fakeGetTime_);
fontWatcher.watch(fontFamilies, fontDescriptions, {}, true);
@@ -235,7 +246,8 @@ FontWatcherTest.prototype.testWatchMultipleFontsWithTestStrings = function() {
'fontFamily4': null
};
- var fontWatcher = new webfont.FontWatcher(this.fakeDomHelper_, this.fakeEventDispatcher_,
+ var fontWatcher = new webfont.FontWatcher(this.fakeUserAgent_,
+ this.fakeDomHelper_, this.fakeEventDispatcher_,
this.fakeFontSizer_, this.fakeAsyncCall_, this.fakeGetTime_);
fontWatcher.watch(fontFamilies, {}, fontTestStrings, true);
184 src-test/core/fontwatchrunnertest.js
View
@@ -19,6 +19,9 @@ FontWatchRunnerTest.prototype.setUp = function() {
self.fontInactive_[fontFamily + ' ' + fontDescription] = true;
};
+ this.fakeFirefoxUserAgent_ = new webfont.UserAgent('Firefox', '3.6', 'Gecko',
+ '1.9.2', 'Macintosh', '10.6', undefined, true);
+
this.createElementCalled_ = 0;
this.createdElements_ = [];
this.insertIntoCalled_ = 0;
@@ -45,6 +48,8 @@ FontWatchRunnerTest.prototype.setUp = function() {
},
removeElement: function(el) {
self.removeElementCalled_++;
+ },
+ setStyle: function(e, styleString) {
}
};
@@ -84,17 +89,17 @@ FontWatchRunnerTest.prototype.setUp = function() {
self.asyncCount_++;
func();
};
-
};
FontWatchRunnerTest.prototype.testWatchFontAlreadyLoaded = function() {
- this.timesToCheckWidthsBeforeChange_ = 0;
+ this.timesToCheckWidthsBeforeChange_ = 1;
this.timesToReportChangedWidth_ = 2;
this.timesToGetTimeBeforeTimeout_ = 10;
new webfont.FontWatchRunner(this.activeCallback_, this.inactiveCallback_,
- this.fakeDomHelper_, this.fakeFontSizer_, this.fakeAsyncCall_,
- this.fakeGetTime_, this.fontFamily_, this.fontDescription_);
+ this.fakeFirefoxUserAgent_, this.fakeDomHelper_, this.fakeFontSizer_,
+ this.fakeAsyncCall_, this.fakeGetTime_, this.fontFamily_,
+ this.fontDescription_);
assertEquals(1, this.asyncCount_);
@@ -104,15 +109,16 @@ FontWatchRunnerTest.prototype.testWatchFontAlreadyLoaded = function() {
};
FontWatchRunnerTest.prototype.testWatchFontWaitForLoadActive = function() {
- this.timesToCheckWidthsBeforeChange_ = 3;
+ this.timesToCheckWidthsBeforeChange_ = 2;
this.timesToReportChangedWidth_ = 2;
this.timesToGetTimeBeforeTimeout_ = 10;
new webfont.FontWatchRunner(this.activeCallback_, this.inactiveCallback_,
- this.fakeDomHelper_, this.fakeFontSizer_, this.fakeAsyncCall_,
- this.fakeGetTime_, this.fontFamily_, this.fontDescription_);
+ this.fakeFirefoxUserAgent_, this.fakeDomHelper_, this.fakeFontSizer_,
+ this.fakeAsyncCall_, this.fakeGetTime_, this.fontFamily_,
+ this.fontDescription_);
- assertEquals(4, this.asyncCount_);
+ assertEquals(2, this.asyncCount_);
assertEquals(1, this.fontActiveCalled_);
assertEquals(true, this.fontActive_['fontFamily1 n4']);
@@ -125,8 +131,9 @@ FontWatchRunnerTest.prototype.testWatchFontWaitForLoadInactive = function() {
this.timesToGetTimeBeforeTimeout_ = 5;
new webfont.FontWatchRunner(this.activeCallback_, this.inactiveCallback_,
- this.fakeDomHelper_, this.fakeFontSizer_, this.fakeAsyncCall_,
- this.fakeGetTime_, this.fontFamily_, this.fontDescription_);
+ this.fakeFirefoxUserAgent_, this.fakeDomHelper_, this.fakeFontSizer_,
+ this.fakeAsyncCall_, this.fakeGetTime_, this.fontFamily_,
+ this.fontDescription_);
assertEquals(4, this.asyncCount_);
@@ -135,38 +142,15 @@ FontWatchRunnerTest.prototype.testWatchFontWaitForLoadInactive = function() {
assertEquals(true, this.fontInactive_['fontFamily1 n4']);
};
-/**
- * This test ensures that even if the fonts change width for one cycle and
- * then change back, active won't be fired. This works around an issue in Webkit
- * browsers, where an inactive webfont will briefly change widths for one cycle
- * and then change back to fallback widths on the next cycle. This is apparently
- * due to some quirk in the way that web fonts are rendered.
- */
-FontWatchRunnerTest.prototype.testWatchFontWithInconsistentWidthIsStillInactive = function() {
- this.timesToCheckWidthsBeforeChange_ = 3;
- // Only report a new width for one cycle, then switch back to original fallback width
- this.timesToReportChangedWidth_ = 1;
- this.timesToGetTimeBeforeTimeout_ = 10;
-
- new webfont.FontWatchRunner(this.activeCallback_, this.inactiveCallback_,
- this.fakeDomHelper_, this.fakeFontSizer_, this.fakeAsyncCall_,
- this.fakeGetTime_, this.fontFamily_, this.fontDescription_);
-
- assertEquals(9, this.asyncCount_);
-
- assertEquals(0, this.fontActiveCalled_);
- assertEquals(1, this.fontInactiveCalled_);
- assertEquals(true, this.fontInactive_['fontFamily1 n4']);
-};
-
FontWatchRunnerTest.prototype.testDomWithDefaultTestString = function() {
this.timesToCheckWidthsBeforeChange_ = 3;
this.timesToReportChangedWidth_ = 2;
this.timesToGetTimeBeforeTimeout_ = 10;
new webfont.FontWatchRunner(this.activeCallback_, this.inactiveCallback_,
- this.fakeDomHelper_, this.fakeFontSizer_, this.fakeAsyncCall_,
- this.fakeGetTime_, this.fontFamily_, this.fontDescription_);
+ this.fakeFirefoxUserAgent_, this.fakeDomHelper_, this.fakeFontSizer_,
+ this.fakeAsyncCall_, this.fakeGetTime_, this.fontFamily_,
+ this.fontDescription_);
assertEquals(4, this.createElementCalled_);
assertEquals('span', this.createdElements_[0]['name']);
@@ -196,8 +180,9 @@ FontWatchRunnerTest.prototype.testDomWithNotDefaultTestString = function() {
this.timesToGetTimeBeforeTimeout_ = 10;
new webfont.FontWatchRunner(this.activeCallback_, this.inactiveCallback_,
- this.fakeDomHelper_, this.fakeFontSizer_, this.fakeAsyncCall_,
- this.fakeGetTime_, this.fontFamily_, this.fontDescription_, 'testString');
+ this.fakeFirefoxUserAgent_, this.fakeDomHelper_, this.fakeFontSizer_,
+ this.fakeAsyncCall_, this.fakeGetTime_, this.fontFamily_,
+ this.fontDescription_, 'testString');
assertEquals(4, this.createElementCalled_);
assertEquals('span', this.createdElements_[0]['name']);
@@ -218,5 +203,128 @@ FontWatchRunnerTest.prototype.testDomWithNotDefaultTestString = function() {
assertEquals('testString', this.createdElements_[3]['innerHtml']);
assertEquals(4, this.insertIntoCalled_);
assertEquals(4, this.removeElementCalled_);
+};
+
+FontWatchRunnerTest.prototype.testLastResortFontIgnored = function() {
+ var userAgent = new webfont.UserAgent('Chrome', '16.0.912.36', 'AppleWebKit',
+ '531.9', 'Macintosh', '10.6', undefined, true);
+ var lastResortFontsCount = 11;
+ var originalSizeCount = 2;
+ var firstSize = 2;
+ var secondSize = 2;
+ var thirdSize = 2;
+
+ new webfont.FontWatchRunner(this.activeCallback_, this.inactiveCallback_,
+ userAgent, this.fakeDomHelper_, {getWidth: function() {
+ if (lastResortFontsCount > 0) {
+ lastResortFontsCount--;
+ return 2;
+ }
+ if (originalSizeCount > 0) {
+ originalSizeCount--;
+ return 1;
+ }
+ if (firstSize > 0) {
+ firstSize--;
+ return 1;
+ }
+ if (secondSize > 0) {
+ secondSize--;
+ return 2;
+ }
+ if (thirdSize > 0) {
+ thirdSize--;
+ return 3;
+ }
+ }}, this.fakeAsyncCall_, this.fakeGetTime_, this.fontFamily_,
+ this.fontDescription_);
+
+ assertEquals(2, this.asyncCount_);
+
+ // When on webkit time out ends up activating the font.
+ assertEquals(1, this.fontActiveCalled_);
+ assertEquals(0, this.fontInactiveCalled_);
+ assertEquals(true, this.fontActive_['fontFamily1 n4']);
+};
+
+FontWatchRunnerTest.prototype.testLastResortFontActiveWhenSizeMatch
+ = function() {
+ this.timesToGetTimeBeforeTimeout_ = 3;
+ var userAgent = new webfont.UserAgent('Chrome', '16.0.912.36', 'AppleWebKit',
+ '531.9', 'Macintosh', '10.6', undefined, true);
+ var lastResortFontsCount = 11;
+ var originalSizeCount = 2;
+ var firstSize = 2;
+
+ new webfont.FontWatchRunner(this.activeCallback_, this.inactiveCallback_,
+ userAgent, this.fakeDomHelper_, {getWidth: function() {
+ if (lastResortFontsCount > 0) {
+ lastResortFontsCount--;
+ return 2;
+ }
+ if (originalSizeCount > 0) {
+ originalSizeCount--;
+ return 1;
+ }
+ if (firstSize > 0) {
+ firstSize--;
+ return 1;
+ }
+ return 2;
+ }}, this.fakeAsyncCall_, this.fakeGetTime_, this.fontFamily_,
+ this.fontDescription_);
+
+ assertEquals(2, this.asyncCount_);
+ assertEquals(1, this.fontActiveCalled_);
+ assertEquals(0, this.fontInactiveCalled_);
+ assertEquals(true, this.fontActive_['fontFamily1 n4']);
+};
+
+FontWatchRunnerTest.prototype.testLastResortFontInactiveWhenSizeNoMatch
+ = function() {
+ this.timesToGetTimeBeforeTimeout_ = 3;
+ var userAgent = new webfont.UserAgent('Chrome', '16.0.912.36', 'AppleWebKit',
+ '531.9', 'Macintosh', '10.6', undefined, true);
+ var lastResortFontsCount = 11;
+ var originalSizeCount = 2;
+ var firstSize = 2;
+ var secondSize = 2;
+ var thirdSize = 2;
+
+ new webfont.FontWatchRunner(this.activeCallback_, this.inactiveCallback_,
+ userAgent, this.fakeDomHelper_, {getWidth: function(elem) {
+ if (lastResortFontsCount > 0) {
+ lastResortFontsCount--;
+ return 2;
+ }
+ if (originalSizeCount > 0) {
+ originalSizeCount--;
+ return 1;
+ }
+ if (firstSize > 0) {
+ firstSize--;
+ return 1;
+ }
+ if (secondSize > 0) {
+ secondSize--;
+ return 2;
+ }
+ if (thirdSize == 2) {
+ thirdSize--;
+ return 2;
+ }
+ if (thirdSize == 1) {
+ thirdSize--;
+ return 4;
+ }
+ }}, this.fakeAsyncCall_, this.fakeGetTime_, this.fontFamily_,
+ this.fontDescription_);
+
+ assertEquals(2, this.asyncCount_);
+
+ // When on webkit time out ends up activating the font.
+ assertEquals(0, this.fontActiveCalled_);
+ assertEquals(1, this.fontInactiveCalled_);
+ assertEquals(true, this.fontInactive_['fontFamily1 n4']);
};
50 src/core/basecheckstrategy.js
View
@@ -0,0 +1,50 @@
+/**
+ * @constructor
+ */
+webfont.BaseCheckStrategy = function(sizingElementCreator, activeCallback,
+ inactiveCallback, fontSizer, fontFamily, fontDescription, opt_fontTestString) {
+ this.sizingElementCreator_ = sizingElementCreator;
+ this.activeCallback_ = activeCallback;
+ this.inactiveCallback_ = inactiveCallback;
+ this.fontSizer_ = fontSizer;
+ this.fontFamily_ = fontFamily;
+ this.fontDescription_ = fontDescription;
+ this.fontTestString_ = opt_fontTestString
+ || webfont.FontWatchRunner.DEFAULT_TEST_STRING;
+};
+
+webfont.BaseCheckStrategy.prototype.setUp = function() {
+ this.originalSizeA_ = this.getStackSize_(webfont.FontWatchRunner.SANS_STACK);
+ this.originalSizeB_ = this.getStackSize_(webfont.FontWatchRunner.SERIF_STACK);
+ this.requestedFontA_ = this.sizingElementCreator_.createSizingElement(
+ this.fontFamily_, webfont.FontWatchRunner.SANS_STACK,
+ this.fontDescription_, this.fontTestString_);
+ this.requestedFontB_ = this.sizingElementCreator_.createSizingElement(
+ this.fontFamily_, webfont.FontWatchRunner.SERIF_STACK,
+ this.fontDescription_, this.fontTestString_);
+};
+
+webfont.BaseCheckStrategy.prototype.tearDown = function() {
+ this.sizingElementCreator_.deleteSizingElement(this.requestedFontA_);
+ this.sizingElementCreator_.deleteSizingElement(this.requestedFontB_);
+};
+
+webfont.BaseCheckStrategy.prototype.getActiveCallback = function() {
+ return webfont.bind(this, function() {
+ this.activeCallback_(this.fontFamily_, this.fontDescription_);
+ });
+};
+
+webfont.BaseCheckStrategy.prototype.getTimeoutCallback = function() {
+ return webfont.bind(this, function() {
+ this.inactiveCallback_(this.fontFamily_, this.fontDescription_);
+ });
+};
+
+webfont.BaseCheckStrategy.prototype.getStackSize_ = function(stack) {
+ var stackElement = this.sizingElementCreator_.createSizingElement('',
+ stack, this.fontDescription_, this.fontTestString_);
+ var size = this.fontSizer_.getWidth(stackElement);
+ this.sizingElementCreator_.deleteSizingElement(stackElement);
+ return size;
+};
29 src/core/defaultcheckstrategy.js
View
@@ -0,0 +1,29 @@
+/**
+ * @constructor
+ */
+webfont.DefaultCheckStrategy = function(sizingElementCreator, activeCallback,
+ inactiveCallback, fontSizer, fontFamily, fontDescription, opt_fontTestString) {
+ webfont.DefaultCheckStrategy.superCtor_.call(this, sizingElementCreator,
+ activeCallback, inactiveCallback, fontSizer, fontFamily, fontDescription,
+ opt_fontTestString);
+};
+webfont.extendsClass(webfont.BaseCheckStrategy, webfont.DefaultCheckStrategy);
+
+webfont.DefaultCheckStrategy.prototype.setUp = function() {
+ webfont.DefaultCheckStrategy.super_.setUp.call(this);
+ this.lastObservedSizeA_ = this.originalSizeA_;
+ this.lastObservedSizeB_ = this.originalSizeB_;
+};
+
+webfont.DefaultCheckStrategy.prototype.isLoaded = function() {
+ var sizeA = this.fontSizer_.getWidth(this.requestedFontA_);
+ var sizeB = this.fontSizer_.getWidth(this.requestedFontB_);
+
+ if ((this.originalSizeA_ != sizeA || this.originalSizeB_ != sizeB) &&
+ this.lastObservedSizeA_ == sizeA && this.lastObservedSizeB_ == sizeB) {
+ return true;
+ }
+ this.lastObservedSizeA_ = sizeA;
+ this.lastObservedSizeB_ = sizeB;
+ return false;
+};
19 src/core/domhelper.js
View
@@ -25,9 +25,9 @@ webfont.DomHelper.prototype.createElement = function(elem, opt_attr,
for (var attr in opt_attr) {
// protect against native prototype augmentations
if (opt_attr.hasOwnProperty(attr)) {
- if (attr == "style" && this.userAgent_.getName() == "MSIE") {
- domElement.style.cssText = opt_attr[attr];
- } else {
+ if (attr == "style") {
+ this.setStyle(domElement, opt_attr[attr]);
+ } else {
domElement.setAttribute(attr, opt_attr[attr]);
}
}
@@ -164,3 +164,16 @@ webfont.DomHelper.prototype.hasClassName = function(e, name) {
}
return false;
};
+
+/**
+ * Sets the style attribute on an element.
+ * @param {Element} e The element.
+ * @param {string} styleString The style string.
+ */
+webfont.DomHelper.prototype.setStyle = function(e, styleString) {
+ if (this.userAgent_.getName() == "MSIE") {
+ e.style.cssText = styleString;
+ } else {
+ e.setAttribute("style", styleString);
+ }
+};
16 src/core/font.js
View
@@ -29,6 +29,8 @@ webfont.WebFont.prototype.load = function(configuration) {
webfont.WebFont.prototype.isModuleSupportingUserAgent_ = function(module, eventDispatcher,
fontWatcher, support) {
+ var checkStrategyCtor = module.getCheckStrategyCtor ?
+ module.getCheckStrategyCtor() : webfont.DefaultCheckStrategy;
if (!support) {
var allModulesLoaded = --this.moduleLoading_ == 0;
@@ -40,26 +42,28 @@ webfont.WebFont.prototype.isModuleSupportingUserAgent_ = function(module, eventD
eventDispatcher.dispatchLoading();
}
}
- fontWatcher.watch([], {}, {}, allModulesLoaded);
+ fontWatcher.watch([], {}, {}, checkStrategyCtor, allModulesLoaded);
return;
}
module.load(webfont.bind(this, this.onModuleReady_, eventDispatcher,
- fontWatcher));
+ fontWatcher, checkStrategyCtor));
};
webfont.WebFont.prototype.onModuleReady_ = function(eventDispatcher, fontWatcher,
- fontFamilies, opt_fontDescriptions, opt_fontTestStrings) {
+ checkStrategyCtor, fontFamilies, opt_fontDescriptions,
+ opt_fontTestStrings) {
var allModulesLoaded = --this.moduleLoading_ == 0;
if (allModulesLoaded) {
eventDispatcher.dispatchLoading();
}
this.asyncCall_(webfont.bind(this, function(_fontWatcher, _fontFamilies,
- _fontDescriptions, _fontTestStrings, _allModulesLoaded) {
+ _fontDescriptions, _fontTestStrings, _checkStrategyCtor,
+ _allModulesLoaded) {
_fontWatcher.watch(_fontFamilies, _fontDescriptions || {},
- _fontTestStrings || {}, _allModulesLoaded);
+ _fontTestStrings || {}, _checkStrategyCtor, _allModulesLoaded);
}, fontWatcher, fontFamilies, opt_fontDescriptions, opt_fontTestStrings,
- allModulesLoaded));
+ checkStrategyCtor, allModulesLoaded));
};
webfont.WebFont.prototype.load_ = function(eventDispatcher, configuration) {
11 src/core/fontwatcher.js
View
@@ -34,7 +34,7 @@ webfont.FontWatcher.DEFAULT_VARIATION = 'n4';
* @param {boolean} last True if this is the last set of families to watch.
*/
webfont.FontWatcher.prototype.watch = function(fontFamilies, fontDescriptions,
- fontTestStrings, last) {
+ fontTestStrings, checkStrategyCtor, last) {
var length = fontFamilies.length;
for (var i = 0; i < length; i++) {
@@ -49,6 +49,8 @@ webfont.FontWatcher.prototype.watch = function(fontFamilies, fontDescriptions,
this.last_ = last;
}
+ var sizingElementCreator = new webfont.SizingElementCreator(this.domHelper_);
+
for (var i = 0; i < length; i++) {
var fontFamily = fontFamilies[i];
var descriptions = fontDescriptions[fontFamily];
@@ -61,9 +63,10 @@ webfont.FontWatcher.prototype.watch = function(fontFamilies, fontDescriptions,
var activeCallback = webfont.bind(this, this.fontActive_);
var inactiveCallback = webfont.bind(this, this.fontInactive_)
- new webfont.FontWatchRunner(activeCallback, inactiveCallback,
- this.domHelper_, this.fontSizer_, this.asyncCall_, this.getTime_,
- fontFamily, fontDescription, fontTestString);
+ new webfont.FontWatchRunner(this.asyncCall_, this.getTime_,
+ new checkStrategyCtor(sizingElementCreator, activeCallback,
+ inactiveCallback, this.fontSizer_, fontFamily, fontDescription,
+ fontTestString));
}
}
};
99 src/core/fontwatchrunner.js
View
@@ -1,38 +1,14 @@
/**
* @constructor
- * @param {function(string, string)} activeCallback
- * @param {function(string, string)} inactiveCallback
- * @param {webfont.DomHelper} domHelper
- * @param {Object.<string, function(Object): number>} fontSizer
* @param {function(function(), number=)} asyncCall
* @param {function(): number} getTime
- * @param {string} fontFamily
- * @param {string} fontDescription
- * @param {string=} opt_fontTestString
+ * @param {Object} strategy
*/
-webfont.FontWatchRunner = function(activeCallback, inactiveCallback, domHelper,
- fontSizer, asyncCall, getTime, fontFamily, fontDescription, opt_fontTestString) {
- this.activeCallback_ = activeCallback;
- this.inactiveCallback_ = inactiveCallback;
- this.domHelper_ = domHelper;
- this.fontSizer_ = fontSizer;
+webfont.FontWatchRunner = function(asyncCall, getTime, strategy) {
this.asyncCall_ = asyncCall;
this.getTime_ = getTime;
- this.nameHelper_ = new webfont.CssFontFamilyName();
- this.fvd_ = new webfont.FontVariationDescription();
- this.fontFamily_ = fontFamily;
- this.fontDescription_ = fontDescription;
- this.fontTestString_ = opt_fontTestString || webfont.FontWatchRunner.DEFAULT_TEST_STRING;
- this.originalSizeA_ = this.getDefaultFontSize_(
- webfont.FontWatchRunner.DEFAULT_FONTS_A);
- this.originalSizeB_ = this.getDefaultFontSize_(
- webfont.FontWatchRunner.DEFAULT_FONTS_B);
- this.lastObservedSizeA_ = this.originalSizeA_;
- this.lastObservedSizeB_ = this.originalSizeB_;
- this.requestedFontA_ = this.createHiddenElementWithFont_(
- webfont.FontWatchRunner.DEFAULT_FONTS_A);
- this.requestedFontB_ = this.createHiddenElementWithFont_(
- webfont.FontWatchRunner.DEFAULT_FONTS_B);
+ this.strategy_ = strategy;
+ this.strategy_.setUp();
this.started_ = getTime();
this.check_();
};
@@ -46,7 +22,7 @@ webfont.FontWatchRunner = function(activeCallback, inactiveCallback, domHelper,
* @type {string}
* @const
*/
-webfont.FontWatchRunner.DEFAULT_FONTS_A = "arial,'URW Gothic L',sans-serif";
+webfont.FontWatchRunner.SANS_STACK = "arial,'URW Gothic L',sans-serif";
/**
* A set of serif fonts and a generic family that cover most platforms. We
@@ -59,7 +35,7 @@ webfont.FontWatchRunner.DEFAULT_FONTS_A = "arial,'URW Gothic L',sans-serif";
* @type {string}
* @const
*/
-webfont.FontWatchRunner.DEFAULT_FONTS_B = "Georgia,'Century Schoolbook L',serif";
+webfont.FontWatchRunner.SERIF_STACK = "Georgia,'Century Schoolbook L',serif";
/**
* Default test string. Characters are chosen so that their widths vary a lot
@@ -71,32 +47,14 @@ webfont.FontWatchRunner.DEFAULT_FONTS_B = "Georgia,'Century Schoolbook L',serif"
webfont.FontWatchRunner.DEFAULT_TEST_STRING = 'BESs';
/**
- * Checks the size of the two spans against their original sizes during each
- * async loop. If the size of one of the spans is different than the original
- * size, then we know that the font is rendering and finish with the active
- * callback. If we wait more than 5 seconds and nothing has changed, we finish
- * with the inactive callback.
- *
- * Because of an odd Webkit quirk, we wait to observe the new width twice
- * in a row before finishing with the active callback. Sometimes, Webkit will
- * render the spans with a changed width for one iteration even though the font
- * is broken. This only happens for one async loop, so waiting for 2 consistent
- * measurements allows us to work around the quirk.
- *
* @private
*/
webfont.FontWatchRunner.prototype.check_ = function() {
- var sizeA = this.fontSizer_.getWidth(this.requestedFontA_);
- var sizeB = this.fontSizer_.getWidth(this.requestedFontB_);
-
- if ((this.originalSizeA_ != sizeA || this.originalSizeB_ != sizeB) &&
- this.lastObservedSizeA_ == sizeA && this.lastObservedSizeB_ == sizeB) {
- this.finish_(this.activeCallback_);
+ if (this.strategy_.isLoaded()) {
+ this.finish_(this.strategy_.getActiveCallback());
} else if (this.getTime_() - this.started_ >= 5000) {
- this.finish_(this.inactiveCallback_);
+ this.finish_(this.strategy_.getTimeoutCallback());
} else {
- this.lastObservedSizeA_ = sizeA;
- this.lastObservedSizeB_ = sizeB;
this.asyncCheck_();
}
};
@@ -114,42 +72,9 @@ webfont.FontWatchRunner.prototype.asyncCheck_ = function() {
/**
* @private
- * @param {function(string, string)} callback
+ * @param {function()} callback
*/
webfont.FontWatchRunner.prototype.finish_ = function(callback) {
- this.domHelper_.removeElement(this.requestedFontA_);
- this.domHelper_.removeElement(this.requestedFontB_);
- callback(this.fontFamily_, this.fontDescription_);
-};
-
-/**
- * @private
- * @param {string} defaultFonts
- */
-webfont.FontWatchRunner.prototype.getDefaultFontSize_ = function(defaultFonts) {
- var defaultFont = this.createHiddenElementWithFont_(defaultFonts, true);
- var size = this.fontSizer_.getWidth(defaultFont);
-
- this.domHelper_.removeElement(defaultFont);
- return size;
-};
-
-/**
- * @private
- * @param {string} defaultFonts
- * @param {boolean=} opt_withoutFontFamily
- */
-webfont.FontWatchRunner.prototype.createHiddenElementWithFont_ = function(
- defaultFonts, opt_withoutFontFamily) {
- var variationCss = this.fvd_.expand(this.fontDescription_);
- var styleString = "position:absolute;top:-999px;left:-999px;" +
- "font-size:300px;width:auto;height:auto;line-height:normal;margin:0;" +
- "padding:0;font-variant:normal;font-family:" + (opt_withoutFontFamily ? "" :
- this.nameHelper_.quote(this.fontFamily_) + ",") +
- defaultFonts + ";" + variationCss;
- var span = this.domHelper_.createElement('span', { 'style': styleString },
- this.fontTestString_);
-
- this.domHelper_.insertInto('body', span);
- return span;
+ this.strategy_.tearDown();
+ callback();
};
57 src/core/lastresortwebkitcheckstrategy.js
View
@@ -0,0 +1,57 @@
+/**
+ * @constructor
+ */
+webfont.LastResortWebKitCheckStrategy = function(sizingElementCreator, activeCallback,
+ inactiveCallback, fontSizer, fontFamily, fontDescription, opt_fontTestString) {
+ webfont.LastResortWebKitCheckStrategy.superCtor_.call(this,
+ sizingElementCreator, activeCallback, inactiveCallback, fontSizer,
+ fontFamily, fontDescription, opt_fontTestString);
+};
+webfont.extendsClass(webfont.BaseCheckStrategy,
+ webfont.LastResortWebKitCheckStrategy);
+
+webfont.LastResortWebKitCheckStrategy.prototype.setUp = function() {
+ webfont.LastResortWebKitCheckStrategy.super_.setUp.call(this);
+ this.webKitLastResortFontSizes_ = this.setUpWebKitLastResortFontSizes_();
+};
+
+webfont.LastResortWebKitCheckStrategy.prototype.isLoaded = function() {
+ var sizeA = this.fontSizer_.getWidth(this.requestedFontA_);
+ var sizeB = this.fontSizer_.getWidth(this.requestedFontB_);
+ return ((this.originalSizeA_ != sizeA || this.originalSizeB_ != sizeB)
+ && (!this.webKitLastResortFontSizes_[sizeA]
+ && !this.webKitLastResortFontSizes_[sizeB]));
+};
+
+webfont.LastResortWebKitCheckStrategy.prototype.getTimeoutCallback = function() {
+ return this.getActiveCallback();
+};
+
+/**
+ * While loading a web font webkit applies a last resort fallback font to the
+ * element on which the web font is applied.
+ * See file: WebKit/Source/WebCore/css/CSSFontFaceSource.cpp.
+ * Looking at the different implementation for the different platforms,
+ * the last resort fallback font is different. This code uses the default
+ * OS/browsers values.
+ */
+webfont.LastResortWebKitCheckStrategy.prototype.setUpWebKitLastResortFontSizes_ = function() {
+ var lastResortFonts = ["Times New Roman",
+ "Lucida Sans Unicode", "Courier New", "Tahoma", "Arial",
+ "Microsoft Sans Serif", "Times", "Lucida Console", "Sans", "Serif",
+ "Monospace"];
+ var lastResortFontSizes = lastResortFonts.length;
+ var webKitLastResortFontSizes = {};
+ var element = this.sizingElementCreator_.createSizingElement(lastResortFonts[0],
+ '', this.fontDescription_, this.fontTestString_);
+
+ webKitLastResortFontSizes[this.fontSizer_.getWidth(element)] = true;
+ for (var i = 1; i < lastResortFontSizes; i++) {
+ var font = lastResortFonts[i];
+ this.sizingElementCreator_.updateSizingElementStyle(element, font, '',
+ this.fontDescription_);
+ webKitLastResortFontSizes[this.fontSizer_.getWidth(element)] = true;
+ }
+ this.sizingElementCreator_.deleteSizingElement(element);
+ return webKitLastResortFontSizes;
+};
14 src/core/namespace.js
View
@@ -14,3 +14,17 @@ webfont.bind = function(context, func, opt_args) {
return func.apply(context, args);
};
};
+
+webfont.extendsClass = function(baseClass, subClass) {
+
+ // Avoid polluting the baseClass prototype object with methods from the
+ // subClass
+ /** @constructor */
+ function baseExtendClass() {};
+ baseExtendClass.prototype = baseClass.prototype;
+ subClass.prototype = new baseExtendClass();
+
+ subClass.prototype.constructor = subClass;
+ subClass.superCtor_ = baseClass;
+ subClass.super_ = baseClass.prototype;
+};
49 src/core/sizingelementcreator.js
View
@@ -0,0 +1,49 @@
+/**
+ * @constructor
+ */
+webfont.SizingElementCreator = function(domHelper) {
+ this.domHelper_ = domHelper;
+ this.fvd_ = new webfont.FontVariationDescription();
+ this.nameHelper_ = new webfont.CssFontFamilyName();
+};
+
+webfont.SizingElementCreator.prototype.createSizingElement = function(
+ fontFamily, fontFamilyStack, fontDescription, fontTestString) {
+ var styleString = this.computeSizingElementStyle_(fontFamily,
+ fontFamilyStack, fontDescription);
+ var span = this.domHelper_.createElement('span', { 'style': styleString },
+ fontTestString);
+
+ this.domHelper_.insertInto('body', span);
+ return span;
+};
+
+webfont.SizingElementCreator.prototype.deleteSizingElement = function(element) {
+ this.domHelper_.removeElement(element);
+};
+
+/**
+ * @return {string} The style string for a sizing element.
+ */
+webfont.SizingElementCreator.prototype.computeSizingElementStyle_ = function(
+ fontFamily, fontFamilyStack, fontDescription) {
+ var variationCss = this.fvd_.expand(fontDescription);
+ var fontFamilyStringBuilder = [];
+ if (fontFamily.length > 0) {
+ fontFamilyStringBuilder.push(this.nameHelper_.quote(fontFamily));
+ }
+ if (fontFamilyStack.length > 0) {
+ fontFamilyStringBuilder.push(fontFamilyStack);
+ }
+ var styleString = "position:absolute;top:-999px;left:-999px;" +
+ "font-size:300px;width:auto;height:auto;line-height:normal;margin:0;" +
+ "padding:0;font-variant:normal;font-family:"
+ + fontFamilyStringBuilder.join(",") + ";" + variationCss;
+ return styleString;
+};
+
+webfont.SizingElementCreator.prototype.updateSizingElementStyle = function(
+ element, fontFamily, fontFamilyStack, fontDescription) {
+ this.domHelper_.setStyle(element, this.computeSizingElementStyle_(fontFamily,
+ fontFamilyStack, fontDescription));
+};
7 src/google/googlefontapi.js
View
@@ -13,6 +13,13 @@ webfont.GoogleFontApi.prototype.supportUserAgent = function(userAgent, support)
support(userAgent.isSupportingWebFont());
};
+webfont.GoogleFontApi.prototype.getCheckStrategyCtor = function() {
+ if (this.userAgent_.getEngine() == "AppleWebKit") {
+ return webfont.LastResortWebKitCheckStrategy;
+ }
+ return webfont.DefaultCheckStrategy;
+};
+
webfont.GoogleFontApi.prototype.load = function(onReady) {
var domHelper = this.domHelper_;
var nonBlockingIe = this.userAgent_.getName() == 'MSIE' &&
4 src/modules.yml
View
@@ -5,6 +5,10 @@ core:
- core/useragentparser.js
- core/eventdispatcher.js
- core/fontmoduleloader.js
+ - core/sizingelementcreator.js
+ - core/basecheckstrategy.js
+ - core/defaultcheckstrategy.js
+ - core/lastresortwebkitcheckstrategy.js
- core/fontwatcher.js
- core/fontwatchrunner.js
- core/font.js
Something went wrong with that request. Please try again.