Repository: bramstein/fontfaceobserver
Branch: master
Commit: 46553ba71b2a
Files: 23
Total size: 107.1 KB
Directory structure:
gitextract_wegyr1t_/
├── .gitignore
├── .travis.yml
├── Gruntfile.js
├── LICENSE
├── README.md
├── bower.json
├── exports.js
├── externs-commonjs.js
├── externs.js
├── fontfaceobserver.js
├── fontfaceobserver.standalone.js
├── package.json
├── src/
│ ├── descriptors.js
│ ├── observer.js
│ └── ruler.js
├── test/
│ ├── assets/
│ │ ├── index.html
│ │ └── late.css
│ ├── browser-test.html
│ ├── deps.js
│ ├── index.html
│ ├── observer-test.js
│ └── ruler-test.js
└── vendor/
└── google/
└── base.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
build
node_modules
================================================
FILE: .travis.yml
================================================
sudo: false
language: node_js
before_install:
- npm install -g grunt-cli
node_js:
- "4"
matrix:
fast_finish: true
cache:
directories:
- node_modules
================================================
FILE: Gruntfile.js
================================================
module.exports = function (grunt) {
require('google-closure-compiler').grunt(grunt, {
max_parallel_compilations: require('os').cpus().length
});
var compilerOptions = {
compilation_level: 'ADVANCED_OPTIMIZATIONS',
warning_level: 'VERBOSE',
summary_detail_level: 3,
language_in: 'ECMASCRIPT5_STRICT',
output_wrapper: '(function(){%output%}());',
use_types_for_optimization: true,
externs: ['externs-commonjs.js']
};
var src = [
'vendor/google/base.js',
'node_modules/closure-dom/src/dom.js',
'src/descriptors.js',
'src/ruler.js',
'src/observer.js',
'exports.js'
];
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
clean: {
options: {
force: true
},
build: ['build']
},
exec: {
test: './node_modules/.bin/mocha-headless-chrome -f test/index.html',
deps: 'calcdeps -i src -i exports.js -p src -p ./vendor/google/base.js -p node_modules/closure-dom/src/dom.js -o deps > test/deps.js'
},
jshint: {
all: ['src/**/*.js'],
options: {
// ... better written as dot notation
'-W069': true,
// type definitions
'-W030': true,
// Don't make functions within loops
'-W083': true,
// Wrap the /regexp/ literal in parens to disambiguate the slash operator
'-W092': true
}
},
'closure-compiler': {
dist: {
files: {
'fontfaceobserver.js': src
},
options: compilerOptions
},
compile: {
files: {
'build/fontfaceobserver.js': src
},
options: compilerOptions
},
debug: {
files: {
'build/fontfaceobserver.debug.js': src
},
options: Object.assign({}, compilerOptions, {
debug: true,
formatting: ['PRETTY_PRINT', 'PRINT_INPUT_DELIMITER']
})
}
},
concat: {
options: {
banner: '/* Font Face Observer v<%= pkg.version %> - © Bram Stein. License: BSD-3-Clause */'
},
dist_promises: {
src: ['node_modules/promis/promise.js', 'build/fontfaceobserver.js'],
dest: 'fontfaceobserver.js'
},
dist: {
src: ['build/fontfaceobserver.js'],
dest: 'fontfaceobserver.standalone.js'
}
}
});
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-exec');
grunt.registerTask('compile', ['closure-compiler:compile']);
grunt.registerTask('debug', ['closure-compiler:debug']);
grunt.registerTask('default', ['compile']);
grunt.registerTask('test', ['jshint', 'exec:test']);
grunt.registerTask('dist', ['clean', 'closure-compiler:compile', 'concat:dist', 'concat:dist_promises']);
};
================================================
FILE: LICENSE
================================================
Copyright (c) 2014 - Bram Stein
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
# Font Face Observer [](https://travis-ci.org/bramstein/fontfaceobserver)
Font Face Observer is a small `@font-face` loader and monitor (3.5KB minified and 1.3KB gzipped) compatible with any webfont service. It will monitor when a webfont is loaded and notify you. It does not limit you in any way in where, when, or how you load your webfonts. Unlike the [Web Font Loader](https://github.com/typekit/webfontloader) Font Face Observer uses scroll events to detect font loads efficiently and with minimum overhead.
## How to use
Include your `@font-face` rules as usual. Fonts can be supplied by either a font service such as [Google Fonts](http://www.google.com/fonts), [Typekit](http://typekit.com), and [Webtype](http://webtype.com) or be self-hosted. You can set up monitoring for a single font family at a time:
```js
var font = new FontFaceObserver('My Family', {
weight: 400
});
font.load().then(function () {
console.log('Font is available');
}, function () {
console.log('Font is not available');
});
```
The `FontFaceObserver` constructor takes two arguments: the font-family name (required) and an object describing the variation (optional). The object can contain `weight`, `style`, and `stretch` properties. If a property is not present it will default to `normal`. To start loading the font, call the `load` method. It'll immediately return a new Promise that resolves when the font is loaded and rejected when the font fails to load.
If your font doesn't contain at least the latin "BESbwy" characters you must pass a custom test string to the `load` method.
```js
var font = new FontFaceObserver('My Family');
font.load('中国').then(function () {
console.log('Font is available');
}, function () {
console.log('Font is not available');
});
```
The default timeout for giving up on font loading is 3 seconds. You can increase or decrease this by passing a number of milliseconds as the second parameter to the `load` method.
```js
var font = new FontFaceObserver('My Family');
font.load(null, 5000).then(function () {
console.log('Font is available');
}, function () {
console.log('Font is not available after waiting 5 seconds');
});
```
Multiple fonts can be loaded by creating a `FontFaceObserver` instance for each.
```js
var fontA = new FontFaceObserver('Family A');
var fontB = new FontFaceObserver('Family B');
fontA.load().then(function () {
console.log('Family A is available');
});
fontB.load().then(function () {
console.log('Family B is available');
});
```
You may also load both at the same time, rather than loading each individually.
```js
var fontA = new FontFaceObserver('Family A');
var fontB = new FontFaceObserver('Family B');
Promise.all([fontA.load(), fontB.load()]).then(function () {
console.log('Family A & B have loaded');
});
```
If you are working with a large number of fonts, you may decide to create `FontFaceObserver` instances dynamically:
```js
// An example collection of font data with additional metadata,
// in this case “color.”
var exampleFontData = {
'Family A': { weight: 400, color: 'red' },
'Family B': { weight: 400, color: 'orange' },
'Family C': { weight: 900, color: 'yellow' },
// Etc.
};
var observers = [];
// Make one observer for each font,
// by iterating over the data we already have
Object.keys(exampleFontData).forEach(function(family) {
var data = exampleFontData[family];
var obs = new FontFaceObserver(family, data);
observers.push(obs.load());
});
Promise.all(observers)
.then(function(fonts) {
fonts.forEach(function(font) {
console.log(font.family + ' ' + font.weight + ' ' + 'loaded');
// Map the result of the Promise back to our existing data,
// to get the other properties we need.
console.log(exampleFontData[font.family].color);
});
})
.catch(function(err) {
console.warn('Some critical font are not available:', err);
});
```
The following example emulates FOUT with Font Face Observer for `My Family`.
```js
var font = new FontFaceObserver('My Family');
font.load().then(function () {
document.documentElement.className += " fonts-loaded";
});
```
```css
.fonts-loaded {
body {
font-family: My Family, sans-serif;
}
}
```
## Installation
If you're using npm you can install Font Face Observer as a dependency:
```shell
$ npm install fontfaceobserver
```
You can then require `fontfaceobserver` as a CommonJS (Browserify) module:
```js
var FontFaceObserver = require('fontfaceobserver');
var font = new FontFaceObserver('My Family');
font.load().then(function () {
console.log('My Family has loaded');
});
```
If you're not using npm, grab `fontfaceobserver.js` or `fontfaceobserver.standalone.js` (see below) and include it in your project. It'll export a global `FontFaceObserver` that you can use to create new instances.
Font Face Observer uses Promises in its API, so for [browsers that do not support promises](http://caniuse.com/#search=promise) you'll need to include a polyfill. If you use your own Promise polyfill you just need to include `fontfaceobserver.standalone.js` in your project. If you do not have an existing Promise polyfill you should use `fontfaceobserver.js` which includes a small Promise polyfill. Using the Promise polyfill adds roughly 1.4KB (500 bytes gzipped) to the file size.
## Browser support
FontFaceObserver has been tested and works on the following browsers:
* Chrome (desktop & Android)
* Firefox
* Opera
* Safari (desktop & iOS)
* IE8+
* Android WebKit
## License
Font Face Observer is licensed under the BSD License. Copyright 2014-2017 Bram Stein. All rights reserved.
================================================
FILE: bower.json
================================================
{
"name": "fontfaceobserver",
"description": "Fast and simple web font loading.",
"main": "fontfaceobserver.standalone.js",
"authors": [
"Bram Stein <b.l.stein@gmail.com> (http://www.bramstein.com/)"
],
"license": "BSD-3-Clause",
"keywords": [
"fontloader",
"fonts",
"font",
"font-face",
"web",
"font",
"font",
"load",
"font",
"events"
],
"homepage": "https://github.com/bramstein/fontfaceobserver"
}
================================================
FILE: exports.js
================================================
goog.require('fontface.Observer');
if (typeof module === 'object') {
module.exports = fontface.Observer;
} else {
window['FontFaceObserver'] = fontface.Observer;
window['FontFaceObserver']['prototype']['load'] = fontface.Observer.prototype.load;
}
================================================
FILE: externs-commonjs.js
================================================
/**
* @type {Object}
*/
var module = {};
/**
* @type {Object}
*/
module.exports = {};
================================================
FILE: externs.js
================================================
/**
* @constructor
*
* @param {string} family
* @param {Object} descriptors
*/
var FontFaceObserver = function (family, descriptors) {};
/**
* @param {string=} opt_text
* @param {number=} opt_timeout
*
* @return {Promise.<FontFaceObserver>}
*/
FontFaceObserver.prototype.load = function (opt_text, opt_timeout) {};
================================================
FILE: fontfaceobserver.js
================================================
/* Font Face Observer v2.3.0 - © Bram Stein. License: BSD-3-Clause */(function(){'use strict';var f,g=[];function l(a){g.push(a);1==g.length&&f()}function m(){for(;g.length;)g[0](),g.shift()}f=function(){setTimeout(m)};function n(a){this.a=p;this.b=void 0;this.f=[];var b=this;try{a(function(a){q(b,a)},function(a){r(b,a)})}catch(c){r(b,c)}}var p=2;function t(a){return new n(function(b,c){c(a)})}function u(a){return new n(function(b){b(a)})}function q(a,b){if(a.a==p){if(b==a)throw new TypeError;var c=!1;try{var d=b&&b.then;if(null!=b&&"object"==typeof b&&"function"==typeof d){d.call(b,function(b){c||q(a,b);c=!0},function(b){c||r(a,b);c=!0});return}}catch(e){c||r(a,e);return}a.a=0;a.b=b;v(a)}}
function r(a,b){if(a.a==p){if(b==a)throw new TypeError;a.a=1;a.b=b;v(a)}}function v(a){l(function(){if(a.a!=p)for(;a.f.length;){var b=a.f.shift(),c=b[0],d=b[1],e=b[2],b=b[3];try{0==a.a?"function"==typeof c?e(c.call(void 0,a.b)):e(a.b):1==a.a&&("function"==typeof d?e(d.call(void 0,a.b)):b(a.b))}catch(h){b(h)}}})}n.prototype.g=function(a){return this.c(void 0,a)};n.prototype.c=function(a,b){var c=this;return new n(function(d,e){c.f.push([a,b,d,e]);v(c)})};
function w(a){return new n(function(b,c){function d(c){return function(d){h[c]=d;e+=1;e==a.length&&b(h)}}var e=0,h=[];0==a.length&&b(h);for(var k=0;k<a.length;k+=1)u(a[k]).c(d(k),c)})}function x(a){return new n(function(b,c){for(var d=0;d<a.length;d+=1)u(a[d]).c(b,c)})};window.Promise||(window.Promise=n,window.Promise.resolve=u,window.Promise.reject=t,window.Promise.race=x,window.Promise.all=w,window.Promise.prototype.then=n.prototype.c,window.Promise.prototype["catch"]=n.prototype.g);}());
(function(){function p(a,c){document.addEventListener?a.addEventListener("scroll",c,!1):a.attachEvent("scroll",c)}function u(a){document.body?a():document.addEventListener?document.addEventListener("DOMContentLoaded",function b(){document.removeEventListener("DOMContentLoaded",b);a()}):document.attachEvent("onreadystatechange",function g(){if("interactive"==document.readyState||"complete"==document.readyState)document.detachEvent("onreadystatechange",g),a()})};function w(a){this.g=document.createElement("div");this.g.setAttribute("aria-hidden","true");this.g.appendChild(document.createTextNode(a));this.h=document.createElement("span");this.i=document.createElement("span");this.m=document.createElement("span");this.j=document.createElement("span");this.l=-1;this.h.style.cssText="max-width:none;display:inline-block;position:absolute;height:100%;width:100%;overflow:scroll;font-size:16px;";this.i.style.cssText="max-width:none;display:inline-block;position:absolute;height:100%;width:100%;overflow:scroll;font-size:16px;";
this.j.style.cssText="max-width:none;display:inline-block;position:absolute;height:100%;width:100%;overflow:scroll;font-size:16px;";this.m.style.cssText="display:inline-block;width:200%;height:200%;font-size:16px;max-width:none;";this.h.appendChild(this.m);this.i.appendChild(this.j);this.g.appendChild(this.h);this.g.appendChild(this.i)}
function x(a,c){a.g.style.cssText="max-width:none;min-width:20px;min-height:20px;display:inline-block;overflow:hidden;position:absolute;width:auto;margin:0;padding:0;top:-999px;white-space:nowrap;font-synthesis:none;font:"+c+";"}function B(a){var c=a.g.offsetWidth,b=c+100;a.j.style.width=b+"px";a.i.scrollLeft=b;a.h.scrollLeft=a.h.scrollWidth+100;return a.l!==c?(a.l=c,!0):!1}function C(a,c){function b(){var e=g;B(e)&&null!==e.g.parentNode&&c(e.l)}var g=a;p(a.h,b);p(a.i,b);B(a)};function D(a,c,b){c=c||{};b=b||window;this.family=a;this.style=c.style||"normal";this.weight=c.weight||"normal";this.stretch=c.stretch||"normal";this.context=b}var E=null,F=null,G=null,H=null;function I(a){null===F&&(M(a)&&/Apple/.test(window.navigator.vendor)?(a=/AppleWebKit\/([0-9]+)(?:\.([0-9]+))(?:\.([0-9]+))/.exec(window.navigator.userAgent),F=!!a&&603>parseInt(a[1],10)):F=!1);return F}function M(a){null===H&&(H=!!a.document.fonts);return H}
function N(a,c){var b=a.style,g=a.weight;if(null===G){var e=document.createElement("div");try{e.style.font="condensed 100px sans-serif"}catch(q){}G=""!==e.style.font}return[b,g,G?a.stretch:"","100px",c].join(" ")}
D.prototype.load=function(a,c){var b=this,g=a||"BESbswy",e=0,q=c||3E3,J=(new Date).getTime();return new Promise(function(K,L){if(M(b.context)&&!I(b.context)){var O=new Promise(function(r,t){function h(){(new Date).getTime()-J>=q?t(Error(""+q+"ms timeout exceeded")):b.context.document.fonts.load(N(b,'"'+b.family+'"'),g).then(function(n){1<=n.length?r():setTimeout(h,25)},t)}h()}),P=new Promise(function(r,t){e=setTimeout(function(){t(Error(""+q+"ms timeout exceeded"))},q)});Promise.race([P,O]).then(function(){clearTimeout(e);
K(b)},L)}else u(function(){function r(){var d;if(d=-1!=k&&-1!=l||-1!=k&&-1!=m||-1!=l&&-1!=m)(d=k!=l&&k!=m&&l!=m)||(null===E&&(d=/AppleWebKit\/([0-9]+)(?:\.([0-9]+))/.exec(window.navigator.userAgent),E=!!d&&(536>parseInt(d[1],10)||536===parseInt(d[1],10)&&11>=parseInt(d[2],10))),d=E&&(k==y&&l==y&&m==y||k==z&&l==z&&m==z||k==A&&l==A&&m==A)),d=!d;d&&(null!==f.parentNode&&f.parentNode.removeChild(f),clearTimeout(e),K(b))}function t(){if((new Date).getTime()-J>=q)null!==f.parentNode&&f.parentNode.removeChild(f),
L(Error(""+q+"ms timeout exceeded"));else{var d=b.context.document.hidden;if(!0===d||void 0===d)k=h.g.offsetWidth,l=n.g.offsetWidth,m=v.g.offsetWidth,r();e=setTimeout(t,50)}}var h=new w(g),n=new w(g),v=new w(g),k=-1,l=-1,m=-1,y=-1,z=-1,A=-1,f=document.createElement("div");f.dir="ltr";x(h,N(b,"sans-serif"));x(n,N(b,"serif"));x(v,N(b,"monospace"));f.appendChild(h.g);f.appendChild(n.g);f.appendChild(v.g);b.context.document.body.appendChild(f);y=h.g.offsetWidth;z=n.g.offsetWidth;A=v.g.offsetWidth;t();
C(h,function(d){k=d;r()});x(h,N(b,'"'+b.family+'",sans-serif'));C(n,function(d){l=d;r()});x(n,N(b,'"'+b.family+'",serif'));C(v,function(d){m=d;r()});x(v,N(b,'"'+b.family+'",monospace'))})})};"object"===typeof module?module.exports=D:(window.FontFaceObserver=D,window.FontFaceObserver.prototype.load=D.prototype.load);}());
================================================
FILE: fontfaceobserver.standalone.js
================================================
/* Font Face Observer v2.3.0 - © Bram Stein. License: BSD-3-Clause */(function(){function p(a,c){document.addEventListener?a.addEventListener("scroll",c,!1):a.attachEvent("scroll",c)}function u(a){document.body?a():document.addEventListener?document.addEventListener("DOMContentLoaded",function b(){document.removeEventListener("DOMContentLoaded",b);a()}):document.attachEvent("onreadystatechange",function g(){if("interactive"==document.readyState||"complete"==document.readyState)document.detachEvent("onreadystatechange",g),a()})};function w(a){this.g=document.createElement("div");this.g.setAttribute("aria-hidden","true");this.g.appendChild(document.createTextNode(a));this.h=document.createElement("span");this.i=document.createElement("span");this.m=document.createElement("span");this.j=document.createElement("span");this.l=-1;this.h.style.cssText="max-width:none;display:inline-block;position:absolute;height:100%;width:100%;overflow:scroll;font-size:16px;";this.i.style.cssText="max-width:none;display:inline-block;position:absolute;height:100%;width:100%;overflow:scroll;font-size:16px;";
this.j.style.cssText="max-width:none;display:inline-block;position:absolute;height:100%;width:100%;overflow:scroll;font-size:16px;";this.m.style.cssText="display:inline-block;width:200%;height:200%;font-size:16px;max-width:none;";this.h.appendChild(this.m);this.i.appendChild(this.j);this.g.appendChild(this.h);this.g.appendChild(this.i)}
function x(a,c){a.g.style.cssText="max-width:none;min-width:20px;min-height:20px;display:inline-block;overflow:hidden;position:absolute;width:auto;margin:0;padding:0;top:-999px;white-space:nowrap;font-synthesis:none;font:"+c+";"}function B(a){var c=a.g.offsetWidth,b=c+100;a.j.style.width=b+"px";a.i.scrollLeft=b;a.h.scrollLeft=a.h.scrollWidth+100;return a.l!==c?(a.l=c,!0):!1}function C(a,c){function b(){var e=g;B(e)&&null!==e.g.parentNode&&c(e.l)}var g=a;p(a.h,b);p(a.i,b);B(a)};function D(a,c,b){c=c||{};b=b||window;this.family=a;this.style=c.style||"normal";this.weight=c.weight||"normal";this.stretch=c.stretch||"normal";this.context=b}var E=null,F=null,G=null,H=null;function I(a){null===F&&(M(a)&&/Apple/.test(window.navigator.vendor)?(a=/AppleWebKit\/([0-9]+)(?:\.([0-9]+))(?:\.([0-9]+))/.exec(window.navigator.userAgent),F=!!a&&603>parseInt(a[1],10)):F=!1);return F}function M(a){null===H&&(H=!!a.document.fonts);return H}
function N(a,c){var b=a.style,g=a.weight;if(null===G){var e=document.createElement("div");try{e.style.font="condensed 100px sans-serif"}catch(q){}G=""!==e.style.font}return[b,g,G?a.stretch:"","100px",c].join(" ")}
D.prototype.load=function(a,c){var b=this,g=a||"BESbswy",e=0,q=c||3E3,J=(new Date).getTime();return new Promise(function(K,L){if(M(b.context)&&!I(b.context)){var O=new Promise(function(r,t){function h(){(new Date).getTime()-J>=q?t(Error(""+q+"ms timeout exceeded")):b.context.document.fonts.load(N(b,'"'+b.family+'"'),g).then(function(n){1<=n.length?r():setTimeout(h,25)},t)}h()}),P=new Promise(function(r,t){e=setTimeout(function(){t(Error(""+q+"ms timeout exceeded"))},q)});Promise.race([P,O]).then(function(){clearTimeout(e);
K(b)},L)}else u(function(){function r(){var d;if(d=-1!=k&&-1!=l||-1!=k&&-1!=m||-1!=l&&-1!=m)(d=k!=l&&k!=m&&l!=m)||(null===E&&(d=/AppleWebKit\/([0-9]+)(?:\.([0-9]+))/.exec(window.navigator.userAgent),E=!!d&&(536>parseInt(d[1],10)||536===parseInt(d[1],10)&&11>=parseInt(d[2],10))),d=E&&(k==y&&l==y&&m==y||k==z&&l==z&&m==z||k==A&&l==A&&m==A)),d=!d;d&&(null!==f.parentNode&&f.parentNode.removeChild(f),clearTimeout(e),K(b))}function t(){if((new Date).getTime()-J>=q)null!==f.parentNode&&f.parentNode.removeChild(f),
L(Error(""+q+"ms timeout exceeded"));else{var d=b.context.document.hidden;if(!0===d||void 0===d)k=h.g.offsetWidth,l=n.g.offsetWidth,m=v.g.offsetWidth,r();e=setTimeout(t,50)}}var h=new w(g),n=new w(g),v=new w(g),k=-1,l=-1,m=-1,y=-1,z=-1,A=-1,f=document.createElement("div");f.dir="ltr";x(h,N(b,"sans-serif"));x(n,N(b,"serif"));x(v,N(b,"monospace"));f.appendChild(h.g);f.appendChild(n.g);f.appendChild(v.g);b.context.document.body.appendChild(f);y=h.g.offsetWidth;z=n.g.offsetWidth;A=v.g.offsetWidth;t();
C(h,function(d){k=d;r()});x(h,N(b,'"'+b.family+'",sans-serif'));C(n,function(d){l=d;r()});x(n,N(b,'"'+b.family+'",serif'));C(v,function(d){m=d;r()});x(v,N(b,'"'+b.family+'",monospace'))})})};"object"===typeof module?module.exports=D:(window.FontFaceObserver=D,window.FontFaceObserver.prototype.load=D.prototype.load);}());
================================================
FILE: package.json
================================================
{
"name": "fontfaceobserver",
"version": "2.3.0",
"description": "Detect if web fonts are available",
"directories": {
"test": "test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/bramstein/fontfaceobserver.git"
},
"bugs": {
"url": "https://github.com/bramstein/fontfaceobserver/issues"
},
"homepage": "https://fontfaceobserver.com/",
"main": "fontfaceobserver.standalone.js",
"keywords": [
"fontloader",
"fonts",
"font",
"font-face",
"web font",
"font load",
"font events"
],
"files": [
"fontfaceobserver.js",
"fontfaceobserver.standalone.js",
"src/*.js",
"externs.js"
],
"author": "Bram Stein <b.l.stein@gmail.com> (http://www.bramstein.com/)",
"license": "BSD-2-Clause",
"devDependencies": {
"closure-dom": "=0.2.6",
"google-closure-compiler": "=v20220502",
"grunt": "^1.0.3",
"grunt-contrib-clean": "^2.0.1",
"grunt-contrib-concat": "^1.0.1",
"grunt-contrib-jshint": "^3.2.0",
"grunt-exec": "~1.0.0",
"mocha": "^10.0.0",
"mocha-headless-chrome": "^4.0.0",
"promis": "=1.1.4",
"sinon": "^14.0.0",
"unexpected": "^13.0.0"
},
"scripts": {
"preversion": "npm test",
"version": "grunt dist && git add fontfaceobserver.js && git add fontfaceobserver.standalone.js",
"postversion": "git push && git push --tags && rm -rf build && npm publish",
"test": "grunt test"
}
}
================================================
FILE: src/descriptors.js
================================================
goog.provide('fontface.Descriptors');
/**
* @typedef {{
* style: (string|undefined),
* weight: (string|undefined),
* stretch: (string|undefined)
* }}
*/
fontface.Descriptors;
================================================
FILE: src/observer.js
================================================
goog.provide('fontface.Observer');
goog.require('fontface.Ruler');
goog.require('dom');
goog.scope(function () {
var Ruler = fontface.Ruler;
/**
* @constructor
*
* @param {string} family
* @param {fontface.Descriptors=} opt_descriptors
* @param {Window=} opt_context
*/
fontface.Observer = function (family, opt_descriptors, opt_context) {
var descriptors = opt_descriptors || {};
var context = opt_context || window;
/**
* @type {string}
*/
this['family'] = family;
/**
* @type {string}
*/
this['style'] = descriptors.style || 'normal';
/**
* @type {string}
*/
this['weight'] = descriptors.weight || 'normal';
/**
* @type {string}
*/
this['stretch'] = descriptors.stretch || 'normal';
/**
* @type {Window}
*/
this['context'] = context;
};
var Observer = fontface.Observer;
/**
* @type {null|boolean}
*/
Observer.HAS_WEBKIT_FALLBACK_BUG = null;
/**
* @type {null|boolean}
*/
Observer.HAS_SAFARI_10_BUG = null;
/**
* @type {null|boolean}
*/
Observer.SUPPORTS_STRETCH = null;
/**
* @type {null|boolean}
*/
Observer.SUPPORTS_NATIVE_FONT_LOADING = null;
/**
* @type {number}
*/
Observer.DEFAULT_TIMEOUT = 3000;
/**
* @return {string}
*/
Observer.getUserAgent = function () {
return window.navigator.userAgent;
};
/**
* @return {string}
*/
Observer.getNavigatorVendor = function () {
return window.navigator.vendor;
};
/**
* Returns true if this browser is WebKit and it has the fallback bug
* which is present in WebKit 536.11 and earlier.
*
* @return {boolean}
*/
Observer.hasWebKitFallbackBug = function () {
if (Observer.HAS_WEBKIT_FALLBACK_BUG === null) {
var match = /AppleWebKit\/([0-9]+)(?:\.([0-9]+))/.exec(Observer.getUserAgent());
Observer.HAS_WEBKIT_FALLBACK_BUG = !!match &&
(parseInt(match[1], 10) < 536 ||
(parseInt(match[1], 10) === 536 &&
parseInt(match[2], 10) <= 11));
}
return Observer.HAS_WEBKIT_FALLBACK_BUG;
};
/**
* Returns true if the browser has the Safari 10 bugs. The
* native font load API in Safari 10 has two bugs that cause
* the document.fonts.load and FontFace.prototype.load methods
* to return promises that don't reliably get settled.
*
* The bugs are described in more detail here:
* - https://bugs.webkit.org/show_bug.cgi?id=165037
* - https://bugs.webkit.org/show_bug.cgi?id=164902
*
* If the browser is made by Apple, and has native font
* loading support, it is potentially affected. But the API
* was fixed around AppleWebKit version 603, so any newer
* versions that that does not contain the bug.
*
* @return {boolean}
*/
Observer.hasSafari10Bug = function (context) {
if (Observer.HAS_SAFARI_10_BUG === null) {
if (Observer.supportsNativeFontLoading(context) && /Apple/.test(Observer.getNavigatorVendor())) {
var match = /AppleWebKit\/([0-9]+)(?:\.([0-9]+))(?:\.([0-9]+))/.exec(Observer.getUserAgent());
Observer.HAS_SAFARI_10_BUG = !!match && parseInt(match[1], 10) < 603;
} else {
Observer.HAS_SAFARI_10_BUG = false;
}
}
return Observer.HAS_SAFARI_10_BUG;
};
/**
* Returns true if the browser supports the native font loading
* API.
*
* @return {boolean}
*/
Observer.supportsNativeFontLoading = function (context) {
if (Observer.SUPPORTS_NATIVE_FONT_LOADING === null) {
Observer.SUPPORTS_NATIVE_FONT_LOADING = !!context.document['fonts'];
}
return Observer.SUPPORTS_NATIVE_FONT_LOADING;
};
/**
* Returns true if the browser supports font-style in the font
* short-hand syntax.
*
* @return {boolean}
*/
Observer.supportStretch = function () {
if (Observer.SUPPORTS_STRETCH === null) {
var div = dom.createElement('div');
try {
div.style.font = 'condensed 100px sans-serif';
} catch (e) {}
Observer.SUPPORTS_STRETCH = (div.style.font !== '');
}
return Observer.SUPPORTS_STRETCH;
};
/**
* @private
*
* @param {string} family
* @return {string}
*/
Observer.prototype.getStyle = function (family) {
return [this['style'], this['weight'], Observer.supportStretch() ? this['stretch'] : '', '100px', family].join(' ');
};
/**
* Returns the current time in milliseconds
*
* @return {number}
*/
Observer.prototype.getTime = function () {
return new Date().getTime();
};
/**
* @param {string=} text Optional test string to use for detecting if a font is available.
* @param {number=} timeout Optional timeout for giving up on font load detection and rejecting the promise (defaults to 3 seconds).
* @return {Promise.<fontface.Observer>}
*/
Observer.prototype.load = function (text, timeout) {
var that = this;
var testString = text || 'BESbswy';
var timeoutId = 0;
var timeoutValue = timeout || Observer.DEFAULT_TIMEOUT;
var start = that.getTime();
return new Promise(function (resolve, reject) {
if (Observer.supportsNativeFontLoading(that['context']) && !Observer.hasSafari10Bug(that['context'])) {
var loader = new Promise(function (resolve, reject) {
var check = function () {
var now = that.getTime();
if (now - start >= timeoutValue) {
reject(new Error('' + timeoutValue + 'ms timeout exceeded'));
} else {
that['context'].document.fonts.load(that.getStyle('"' + that['family'] + '"'), testString).then(function (fonts) {
if (fonts.length >= 1) {
resolve();
} else {
setTimeout(check, 25);
}
}, reject);
}
};
check();
});
var timer = new Promise(function (resolve, reject) {
timeoutId = setTimeout(
function() { reject(new Error('' + timeoutValue + 'ms timeout exceeded')); },
timeoutValue
);
});
Promise.race([timer, loader]).then(function () {
clearTimeout(timeoutId);
resolve(that);
}, reject);
} else {
dom.waitForBody(function () {
var rulerA = new Ruler(testString);
var rulerB = new Ruler(testString);
var rulerC = new Ruler(testString);
var widthA = -1;
var widthB = -1;
var widthC = -1;
var fallbackWidthA = -1;
var fallbackWidthB = -1;
var fallbackWidthC = -1;
var container = dom.createElement('div');
/**
* @private
*/
function removeContainer() {
if (container.parentNode !== null) {
dom.remove(container.parentNode, container);
}
}
/**
* @private
*
* If metric compatible fonts are detected, one of the widths will be -1. This is
* because a metric compatible font won't trigger a scroll event. We work around
* this by considering a font loaded if at least two of the widths are the same.
* Because we have three widths, this still prevents false positives.
*
* Cases:
* 1) Font loads: both a, b and c are called and have the same value.
* 2) Font fails to load: resize callback is never called and timeout happens.
* 3) WebKit bug: both a, b and c are called and have the same value, but the
* values are equal to one of the last resort fonts, we ignore this and
* continue waiting until we get new values (or a timeout).
*/
function check() {
if ((widthA != -1 && widthB != -1) || (widthA != -1 && widthC != -1) || (widthB != -1 && widthC != -1)) {
if (widthA == widthB || widthA == widthC || widthB == widthC) {
// All values are the same, so the browser has most likely loaded the web font
if (Observer.hasWebKitFallbackBug()) {
// Except if the browser has the WebKit fallback bug, in which case we check to see if all
// values are set to one of the last resort fonts.
if (((widthA == fallbackWidthA && widthB == fallbackWidthA && widthC == fallbackWidthA) ||
(widthA == fallbackWidthB && widthB == fallbackWidthB && widthC == fallbackWidthB) ||
(widthA == fallbackWidthC && widthB == fallbackWidthC && widthC == fallbackWidthC))) {
// The width we got matches some of the known last resort fonts, so let's assume we're dealing with the last resort font.
return;
}
}
removeContainer();
clearTimeout(timeoutId);
resolve(that);
}
}
}
// This ensures the scroll direction is correct.
container.dir = 'ltr';
rulerA.setFont(that.getStyle('sans-serif'));
rulerB.setFont(that.getStyle('serif'));
rulerC.setFont(that.getStyle('monospace'));
dom.append(container, rulerA.getElement());
dom.append(container, rulerB.getElement());
dom.append(container, rulerC.getElement());
dom.append(that['context'].document.body, container);
fallbackWidthA = rulerA.getWidth();
fallbackWidthB = rulerB.getWidth();
fallbackWidthC = rulerC.getWidth();
function checkForTimeout() {
var now = that.getTime();
if (now - start >= timeoutValue) {
removeContainer();
reject(new Error('' + timeoutValue + 'ms timeout exceeded'));
} else {
var hidden = that['context'].document['hidden'];
if (hidden === true || hidden === undefined) {
widthA = rulerA.getWidth();
widthB = rulerB.getWidth();
widthC = rulerC.getWidth();
check();
}
timeoutId = setTimeout(checkForTimeout, 50);
}
}
checkForTimeout();
rulerA.onResize(function (width) {
widthA = width;
check();
});
rulerA.setFont(that.getStyle('"' + that['family'] + '",sans-serif'));
rulerB.onResize(function (width) {
widthB = width;
check();
});
rulerB.setFont(that.getStyle('"' + that['family'] + '",serif'));
rulerC.onResize(function (width) {
widthC = width;
check();
});
rulerC.setFont(that.getStyle('"' + that['family'] + '",monospace'));
});
}
});
};
});
================================================
FILE: src/ruler.js
================================================
goog.provide('fontface.Ruler');
goog.require('dom');
goog.scope(function () {
/**
* @constructor
* @param {string} text
*/
fontface.Ruler = function (text) {
var style = 'max-width:none;' +
'display:inline-block;' +
'position:absolute;' +
'height:100%;' +
'width:100%;' +
'overflow:scroll;' +
'font-size:16px;';
this.element = dom.createElement('div');
this.element.setAttribute('aria-hidden', 'true');
dom.append(this.element, dom.createText(text));
this.collapsible = dom.createElement('span');
this.expandable = dom.createElement('span');
this.collapsibleInner = dom.createElement('span');
this.expandableInner = dom.createElement('span');
this.lastOffsetWidth = -1;
dom.style(this.collapsible, style);
dom.style(this.expandable, style);
dom.style(this.expandableInner, style);
dom.style(this.collapsibleInner, 'display:inline-block;width:200%;height:200%;font-size:16px;max-width:none;');
dom.append(this.collapsible, this.collapsibleInner);
dom.append(this.expandable, this.expandableInner);
dom.append(this.element, this.collapsible);
dom.append(this.element, this.expandable);
};
var Ruler = fontface.Ruler;
/**
* @return {Element}
*/
Ruler.prototype.getElement = function () {
return this.element;
};
/**
* @param {string} font
*/
Ruler.prototype.setFont = function (font) {
dom.style(this.element, 'max-width:none;' +
'min-width:20px;' +
'min-height:20px;' +
'display:inline-block;' +
'overflow:hidden;' +
'position:absolute;' +
'width:auto;' +
'margin:0;' +
'padding:0;' +
'top:-999px;' +
'white-space:nowrap;' +
'font-synthesis:none;' +
'font:' + font + ';');
};
/**
* @return {number}
*/
Ruler.prototype.getWidth = function () {
return this.element.offsetWidth;
};
/**
* @param {string} width
*/
Ruler.prototype.setWidth = function (width) {
this.element.style.width = width + 'px';
};
/**
* @private
*
* @return {boolean}
*/
Ruler.prototype.reset = function () {
var offsetWidth = this.getWidth(),
width = offsetWidth + 100;
this.expandableInner.style.width = width + 'px';
this.expandable.scrollLeft = width;
this.collapsible.scrollLeft = this.collapsible.scrollWidth + 100;
if (this.lastOffsetWidth !== offsetWidth) {
this.lastOffsetWidth = offsetWidth;
return true;
} else {
return false;
}
};
/**
* @private
* @param {function(number)} callback
*/
Ruler.prototype.onScroll = function (callback) {
if (this.reset() && this.element.parentNode !== null) {
callback(this.lastOffsetWidth);
}
};
/**
* @param {function(number)} callback
*/
Ruler.prototype.onResize = function (callback) {
var that = this;
function onScroll() {
that.onScroll(callback);
}
dom.addListener(this.collapsible, 'scroll', onScroll);
dom.addListener(this.expandable, 'scroll', onScroll);
this.reset();
};
});
================================================
FILE: test/assets/index.html
================================================
<!doctype html>
<html>
<head>
</head>
<body>
hello
</body>
</html>
================================================
FILE: test/assets/late.css
================================================
@font-face { font-family: observer-test9; src: url(sourcesanspro-regular.woff) format('woff'), url(sourcesanspro-regular.ttf) format('truetype'); }
================================================
FILE: test/browser-test.html
================================================
<!doctype html>
<html>
<head>
<title>Browser Test</title>
<meta charset="utf-8">
<style>
@font-face {
font-family: test;
src: url(assets/sourcesanspro-regular.woff);
}
</style>
</head>
<body>
<script src="../fontfaceobserver.js"></script>
<script>
const font = new FontFaceObserver('test');
font.load().then(f => {
console.log('Font has loaded');
}).catch(e => {
console.error(e);
});
</script>
</body>
</html>
================================================
FILE: test/deps.js
================================================
// This file was autogenerated by calcdeps.js
goog.addDependency('../../node_modules/closure-dom/src/dom.js', ['dom'], []);
goog.addDependency('../../src/descriptors.js', ['fontface.Descriptors'], []);
goog.addDependency('../../src/observer.js', ['fontface.Observer'], ['fontface.Ruler','dom']);
goog.addDependency('../../src/ruler.js', ['fontface.Ruler'], ['dom']);
================================================
FILE: test/index.html
================================================
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>FontFaceObserver</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="../node_modules/mocha/mocha.css" rel="stylesheet">
<style>
@font-face {
font-family: observer-test1;
src: url(assets/sourcesanspro-regular.woff) format('woff'),
url(assets/sourcesanspro-regular.ttf) format('truetype');
}
@font-face {
font-family: observer-test2;
src: url(unknown.woff) format('woff'),
url(unknown.ttf) format('truetype');
}
@font-face {
font-family: observer-test3;
src: url(assets/sourcesanspro-regular.woff) format('woff'),
url(assets/sourcesanspro-regular.ttf) format('truetype');
}
@font-face {
font-family: observer-test4;
src: url(assets/subset.woff) format('woff'),
url(assets/subset.ttf) format('truetype');
unicode-range: u+0021;
}
@font-face {
font-family: observer-test5;
src: url(assets/subset.woff) format('woff'),
url(assets/subset.ttf) format('truetype');
unicode-range: u+4e2d,u+56fd;
}
@font-face {
font-family: observer-test6;
src: url(assets/subset.woff) format('woff'),
url(assets/subset.ttf) format('truetype');
unicode-range: u+10ffff;
}
@font-face {
font-family: observer-test7;
src: url(assets/subset.woff) format('woff'),
url(assets/subset.ttf) format('truetype');
unicode-range: u+23;
}
@font-face {
font-family: observer-test8;
src: url(assets/sourcesanspro-regular.woff) format('woff'),
url(assets/sourcesanspro-regular.ttf) format('truetype');
}
@font-face {
font-family: Trebuchet W01 Regular;
src: url(assets/sourcesanspro-regular.woff) format('woff'),
url(assets/sourcesanspro-regular.ttf) format('truetype');
}
@font-face {
font-family: 'Neue Frutiger 1450 W04';
src: url(assets/sourcesanspro-regular.woff) format('woff'),
url(assets/sourcesanspro-regular.ttf) format('truetype');
}
</style>
</head>
<body>
<div id="mocha"></div>
<script>CLOSURE_NO_DEPS = true;</script>
<script src="../vendor/google/base.js"></script>
<script src="../node_modules/unexpected/unexpected.js"></script>
<script src="../node_modules/mocha/mocha.js"></script>
<script src="../node_modules/sinon/pkg/sinon.js"></script>
<script src="../node_modules/promis/promise.js"></script>
<script src="deps.js"></script>
<script>
goog.require('fontface.Observer');
mocha.ui('bdd');
expect = weknowhow.expect;
</script>
<script src="./ruler-test.js"></script>
<script src="./observer-test.js"></script>
<script>
window.onload = function () {
mocha.run();
};
</script>
</body>
</html>
================================================
FILE: test/observer-test.js
================================================
describe('Observer', function () {
var Observer = fontface.Observer,
Ruler = fontface.Ruler;
describe('#constructor', function () {
it('creates a new instance with the correct signature', function () {
var observer = new Observer('my family', {});
expect(observer, 'not to be', null);
expect(observer.load, 'to be a function');
});
it('parses descriptors', function () {
var observer = new Observer('my family', {
weight: 'bold'
});
expect(observer.family, 'to equal', 'my family');
expect(observer.weight, 'to equal', 'bold');
});
it('defaults descriptors that are not given', function () {
var observer = new Observer('my family', {
weight: 'bold'
});
expect(observer.style, 'to equal', 'normal');
});
it('defaults context to current window', function () {
var observer = new Observer('my family', {});
expect(observer.context, 'to equal', window);
});
});
describe('#getStyle', function () {
it('creates the correct default style', function () {
var observer = new Observer('my family', {});
if (Observer.supportStretch()) {
expect(observer.getStyle('sans-serif'), 'to equal', 'normal normal normal 100px sans-serif');
} else {
expect(observer.getStyle('sans-serif'), 'to equal', 'normal normal 100px sans-serif');
}
});
it('passes through all descriptors', function () {
var observer = new Observer('my family', {
style: 'italic',
weight: 'bold',
stretch: 'condensed'
});
if (Observer.supportStretch()) {
expect(observer.getStyle('sans-serif'), 'to equal', 'italic bold condensed 100px sans-serif');
} else {
expect(observer.getStyle('sans-serif'), 'to equal', 'italic bold 100px sans-serif');
}
});
});
describe('#load', function () {
this.timeout(5000);
it('finds a font and resolve the promise', function (done) {
var observer = new Observer('observer-test1', {}),
ruler = new Ruler('hello');
document.body.appendChild(ruler.getElement());
ruler.setFont('monospace', '');
var beforeWidth = ruler.getWidth();
ruler.setFont('100px observer-test1, monospace');
observer.load(null, 5000).then(function () {
var activeWidth = ruler.getWidth();
expect(activeWidth, 'not to equal', beforeWidth);
setTimeout(function () {
var afterWidth = ruler.getWidth();
expect(afterWidth, 'to equal', activeWidth);
expect(afterWidth, 'not to equal', beforeWidth);
document.body.removeChild(ruler.getElement());
done();
}, 0);
}, function () {
done(new Error('Timeout'));
});
});
it('finds a font and resolve the promise in an iframe (context)', function (done) {
var iframe = document.createElement('iframe');
iframe.style.position = 'fixed';
iframe.style.left = '-9999px';
document.body.appendChild(iframe);
// Can't just load a local HTML file, because under file:// we wouldn't
// be able to interact with it due to same-origin policy
var style = iframe.contentWindow.document.createElement('style');
style.textContent =
"@font-face {" +
" font-family: observer-test1;" +
" src: url(assets/sourcesanspro-regular.woff) format('woff')," +
" url(assets/sourcesanspro-regular.ttf) format('truetype');" +
"}";
iframe.contentWindow.document.head.appendChild(style);
var observer = new Observer('observer-test1', {}, iframe.contentWindow),
ruler = new Ruler('hello');
iframe.contentWindow.document.body.appendChild(ruler.getElement());
ruler.setFont('monospace', '');
var beforeWidth = ruler.getWidth();
ruler.setFont('100px observer-test1, monospace');
observer.load(null, 5000).then(function () {
var activeWidth = ruler.getWidth();
expect(activeWidth, 'not to equal', beforeWidth);
setTimeout(function () {
var afterWidth = ruler.getWidth();
expect(afterWidth, 'to equal', activeWidth);
expect(afterWidth, 'not to equal', beforeWidth);
document.body.removeChild(iframe);
done();
}, 0);
}, function () {
document.body.removeChild(iframe);
done(new Error('Timeout'));
});
});
it('finds a font and resolves the promise even though the @font-face rule is not in the CSSOM yet', function (done) {
var observer = new Observer('observer-test9', {}),
ruler = new Ruler('hello');
document.body.appendChild(ruler.getElement());
ruler.setFont('monospace', '');
var beforeWidth = ruler.getWidth();
ruler.setFont('100px observer-test9, monospace');
observer.load(null, 10000).then(function () {
var activeWidth = ruler.getWidth();
expect(activeWidth, 'not to equal', beforeWidth);
setTimeout(function () {
var afterWidth = ruler.getWidth();
expect(afterWidth, 'to equal', activeWidth);
expect(afterWidth, 'not to equal', beforeWidth);
document.body.removeChild(ruler.getElement());
done();
}, 0);
}, function () {
done(new Error('Timeout'));
});
// We don't use a style element here because IE9/10 have issues with
// dynamically inserted @font-face rules.
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'assets/late.css';
document.head.appendChild(link);
});
it('finds a font and resolve the promise even when the page is RTL', function (done) {
var observer = new Observer('observer-test8', {}),
ruler = new Ruler('hello');
document.body.dir = 'rtl';
document.body.appendChild(ruler.getElement());
ruler.setFont('monospace', '');
var beforeWidth = ruler.getWidth();
ruler.setFont('100px observer-test1, monospace');
observer.load(null, 5000).then(function () {
var activeWidth = ruler.getWidth();
expect(activeWidth, 'not to equal', beforeWidth);
setTimeout(function () {
var afterWidth = ruler.getWidth();
expect(afterWidth, 'to equal', activeWidth);
expect(afterWidth, 'not to equal', beforeWidth);
document.body.removeChild(ruler.getElement());
document.body.dir = 'ltr';
done();
}, 0);
}, function () {
done(new Error('Timeout'));
});
});
it('finds a font with spaces in the name and resolve the promise', function (done) {
var observer = new Observer('Trebuchet W01 Regular', {}),
ruler = new Ruler('hello');
document.body.appendChild(ruler.getElement());
ruler.setFont('100px monospace');
var beforeWidth = ruler.getWidth();
ruler.setFont('100px "Trebuchet W01 Regular", monospace');
observer.load(null, 5000).then(function () {
var activeWidth = ruler.getWidth();
expect(activeWidth, 'not to equal', beforeWidth);
setTimeout(function () {
var afterWidth = ruler.getWidth();
expect(afterWidth, 'to equal', activeWidth);
expect(afterWidth, 'not to equal', beforeWidth);
document.body.removeChild(ruler.getElement());
done();
}, 0);
}, function () {
done(new Error('Timeout'));
});
});
it('loads a font with spaces and numbers in the name and resolve the promise', function (done) {
var observer = new Observer('Neue Frutiger 1450 W04', {}),
ruler = new Ruler('hello');
document.body.appendChild(ruler.getElement());
ruler.setFont('100px monospace');
var beforeWidth = ruler.getWidth();
ruler.setFont('100px "Neue Frutiger 1450 W04", monospace');
observer.load(null, 5000).then(function () {
var activeWidth = ruler.getWidth();
expect(activeWidth, 'not to equal', beforeWidth);
setTimeout(function () {
var afterWidth = ruler.getWidth();
expect(afterWidth, 'to equal', activeWidth);
expect(afterWidth, 'not to equal', beforeWidth);
document.body.removeChild(ruler.getElement());
done();
}, 0);
}, function () {
done(new Error('Timeout'));
});
});
it('fails to find a font and reject the promise', function (done) {
var observer = new Observer('observer-test2', {});
observer.load(null, 50).then(function () {
done(new Error('Should not resolve'));
}, function (err) {
try {
done();
} catch(testFailure) {
done(testFailure);
}
});
});
it('finds the font even if it is already loaded', function (done) {
var observer = new Observer('observer-test3', {});
observer.load(null, 5000).then(function () {
observer.load(null, 5000).then(function () {
done();
}, function () {
done(new Error('Second call failed'));
});
}, function () {
done(new Error('Timeout'));
});
});
it('finds a font with a custom unicode range within ASCII', function (done) {
var observer = new Observer('observer-test4', {}),
ruler = new Ruler('\u0021');
ruler.setFont('monospace', '');
document.body.appendChild(ruler.getElement());
var beforeWidth = ruler.getWidth();
ruler.setFont('100px observer-test4,monospace');
observer.load('\u0021', 5000).then(function () {
var activeWidth = ruler.getWidth();
expect(activeWidth, 'not to equal', beforeWidth);
setTimeout(function () {
var afterWidth = ruler.getWidth();
expect(afterWidth, 'to equal', activeWidth);
expect(afterWidth, 'not to equal', beforeWidth);
document.body.removeChild(ruler.getElement());
done();
}, 0);
}, function () {
done(new Error('Timeout'));
});
});
it('finds a font with a custom unicode range outside ASCII (but within BMP)', function (done) {
var observer = new Observer('observer-test5', {}),
ruler = new Ruler('\u4e2d\u56fd');
ruler.setFont('100px monospace');
document.body.appendChild(ruler.getElement());
var beforeWidth = ruler.getWidth();
ruler.setFont('100px observer-test5,monospace');
observer.load('\u4e2d\u56fd', 5000).then(function () {
var activeWidth = ruler.getWidth();
expect(activeWidth, 'not to equal', beforeWidth);
setTimeout(function () {
var afterWidth = ruler.getWidth();
expect(afterWidth, 'to equal', activeWidth);
expect(afterWidth, 'not to equal', beforeWidth);
document.body.removeChild(ruler.getElement());
done();
}, 0);
}, function () {
done(new Error('Timeout'));
});
});
it('finds a font with a custom unicode range outside the BMP', function (done) {
var observer = new Observer('observer-test6', {}),
ruler = new Ruler('\udbff\udfff');
ruler.setFont('100px monospace');
document.body.appendChild(ruler.getElement());
var beforeWidth = ruler.getWidth();
ruler.setFont('100px observer-test6,monospace');
observer.load('\udbff\udfff', 5000).then(function () {
var activeWidth = ruler.getWidth();
expect(activeWidth, 'not to equal', beforeWidth);
setTimeout(function () {
var afterWidth = ruler.getWidth();
expect(afterWidth, 'to equal', activeWidth);
expect(afterWidth, 'not to equal', beforeWidth);
document.body.removeChild(ruler.getElement());
done();
}, 0);
}, function () {
done(new Error('Timeout'));
});
});
it('fails to find the font if it is available but does not contain the test string', function (done) {
var observer = new Observer('observer-test7', {});
observer.load(null, 50).then(function () {
done(new Error('Should not be called'));
}, function () {
done();
});
});
xit('finds a locally installed font', function (done) {
var observer = new Observer('sans-serif', {});
observer.load(null, 50).then(function () {
done();
}, function () {
done(new Error('Did not detect local font'));
});
});
xit('finds a locally installed font with the same metrics as the a fallback font (on OS X)', function (done) {
var observer = new Observer('serif', {});
observer.load(null, 50).then(function () {
done();
}, function () {
done(new Error('Did not detect local font'));
});
});
});
describe('hasSafari10Bug', function () {
var getUserAgent = null;
var getNavigatorVendor = null;
var supportsNativeFontLoading = null;
beforeEach(function () {
Observer.HAS_SAFARI_10_BUG = null;
getUserAgent = sinon.stub(Observer, 'getUserAgent');
getNavigatorVendor = sinon.stub(Observer, 'getNavigatorVendor');
supportsNativeFontLoading = sinon.stub(Observer, 'supportsNativeFontLoading');
});
afterEach(function () {
getUserAgent.restore();
getNavigatorVendor.restore();
supportsNativeFontLoading.restore();
});
it('returns false when the user agent is not WebKit', function () {
getUserAgent.returns('Mozilla/5.0 (Android; Mobile; rv:13.0) Gecko/15.0 Firefox/14.0');
getNavigatorVendor.returns('Google');
supportsNativeFontLoading.returns(true);
expect(Observer.hasSafari10Bug(), 'to be false');
});
it('returns true if the browser is an affected version of Safari 10', function () {
getUserAgent.returns('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/602.2.14 (KHTML, like Gecko) Version/10.0.1 Safari/602.2.14');
getNavigatorVendor.returns('Apple');
supportsNativeFontLoading.returns(true);
expect(Observer.hasSafari10Bug(), 'to be true');
});
it('returns true if the browser is an WebView with an affected version of Safari 10', function () {
getUserAgent.returns('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/602.2.14 (KHTML, like Gecko) FxiOS/6.1 Safari/602.2.14');
getNavigatorVendor.returns('Apple');
supportsNativeFontLoading.returns(true);
expect(Observer.hasSafari10Bug(), 'to be true');
});
it('returns false in older versions of Safari', function () {
getUserAgent.returns('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/9.3.2 Safari/537.75.14');
getNavigatorVendor.returns('Apple');
supportsNativeFontLoading.returns(false);
expect(Observer.hasSafari10Bug(), 'to be false');
});
it('returns false in newer versions of Safari', function () {
getUserAgent.returns('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.20 (KHTML, like Gecko) Version/10.1 Safari/603.1.20');
getNavigatorVendor.returns('Apple');
supportsNativeFontLoading.returns(true);
expect(Observer.hasSafari10Bug(), 'to be false');
});
});
describe('hasWebKitFallbackBug', function () {
var getUserAgent = null;
beforeEach(function () {
Observer.HAS_WEBKIT_FALLBACK_BUG = null;
getUserAgent = sinon.stub(Observer, 'getUserAgent');
});
afterEach(function () {
getUserAgent.restore();
});
it('returns false when the user agent is not WebKit', function () {
getUserAgent.returns('Mozilla/5.0 (Android; Mobile; rv:13.0) Gecko/15.0 Firefox/14.0');
expect(Observer.hasWebKitFallbackBug(), 'to be false');
});
it('returns false when the user agent is WebKit but the bug is not present', function () {
getUserAgent.returns('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.12 (KHTML, like Gecko) Chrome/20.0.814.2 Safari/536.12');
expect(Observer.hasWebKitFallbackBug(), 'to be false');
});
it('returns true when the user agent is WebKit and the bug is present', function () {
getUserAgent.returns('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.814.2 Safari/536.11');
expect(Observer.hasWebKitFallbackBug(), 'to be true');
});
it('returns true when the user agent is WebKit and the bug is present in an old version', function () {
getUserAgent.returns('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/20.0.814.2 Safari/535.19');
expect(Observer.hasWebKitFallbackBug(), 'to be true');
});
it('caches the results', function () {
getUserAgent.returns('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.814.2 Safari/536.11');
expect(Observer.hasWebKitFallbackBug(), 'to be true');
getUserAgent.returns('Mozilla/5.0 (Android; Mobile; rv:13.0) Gecko/15.0 Firefox/14.0');
expect(Observer.hasWebKitFallbackBug(), 'to be true');
});
});
});
================================================
FILE: test/ruler-test.js
================================================
describe('Ruler', function () {
var Ruler = fontface.Ruler,
ruler = null;
beforeEach(function () {
ruler = new Ruler('hello');
ruler.setFont('', '');
ruler.setWidth(100);
document.body.appendChild(ruler.getElement());
});
afterEach(function () {
document.body.removeChild(ruler.getElement());
ruler = null;
});
describe('#constructor', function () {
it('creates a new instance with the correct signature', function () {
expect(ruler, 'not to be', null);
expect(ruler.onResize, 'to be a function');
expect(ruler.setFont, 'to be a function');
});
});
describe('#onResize', function () {
it('detects expansion', function (done) {
ruler.onResize(function (width) {
expect(width, 'to equal', 200);
done();
});
ruler.setWidth(200);
});
it('detects multiple expansions', function (done) {
var first = true;
ruler.onResize(function (width) {
if (first) {
expect(width, 'to equal', 200);
ruler.setWidth(300);
first = false;
} else {
expect(width, 'to equal', 300);
done();
}
});
ruler.setWidth(200);
});
it('detects collapse', function (done) {
ruler.onResize(function (width) {
expect(width, 'to equal', 50);
done();
});
ruler.setWidth(50);
});
it('detects multiple collapses', function (done) {
var first = true;
ruler.onResize(function (width) {
if (first) {
expect(width, 'to equal', 70);
ruler.setWidth(50);
first = false;
} else {
expect(width, 'to equal', 50);
done();
}
});
ruler.setWidth(70);
});
it('detects a collapse and an expansion', function (done) {
var first = true;
ruler.onResize(function (width) {
if (first) {
expect(width, 'to equal', 70);
ruler.setWidth(100);
first = false;
} else {
expect(width, 'to equal', 100);
done();
}
});
ruler.setWidth(70);
});
it('detects an expansion and a collapse', function (done) {
var first = true;
ruler.onResize(function (width) {
if (first) {
expect(width, 'to equal', 200);
ruler.setWidth(100);
first = false;
} else {
expect(width, 'to equal', 100);
done();
}
});
ruler.setWidth(200);
});
});
});
================================================
FILE: vendor/google/base.js
================================================
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Bootstrap for the Google JS Library (Closure).
*
* In uncompiled mode base.js will write out Closure's deps file, unless the
* global <code>CLOSURE_NO_DEPS</code> is set to true. This allows projects to
* include their own deps file(s) from different locations.
*
*
* @provideGoog
*/
/**
* @define {boolean} Overridden to true by the compiler when --closure_pass
* or --mark_as_compiled is specified.
*/
var COMPILED = false;
/**
* Base namespace for the Closure library. Checks to see goog is
* already defined in the current scope before assigning to prevent
* clobbering if base.js is loaded more than once.
*
* @const
*/
var goog = goog || {}; // Identifies this file as the Closure base.
/**
* Reference to the global context. In most cases this will be 'window'.
*/
goog.global = window;
/**
* @define {boolean} DEBUG is provided as a convenience so that debugging code
* that should not be included in a production js_binary can be easily stripped
* by specifying --define goog.DEBUG=false to the JSCompiler. For example, most
* toString() methods should be declared inside an "if (goog.DEBUG)" conditional
* because they are generally used for debugging purposes and it is difficult
* for the JSCompiler to statically determine whether they are used.
*/
goog.DEBUG = true;
/**
* @define {string} LOCALE defines the locale being used for compilation. It is
* used to select locale specific data to be compiled in js binary. BUILD rule
* can specify this value by "--define goog.LOCALE=<locale_name>" as JSCompiler
* option.
*
* Take into account that the locale code format is important. You should use
* the canonical Unicode format with hyphen as a delimiter. Language must be
* lowercase, Language Script - Capitalized, Region - UPPERCASE.
* There are few examples: pt-BR, en, en-US, sr-Latin-BO, zh-Hans-CN.
*
* See more info about locale codes here:
* http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers
*
* For language codes you should use values defined by ISO 693-1. See it here
* http://www.w3.org/WAI/ER/IG/ert/iso639.htm. There is only one exception from
* this rule: the Hebrew language. For legacy reasons the old code (iw) should
* be used instead of the new code (he), see http://wiki/Main/IIISynonyms.
*/
goog.LOCALE = 'en'; // default to en
/**
* @define {boolean} Whether this code is running on trusted sites.
*
* On untrusted sites, several native functions can be defined or overridden by
* external libraries like Prototype, Datejs, and JQuery and setting this flag
* to false forces closure to use its own implementations when possible.
*
* If your javascript can be loaded by a third party site and you are wary about
* relying on non-standard implementations, specify
* "--define goog.TRUSTED_SITE=false" to the JSCompiler.
*/
goog.TRUSTED_SITE = true;
/**
* Creates object stubs for a namespace. The presence of one or more
* goog.provide() calls indicate that the file defines the given
* objects/namespaces. Build tools also scan for provide/require statements
* to discern dependencies, build dependency files (see deps.js), etc.
* @see goog.require
* @param {string} name Namespace provided by this file in the form
* "goog.package.part".
*/
goog.provide = function(name) {
if (!COMPILED) {
// Ensure that the same namespace isn't provided twice. This is intended
// to teach new developers that 'goog.provide' is effectively a variable
// declaration. And when JSCompiler transforms goog.provide into a real
// variable declaration, the compiled JS should work the same as the raw
// JS--even when the raw JS uses goog.provide incorrectly.
if (goog.isProvided_(name)) {
throw Error('Namespace "' + name + '" already declared.');
}
delete goog.implicitNamespaces_[name];
var namespace = name;
while ((namespace = namespace.substring(0, namespace.lastIndexOf('.')))) {
if (goog.getObjectByName(namespace)) {
break;
}
goog.implicitNamespaces_[namespace] = true;
}
}
goog.exportPath_(name);
};
/**
* Marks that the current file should only be used for testing, and never for
* live code in production.
* @param {string=} opt_message Optional message to add to the error that's
* raised when used in production code.
*/
goog.setTestOnly = function(opt_message) {
if (COMPILED && !goog.DEBUG) {
opt_message = opt_message || '';
throw Error('Importing test-only code into non-debug environment' +
opt_message ? ': ' + opt_message : '.');
}
};
if (!COMPILED) {
/**
* Check if the given name has been goog.provided. This will return false for
* names that are available only as implicit namespaces.
* @param {string} name name of the object to look for.
* @return {boolean} Whether the name has been provided.
* @private
*/
goog.isProvided_ = function(name) {
return !goog.implicitNamespaces_[name] && !!goog.getObjectByName(name);
};
/**
* Namespaces implicitly defined by goog.provide. For example,
* goog.provide('goog.events.Event') implicitly declares
* that 'goog' and 'goog.events' must be namespaces.
*
* @type {Object}
* @private
*/
goog.implicitNamespaces_ = {};
}
/**
* Builds an object structure for the provided namespace path,
* ensuring that names that already exist are not overwritten. For
* example:
* "a.b.c" -> a = {};a.b={};a.b.c={};
* Used by goog.provide and goog.exportSymbol.
* @param {string} name name of the object that this file defines.
* @param {*=} opt_object the object to expose at the end of the path.
* @param {Object=} opt_objectToExportTo The object to add the path to; default
* is |goog.global|.
* @private
*/
goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
var parts = name.split('.');
var cur = opt_objectToExportTo || goog.global;
// Internet Explorer exhibits strange behavior when throwing errors from
// methods externed in this manner. See the testExportSymbolExceptions in
// base_test.html for an example.
if (!(parts[0] in cur) && cur.execScript) {
cur.execScript('var ' + parts[0]);
}
// Certain browsers cannot parse code in the form for((a in b); c;);
// This pattern is produced by the JSCompiler when it collapses the
// statement above into the conditional loop below. To prevent this from
// happening, use a for-loop and reserve the init logic as below.
// Parentheses added to eliminate strict JS warning in Firefox.
for (var part; parts.length && (part = parts.shift());) {
if (!parts.length && goog.isDef(opt_object)) {
// last part and we have an object; use it
cur[part] = opt_object;
} else if (cur[part]) {
cur = cur[part];
} else {
cur = cur[part] = {};
}
}
};
/**
* Returns an object based on its fully qualified external name. If you are
* using a compilation pass that renames property names beware that using this
* function will not find renamed properties.
*
* @param {string} name The fully qualified name.
* @param {Object=} opt_obj The object within which to look; default is
* |goog.global|.
* @return {?} The value (object or primitive) or, if not found, null.
*/
goog.getObjectByName = function(name, opt_obj) {
var parts = name.split('.');
var cur = opt_obj || goog.global;
for (var part; part = parts.shift(); ) {
if (goog.isDefAndNotNull(cur[part])) {
cur = cur[part];
} else {
return null;
}
}
return cur;
};
/**
* Globalizes a whole namespace, such as goog or goog.lang.
*
* @param {Object} obj The namespace to globalize.
* @param {Object=} opt_global The object to add the properties to.
* @deprecated Properties may be explicitly exported to the global scope, but
* this should no longer be done in bulk.
*/
goog.globalize = function(obj, opt_global) {
var global = opt_global || goog.global;
for (var x in obj) {
global[x] = obj[x];
}
};
/**
* Adds a dependency from a file to the files it requires.
* @param {string} relPath The path to the js file.
* @param {Array} provides An array of strings with the names of the objects
* this file provides.
* @param {Array} requires An array of strings with the names of the objects
* this file requires.
*/
goog.addDependency = function(relPath, provides, requires) {
if (!COMPILED) {
var provide, require;
var path = relPath.replace(/\\/g, '/');
var deps = goog.dependencies_;
for (var i = 0; provide = provides[i]; i++) {
deps.nameToPath[provide] = path;
if (!(path in deps.pathToNames)) {
deps.pathToNames[path] = {};
}
deps.pathToNames[path][provide] = true;
}
for (var j = 0; require = requires[j]; j++) {
if (!(path in deps.requires)) {
deps.requires[path] = {};
}
deps.requires[path][require] = true;
}
}
};
// NOTE(nnaze): The debug DOM loader was included in base.js as an orignal
// way to do "debug-mode" development. The dependency system can sometimes
// be confusing, as can the debug DOM loader's asyncronous nature.
//
// With the DOM loader, a call to goog.require() is not blocking -- the
// script will not load until some point after the current script. If a
// namespace is needed at runtime, it needs to be defined in a previous
// script, or loaded via require() with its registered dependencies.
// User-defined namespaces may need their own deps file. See http://go/js_deps,
// http://go/genjsdeps, or, externally, DepsWriter.
// http://code.google.com/closure/library/docs/depswriter.html
//
// Because of legacy clients, the DOM loader can't be easily removed from
// base.js. Work is being done to make it disableable or replaceable for
// different environments (DOM-less JavaScript interpreters like Rhino or V8,
// for example). See bootstrap/ for more information.
/**
* @define {boolean} Whether to enable the debug loader.
*
* If enabled, a call to goog.require() will attempt to load the namespace by
* appending a script tag to the DOM (if the namespace has been registered).
*
* If disabled, goog.require() will simply assert that the namespace has been
* provided (and depend on the fact that some outside tool correctly ordered
* the script).
*/
goog.ENABLE_DEBUG_LOADER = true;
/**
* Implements a system for the dynamic resolution of dependencies
* that works in parallel with the BUILD system. Note that all calls
* to goog.require will be stripped by the JSCompiler when the
* --closure_pass option is used.
* @see goog.provide
* @param {string} name Namespace to include (as was given in goog.provide())
* in the form "goog.package.part".
*/
goog.require = function(name) {
// if the object already exists we do not need do do anything
// TODO(arv): If we start to support require based on file name this has
// to change
// TODO(arv): If we allow goog.foo.* this has to change
// TODO(arv): If we implement dynamic load after page load we should probably
// not remove this code for the compiled output
if (!COMPILED) {
if (goog.isProvided_(name)) {
return;
}
if (goog.ENABLE_DEBUG_LOADER) {
var path = goog.getPathFromDeps_(name);
if (path) {
goog.included_[path] = true;
goog.writeScripts_();
return;
}
}
var errorMessage = 'goog.require could not find: ' + name;
if (goog.global.console) {
goog.global.console['error'](errorMessage);
}
throw Error(errorMessage);
}
};
/**
* Path for included scripts
* @type {string}
*/
goog.basePath = '';
/**
* A hook for overriding the base path.
* @type {string|undefined}
*/
goog.global.CLOSURE_BASE_PATH;
/**
* Whether to write out Closure's deps file. By default,
* the deps are written.
* @type {boolean|undefined}
*/
goog.global.CLOSURE_NO_DEPS;
/**
* A function to import a single script. This is meant to be overridden when
* Closure is being run in non-HTML contexts, such as web workers. It's defined
* in the global scope so that it can be set before base.js is loaded, which
* allows deps.js to be imported properly.
*
* The function is passed the script source, which is a relative URI. It should
* return true if the script was imported, false otherwise.
*/
goog.global.CLOSURE_IMPORT_SCRIPT;
/**
* Null function used for default values of callbacks, etc.
* @return {void} Nothing.
*/
goog.nullFunction = function() {};
/**
* The identity function. Returns its first argument.
*
* @param {*=} opt_returnValue The single value that will be returned.
* @param {...*} var_args Optional trailing arguments. These are ignored.
* @return {?} The first argument. We can't know the type -- just pass it along
* without type.
* @deprecated Use goog.functions.identity instead.
*/
goog.identityFunction = function(opt_returnValue, var_args) {
return opt_returnValue;
};
/**
* When defining a class Foo with an abstract method bar(), you can do:
*
* Foo.prototype.bar = goog.abstractMethod
*
* Now if a subclass of Foo fails to override bar(), an error
* will be thrown when bar() is invoked.
*
* Note: This does not take the name of the function to override as
* an argument because that would make it more difficult to obfuscate
* our JavaScript code.
*
* @type {!Function}
* @throws {Error} when invoked to indicate the method should be
* overridden.
*/
goog.abstractMethod = function() {
throw Error('unimplemented abstract method');
};
/**
* Adds a {@code getInstance} static method that always return the same instance
* object.
* @param {!Function} ctor The constructor for the class to add the static
* method to.
*/
goog.addSingletonGetter = function(ctor) {
ctor.getInstance = function() {
if (ctor.instance_) {
return ctor.instance_;
}
if (goog.DEBUG) {
// NOTE: JSCompiler can't optimize away Array#push.
goog.instantiatedSingletons_[goog.instantiatedSingletons_.length] = ctor;
}
return ctor.instance_ = new ctor;
};
};
/**
* All singleton classes that have been instantiated, for testing. Don't read
* it directly, use the {@code goog.testing.singleton} module. The compiler
* removes this variable if unused.
* @type {!Array.<!Function>}
* @private
*/
goog.instantiatedSingletons_ = [];
if (!COMPILED && goog.ENABLE_DEBUG_LOADER) {
/**
* Object used to keep track of urls that have already been added. This
* record allows the prevention of circular dependencies.
* @type {Object}
* @private
*/
goog.included_ = {};
/**
* This object is used to keep track of dependencies and other data that is
* used for loading scripts
* @private
* @type {Object}
*/
goog.dependencies_ = {
pathToNames: {}, // 1 to many
nameToPath: {}, // 1 to 1
requires: {}, // 1 to many
// used when resolving dependencies to prevent us from
// visiting the file twice
visited: {},
written: {} // used to keep track of script files we have written
};
/**
* Tries to detect whether is in the context of an HTML document.
* @return {boolean} True if it looks like HTML document.
* @private
*/
goog.inHtmlDocument_ = function() {
var doc = goog.global.document;
return typeof doc != 'undefined' &&
'write' in doc; // XULDocument misses write.
};
/**
* Tries to detect the base path of the base.js script that bootstraps Closure
* @private
*/
goog.findBasePath_ = function() {
if (goog.global.CLOSURE_BASE_PATH) {
goog.basePath = goog.global.CLOSURE_BASE_PATH;
return;
} else if (!goog.inHtmlDocument_()) {
return;
}
var doc = goog.global.document;
var scripts = doc.getElementsByTagName('script');
// Search backwards since the current script is in almost all cases the one
// that has base.js.
for (var i = scripts.length - 1; i >= 0; --i) {
var src = scripts[i].src;
var qmark = src.lastIndexOf('?');
var l = qmark == -1 ? src.length : qmark;
if (src.substr(l - 7, 7) == 'base.js') {
goog.basePath = src.substr(0, l - 7);
return;
}
}
};
/**
* Imports a script if, and only if, that script hasn't already been imported.
* (Must be called at execution time)
* @param {string} src Script source.
* @private
*/
goog.importScript_ = function(src) {
var importScript = goog.global.CLOSURE_IMPORT_SCRIPT ||
goog.writeScriptTag_;
if (!goog.dependencies_.written[src] && importScript(src)) {
goog.dependencies_.written[src] = true;
}
};
/**
* The default implementation of the import function. Writes a script tag to
* import the script.
*
* @param {string} src The script source.
* @return {boolean} True if the script was imported, false otherwise.
* @private
*/
goog.writeScriptTag_ = function(src) {
if (goog.inHtmlDocument_()) {
var doc = goog.global.document;
// If the user tries to require a new symbol after document load,
// something has gone terribly wrong. Doing a document.write would
// wipe out the page.
if (doc.readyState == 'complete') {
// Certain test frameworks load base.js multiple times, which tries
// to write deps.js each time. If that happens, just fail silently.
// These frameworks wipe the page between each load of base.js, so this
// is OK.
var isDeps = /\bdeps.js$/.test(src);
if (isDeps) {
return false;
} else {
throw Error('Cannot write "' + src + '" after document load');
}
}
doc.write(
'<script type="text/javascript" src="' + src + '"></' + 'script>');
return true;
} else {
return false;
}
};
/**
* Resolves dependencies based on the dependencies added using addDependency
* and calls importScript_ in the correct order.
* @private
*/
goog.writeScripts_ = function() {
// the scripts we need to write this time
var scripts = [];
var seenScript = {};
var deps = goog.dependencies_;
function visitNode(path) {
if (path in deps.written) {
return;
}
// we have already visited this one. We can get here if we have cyclic
// dependencies
if (path in deps.visited) {
if (!(path in seenScript)) {
seenScript[path] = true;
scripts.push(path);
}
return;
}
deps.visited[path] = true;
if (path in deps.requires) {
for (var requireName in deps.requires[path]) {
// If the required name is defined, we assume that it was already
// bootstrapped by other means.
if (!goog.isProvided_(requireName)) {
if (requireName in deps.nameToPath) {
visitNode(deps.nameToPath[requireName]);
} else {
throw Error('Undefined nameToPath for ' + requireName);
}
}
}
}
if (!(path in seenScript)) {
seenScript[path] = true;
scripts.push(path);
}
}
for (var path in goog.included_) {
if (!deps.written[path]) {
visitNode(path);
}
}
for (var i = 0; i < scripts.length; i++) {
if (scripts[i]) {
goog.importScript_(goog.basePath + scripts[i]);
} else {
throw Error('Undefined script input');
}
}
};
/**
* Looks at the dependency rules and tries to determine the script file that
* fulfills a particular rule.
* @param {string} rule In the form goog.namespace.Class or project.script.
* @return {?string} Url corresponding to the rule, or null.
* @private
*/
goog.getPathFromDeps_ = function(rule) {
if (rule in goog.dependencies_.nameToPath) {
return goog.dependencies_.nameToPath[rule];
} else {
return null;
}
};
goog.findBasePath_();
// Allow projects to manage the deps files themselves.
if (!goog.global.CLOSURE_NO_DEPS) {
goog.importScript_(goog.basePath + 'deps.js');
}
}
//==============================================================================
// Language Enhancements
//==============================================================================
/**
* This is a "fixed" version of the typeof operator. It differs from the typeof
* operator in such a way that null returns 'null' and arrays return 'array'.
* @param {*} value The value to get the type of.
* @return {string} The name of the type.
*/
goog.typeOf = function(value) {
var s = typeof value;
if (s == 'object') {
if (value) {
// Check these first, so we can avoid calling Object.prototype.toString if
// possible.
//
// IE improperly marshals tyepof across execution contexts, but a
// cross-context object will still return false for "instanceof Object".
if (value instanceof Array) {
return 'array';
} else if (value instanceof Object) {
return s;
}
// HACK: In order to use an Object prototype method on the arbitrary
// value, the compiler requires the value be cast to type Object,
// even though the ECMA spec explicitly allows it.
var className = Object.prototype.toString.call(
/** @type {Object} */ (value));
// In Firefox 3.6, attempting to access iframe window objects' length
// property throws an NS_ERROR_FAILURE, so we need to special-case it
// here.
if (className == '[object Window]') {
return 'object';
}
// We cannot always use constructor == Array or instanceof Array because
// different frames have different Array objects. In IE6, if the iframe
// where the array was created is destroyed, the array loses its
// prototype. Then dereferencing val.splice here throws an exception, so
// we can't use goog.isFunction. Calling typeof directly returns 'unknown'
// so that will work. In this case, this function will return false and
// most array functions will still work because the array is still
// array-like (supports length and []) even though it has lost its
// prototype.
// Mark Miller noticed that Object.prototype.toString
// allows access to the unforgeable [[Class]] property.
// 15.2.4.2 Object.prototype.toString ( )
// When the toString method is called, the following steps are taken:
// 1. Get the [[Class]] property of this object.
// 2. Compute a string value by concatenating the three strings
// "[object ", Result(1), and "]".
// 3. Return Result(2).
// and this behavior survives the destruction of the execution context.
if ((className == '[object Array]' ||
// In IE all non value types are wrapped as objects across window
// boundaries (not iframe though) so we have to do object detection
// for this edge case
typeof value.length == 'number' &&
typeof value.splice != 'undefined' &&
typeof value.propertyIsEnumerable != 'undefined' &&
!value.propertyIsEnumerable('splice')
)) {
return 'array';
}
// HACK: There is still an array case that fails.
// function ArrayImpostor() {}
// ArrayImpostor.prototype = [];
// var impostor = new ArrayImpostor;
// this can be fixed by getting rid of the fast path
// (value instanceof Array) and solely relying on
// (value && Object.prototype.toString.vall(value) === '[object Array]')
// but that would require many more function calls and is not warranted
// unless closure code is receiving objects from untrusted sources.
// IE in cross-window calls does not correctly marshal the function type
// (it appears just as an object) so we cannot use just typeof val ==
// 'function'. However, if the object has a call property, it is a
// function.
if ((className == '[object Function]' ||
typeof value.call != 'undefined' &&
typeof value.propertyIsEnumerable != 'undefined' &&
!value.propertyIsEnumerable('call'))) {
return 'function';
}
} else {
return 'null';
}
} else if (s == 'function' && typeof value.call == 'undefined') {
// In Safari typeof nodeList returns 'function', and on Firefox
// typeof behaves similarly for HTML{Applet,Embed,Object}Elements
// and RegExps. We would like to return object for those and we can
// detect an invalid function by making sure that the function
// object has a call method.
return 'object';
}
return s;
};
/**
* Returns true if the specified value is not |undefined|.
* WARNING: Do not use this to test if an object has a property. Use the in
* operator instead. Additionally, this function assumes that the global
* undefined variable has not been redefined.
* @param {*} val Variable to test.
* @return {boolean} Whether variable is defined.
*/
goog.isDef = function(val) {
return val !== undefined;
};
/**
* Returns true if the specified value is |null|
* @param {*} val Variable to test.
* @return {boolean} Whether variable is null.
*/
goog.isNull = function(val) {
return val === null;
};
/**
* Returns true if the specified value is defined and not null
* @param {*} val Variable to test.
* @return {boolean} Whether variable is defined and not null.
*/
goog.isDefAndNotNull = function(val) {
// Note that undefined == null.
return val != null;
};
/**
* Returns true if the specified value is an array
* @param {*} val Variable to test.
* @return {boolean} Whether variable is an array.
*/
goog.isArray = function(val) {
return goog.typeOf(val) == 'array';
};
/**
* Returns true if the object looks like an array. To qualify as array like
* the value needs to be either a NodeList or an object with a Number length
* property.
* @param {*} val Variable to test.
* @return {boolean} Whether variable is an array.
*/
goog.isArrayLike = function(val) {
var type = goog.typeOf(val);
return type == 'array' || type == 'object' && typeof val.length == 'number';
};
/**
* Returns true if the object looks like a Date. To qualify as Date-like
* the value needs to be an object and have a getFullYear() function.
* @param {*} val Variable to test.
* @return {boolean} Whether variable is a like a Date.
*/
goog.isDateLike = function(val) {
return goog.isObject(val) && typeof val.getFullYear == 'function';
};
/**
* Returns true if the specified value is a string
* @param {*} val Variable to test.
* @return {boolean} Whether variable is a string.
*/
goog.isString = function(val) {
return typeof val == 'string';
};
/**
* Returns true if the specified value is a boolean
* @param {*} val Variable to test.
* @return {boolean} Whether variable is boolean.
*/
goog.isBoolean = function(val) {
return typeof val == 'boolean';
};
/**
* Returns true if the specified value is a number
* @param {*} val Variable to test.
* @return {boolean} Whether variable is a number.
*/
goog.isNumber = function(val) {
return typeof val == 'number';
};
/**
* Returns true if the specified value is a function
* @param {*} val Variable to test.
* @return {boolean} Whether variable is a function.
*/
goog.isFunction = function(val) {
return goog.typeOf(val) == 'function';
};
/**
* Returns true if the specified value is an object. This includes arrays
* and functions.
* @param {*} val Variable to test.
* @return {boolean} Whether variable is an object.
*/
goog.isObject = function(val) {
var type = typeof val;
return type == 'object' && val != null || type == 'function';
// return Object(val) === val also works, but is slower, especially if val is
// not an object.
};
/**
* Gets a unique ID for an object. This mutates the object so that further
* calls with the same object as a parameter returns the same value. The unique
* ID is guaranteed to be unique across the current session amongst objects that
* are passed into {@code getUid}. There is no guarantee that the ID is unique
* or consistent across sessions. It is unsafe to generate unique ID for
* function prototypes.
*
* @param {Object} obj The object to get the unique ID for.
* @return {number} The unique ID for the object.
*/
goog.getUid = function(obj) {
// TODO(arv): Make the type stricter, do not accept null.
// In Opera window.hasOwnProperty exists but always returns false so we avoid
// using it. As a consequence the unique ID generated for BaseClass.prototype
// and SubClass.prototype will be the same.
return obj[goog.UID_PROPERTY_] ||
(obj[goog.UID_PROPERTY_] = ++goog.uidCounter_);
};
/**
* Removes the unique ID from an object. This is useful if the object was
* previously mutated using {@code goog.getUid} in which case the mutation is
* undone.
* @param {Object} obj The object to remove the unique ID field from.
*/
goog.removeUid = function(obj) {
// TODO(arv): Make the type stricter, do not accept null.
// DOM nodes in IE are not instance of Object and throws exception
// for delete. Instead we try to use removeAttribute
if ('removeAttribute' in obj) {
obj.removeAttribute(goog.UID_PROPERTY_);
}
/** @preserveTry */
try {
delete obj[goog.UID_PROPERTY_];
} catch (ex) {
}
};
/**
* Name for unique ID property. Initialized in a way to help avoid collisions
* with other closure javascript on the same page.
* @type {string}
* @private
*/
goog.UID_PROPERTY_ = 'closure_uid_' + ((Math.random() * 1e9) >>> 0);
/**
* Counter for UID.
* @type {number}
* @private
*/
goog.uidCounter_ = 0;
/**
* Adds a hash code field to an object. The hash code is unique for the
* given object.
* @param {Object} obj The object to get the hash code for.
* @return {number} The hash code for the object.
* @deprecated Use goog.getUid instead.
*/
goog.getHashCode = goog.getUid;
/**
* Removes the hash code field from an object.
* @param {Object} obj The object to remove the field from.
* @deprecated Use goog.removeUid instead.
*/
goog.removeHashCode = goog.removeUid;
/**
* Clones a value. The input may be an Object, Array, or basic type. Objects and
* arrays will be cloned recursively.
*
* WARNINGS:
* <code>goog.cloneObject</code> does not detect reference loops. Objects that
* refer to themselves will cause infinite recursion.
*
* <code>goog.cloneObject</code> is unaware of unique identifiers, and copies
* UIDs created by <code>getUid</code> into cloned results.
*
* @param {*} obj The value to clone.
* @return {*} A clone of the input value.
* @deprecated goog.cloneObject is unsafe. Prefer the goog.object methods.
*/
goog.cloneObject = function(obj) {
var type = goog.typeOf(obj);
if (type == 'object' || type == 'array') {
if (obj.clone) {
return obj.clone();
}
var clone = type == 'array' ? [] : {};
for (var key in obj) {
clone[key] = goog.cloneObject(obj[key]);
}
return clone;
}
return obj;
};
/**
* A native implementation of goog.bind.
* @param {Function} fn A function to partially apply.
* @param {Object|undefined} selfObj Specifies the object which |this| should
* point to when the function is run.
* @param {...*} var_args Additional arguments that are partially
* applied to the function.
* @return {!Function} A partially-applied form of the function bind() was
* invoked as a method of.
* @private
* @suppress {deprecated} The compiler thinks that Function.prototype.bind
* is deprecated because some people have declared a pure-JS version.
* Only the pure-JS version is truly deprecated.
*/
goog.bindNative_ = function(fn, selfObj, var_args) {
return /** @type {!Function} */ (fn.call.apply(fn.bind, arguments));
};
/**
* A pure-JS implementation of goog.bind.
* @param {Function} fn A function to partially apply.
* @param {Object|undefined} selfObj Specifies the object which |this| should
* point to when the function is run.
* @param {...*} var_args Additional arguments that are partially
* applied to the function.
* @return {!Function} A partially-applied form of the function bind() was
* invoked as a method of.
* @private
*/
goog.bindJs_ = function(fn, selfObj, var_args) {
if (!fn) {
throw new Error();
}
if (arguments.length > 2) {
var boundArgs = Array.prototype.slice.call(arguments, 2);
return function() {
// Prepend the bound arguments to the current arguments.
var newArgs = Array.prototype.slice.call(arguments);
Array.prototype.unshift.apply(newArgs, boundArgs);
return fn.apply(selfObj, newArgs);
};
} else {
return function() {
return fn.apply(selfObj, arguments);
};
}
};
/**
* Partially applies this function to a particular 'this object' and zero or
* more arguments. The result is a new function with some arguments of the first
* function pre-filled and the value of |this| 'pre-specified'.<br><br>
*
* Remaining arguments specified at call-time are appended to the pre-
* specified ones.<br><br>
*
* Also see: {@link #partial}.<br><br>
*
* Usage:
* <pre>var barMethBound = bind(myFunction, myObj, 'arg1', 'arg2');
* barMethBound('arg3', 'arg4');</pre>
*
* @param {Function} fn A function to partially apply.
* @param {Object|undefined} selfObj Specifies the object which |this| should
* point to when the function is run.
* @param {...*} var_args Additional arguments that are partially
* applied to the function.
* @return {!Function} A partially-applied form of the function bind() was
* invoked as a method of.
* @suppress {deprecated} See above.
*/
goog.bind = function(fn, selfObj, var_args) {
// TODO(nicksantos): narrow the type signature.
if (Function.prototype.bind &&
// NOTE(nicksantos): Somebody pulled base.js into the default
// Chrome extension environment. This means that for Chrome extensions,
// they get the implementation of Function.prototype.bind that
// calls goog.bind instead of the native one. Even worse, we don't want
// to introduce a circular dependency between goog.bind and
// Function.prototype.bind, so we have to hack this to make sure it
// works correctly.
Function.prototype.bind.toString().indexOf('native code') != -1) {
goog.bind = goog.bindNative_;
} else {
goog.bind = goog.bindJs_;
}
return goog.bind.apply(null, arguments);
};
/**
* Like bind(), except that a 'this object' is not required. Useful when the
* target function is already bound.
*
* Usage:
* var g = partial(f, arg1, arg2);
* g(arg3, arg4);
*
* @param {Function} fn A function to partially apply.
* @param {...*} var_args Additional arguments that are partially
* applied to fn.
* @return {!Function} A partially-applied form of the function bind() was
* invoked as a method of.
*/
goog.partial = function(fn, var_args) {
var args = Array.prototype.slice.call(arguments, 1);
return function() {
// Prepend the bound arguments to the current arguments.
var newArgs = Array.prototype.slice.call(arguments);
newArgs.unshift.apply(newArgs, args);
return fn.apply(this, newArgs);
};
};
/**
* Copies all the members of a source object to a target object. This method
* does not work on all browsers for all objects that contain keys such as
* toString or hasOwnProperty. Use goog.object.extend for this purpose.
* @param {Object} target Target.
* @param {Object} source Source.
*/
goog.mixin = function(target, source) {
for (var x in source) {
target[x] = source[x];
}
// For IE7 or lower, the for-in-loop does not contain any properties that are
// not enumerable on the prototype object (for example, isPrototypeOf from
// Object.prototype) but also it will not include 'replace' on objects that
// extend String and change 'replace' (not that it is common for anyone to
// extend anything except Object).
};
/**
* @return {number} An integer value representing the number of milliseconds
* between midnight, January 1, 1970 and the current time.
*/
goog.now = (goog.TRUSTED_SITE && Date.now) || (function() {
// Unary plus operator converts its operand to a number which in the case of
// a date is done by calling getTime().
return +new Date();
});
/**
* Evals javascript in the global scope. In IE this uses execScript, other
* browsers use goog.global.eval. If goog.global.eval does not evaluate in the
* global scope (for example, in Safari), appends a script tag instead.
* Throws an exception if neither execScript or eval is defined.
* @param {string} script JavaScript string.
*/
goog.globalEval = function(script) {
if (goog.global.execScript) {
goog.global.execScript(script, 'JavaScript');
} else if (goog.global.eval) {
// Test to see if eval works
if (goog.evalWorksForGlobals_ == null) {
goog.global.eval('var _et_ = 1;');
if (typeof goog.global['_et_'] != 'undefined') {
delete goog.global['_et_'];
goog.evalWorksForGlobals_ = true;
} else {
goog.evalWorksForGlobals_ = false;
}
}
if (goog.evalWorksForGlobals_) {
goog.global.eval(script);
} else {
var doc = goog.global.document;
var scriptElt = doc.createElement('script');
scriptElt.type = 'text/javascript';
scriptElt.defer = false;
// Note(user): can't use .innerHTML since "t('<test>')" will fail and
// .text doesn't work in Safari 2. Therefore we append a text node.
scriptElt.appendChild(doc.createTextNode(script));
doc.body.appendChild(scriptElt);
doc.body.removeChild(scriptElt);
}
} else {
throw Error('goog.globalEval not available');
}
};
/**
* Indicates whether or not we can call 'eval' directly to eval code in the
* global scope. Set to a Boolean by the first call to goog.globalEval (which
* empirically tests whether eval works for globals). @see goog.globalEval
* @type {?boolean}
* @private
*/
goog.evalWorksForGlobals_ = null;
/**
* Optional map of CSS class names to obfuscated names used with
* goog.getCssName().
* @type {Object|undefined}
* @private
* @see goog.setCssNameMapping
*/
goog.cssNameMapping_;
/**
* Optional obfuscation style for CSS class names. Should be set to either
* 'BY_WHOLE' or 'BY_PART' if defined.
* @type {string|undefined}
* @private
* @see goog.setCssNameMapping
*/
goog.cssNameMappingStyle_;
/**
* Handles strings that are intended to be used as CSS class names.
*
* This function works in tandem with @see goog.setCssNameMapping.
*
* Without any mapping set, the arguments are simple joined with a
* hyphen and passed through unaltered.
*
* When there is a mapping, there are two possible styles in which
* these mappings are used. In the BY_PART style, each part (i.e. in
* between hyphens) of the passed in css name is rewritten according
* to the map. In the BY_WHOLE style, the full css name is looked up in
* the map directly. If a rewrite is not specified by the map, the
* compiler will output a warning.
*
* When the mapping is passed to the compiler, it will replace calls
* to goog.getCssName with the strings from the mapping, e.g.
* var x = goog.getCssName('foo');
* var y = goog.getCssName(this.baseClass, 'active');
* becomes:
* var x= 'foo';
* var y = this.baseClass + '-active';
*
* If one argument is passed it will be processed, if two are passed
* only the modifier will be processed, as it is assumed the first
* argument was generated as a result of calling goog.getCssName.
*
* @param {string} className The class name.
* @param {string=} opt_modifier A modifier to be appended to the class name.
* @return {string} The class name or the concatenation of the class name and
* the modifier.
*/
goog.getCssName = function(className, opt_modifier) {
var getMapping = function(cssName) {
return goog.cssNameMapping_[cssName] || cssName;
};
var renameByParts = function(cssName) {
// Remap all the parts individually.
var parts = cssName.split('-');
var mapped = [];
for (var i = 0; i < parts.length; i++) {
mapped.push(getMapping(parts[i]));
}
return mapped.join('-');
};
var rename;
if (goog.cssNameMapping_) {
rename = goog.cssNameMappingStyle_ == 'BY_WHOLE' ?
getMapping : renameByParts;
} else {
rename = function(a) {
return a;
};
}
if (opt_modifier) {
return className + '-' + rename(opt_modifier);
} else {
return rename(className);
}
};
/**
* Sets the map to check when returning a value from goog.getCssName(). Example:
* <pre>
* goog.setCssNameMapping({
* "goog": "a",
* "disabled": "b",
* });
*
* var x = goog.getCssName('goog');
* // The following evaluates to: "a a-b".
* goog.getCssName('goog') + ' ' + goog.getCssName(x, 'disabled')
* </pre>
* When declared as a map of string literals to string literals, the JSCompiler
* will replace all calls to goog.getCssName() using the supplied map if the
* --closure_pass flag is set.
*
* @param {!Object} mapping A map of strings to strings where keys are possible
* arguments to goog.getCssName() and values are the corresponding values
* that should be returned.
* @param {string=} opt_style The style of css name mapping. There are two valid
* options: 'BY_PART', and 'BY_WHOLE'.
* @see goog.getCssName for a description.
*/
goog.setCssNameMapping = function(mapping, opt_style) {
goog.cssNameMapping_ = mapping;
goog.cssNameMappingStyle_ = opt_style;
};
/**
* To use CSS renaming in compiled mode, one of the input files should have a
* call to goog.setCssNameMapping() with an object literal that the JSCompiler
* can extract and use to replace all calls to goog.getCssName(). In uncompiled
* mode, JavaScript code should be loaded before this base.js file that declares
* a global variable, CLOSURE_CSS_NAME_MAPPING, which is used below. This is
* to ensure that the mapping is loaded before any calls to goog.getCssName()
* are made in uncompiled mode.
*
* A hook for overriding the CSS name mapping.
* @type {Object|undefined}
*/
goog.global.CLOSURE_CSS_NAME_MAPPING;
if (!COMPILED && goog.global.CLOSURE_CSS_NAME_MAPPING) {
// This does not call goog.setCssNameMapping() because the JSCompiler
// requires that goog.setCssNameMapping() be called with an object literal.
goog.cssNameMapping_ = goog.global.CLOSURE_CSS_NAME_MAPPING;
}
/**
* Gets a localized message.
*
* This function is a compiler primitive. If you give the compiler a localized
* message bundle, it will replace the string at compile-time with a localized
* version, and expand goog.getMsg call to a concatenated string.
*
* Messages must be initialized in the form:
* <code>
* var MSG_NAME = goog.getMsg('Hello {$placeholder}', {'placeholder': 'world'});
* </code>
*
* @param {string} str Translatable string, places holders in the form {$foo}.
* @param {Object=} opt_values Map of place holder name to value.
* @return {string} message with placeholders filled.
*/
goog.getMsg = function(str, opt_values) {
var values = opt_values || {};
for (var key in values) {
var value = ('' + values[key]).replace(/\$/g, '$$$$');
str = str.replace(new RegExp('\\{\\$' + key + '\\}', 'gi'), value);
}
return str;
};
/**
* Gets a localized message. If the message does not have a translation, gives a
* fallback message.
*
* This is useful when introducing a new message that has not yet been
* translated into all languages.
*
* This function is a compiler primtive. Must be used in the form:
* <code>var x = goog.getMsgWithFallback(MSG_A, MSG_B);</code>
* where MSG_A and MSG_B were initialized with goog.getMsg.
*
* @param {string} a The preferred message.
* @param {string} b The fallback message.
* @return {string} The best translated message.
*/
goog.getMsgWithFallback = function(a, b) {
return a;
};
/**
* Exposes an unobfuscated global namespace path for the given object.
* Note that fields of the exported object *will* be obfuscated,
* unless they are exported in turn via this function or
* goog.exportProperty
*
* <p>Also handy for making public items that are defined in anonymous
* closures.
*
* ex. goog.exportSymbol('public.path.Foo', Foo);
*
* ex. goog.exportSymbol('public.path.Foo.staticFunction',
* Foo.staticFunction);
* public.path.Foo.staticFunction();
*
* ex. goog.exportSymbol('public.path.Foo.prototype.myMethod',
* Foo.prototype.myMethod);
* new public.path.Foo().myMethod();
*
* @param {string} publicPath Unobfuscated name to export.
* @param {*} object Object the name should point to.
* @param {Object=} opt_objectToExportTo The object to add the path to; default
* is |goog.global|.
*/
goog.exportSymbol = function(publicPath, object, opt_objectToExportTo) {
goog.exportPath_(publicPath, object, opt_objectToExportTo);
};
/**
* Exports a property unobfuscated into the object's namespace.
* ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction);
* ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod);
* @param {Object} object Object whose static property is being exported.
* @param {string} publicName Unobfuscated name to export.
* @param {*} symbol Object the name should point to.
*/
goog.exportProperty = function(object, publicName, symbol) {
object[publicName] = symbol;
};
/**
* Inherit the prototype methods from one constructor into another.
*
* Usage:
* <pre>
* function ParentClass(a, b) { }
* ParentClass.prototype.foo = function(a) { }
*
* function ChildClass(a, b, c) {
* goog.base(this, a, b);
* }
* goog.inherits(ChildClass, ParentClass);
*
* var child = new ChildClass('a', 'b', 'see');
* child.foo(); // works
* </pre>
*
* In addition, a superclass' implementation of a method can be invoked
* as follows:
*
* <pre>
* ChildClass.prototype.foo = function(a) {
* ChildClass.superClass_.foo.call(this, a);
* // other code
* };
* </pre>
*
* @param {Function} childCtor Child class.
* @param {Function} parentCtor Parent class.
*/
goog.inherits = function(childCtor, parentCtor) {
/** @constructor */
function tempCtor() {};
tempCtor.prototype = parentCtor.prototype;
childCtor.superClass_ = parentCtor.prototype;
childCtor.prototype = new tempCtor();
/** @override */
childCtor.prototype.constructor = childCtor;
};
/**
* Allow for aliasing within scope functions. This function exists for
* uncompiled code - in compiled code the calls will be inlined and the
* aliases applied. In uncompiled code the function is simply run since the
* aliases as written are valid JavaScript.
* @param {function()} fn Function to call. This function can contain aliases
* to namespaces (e.g. "var dom = goog.dom") or classes
* (e.g. "var Timer = goog.Timer").
*/
goog.scope = function(fn) {
fn.call(goog.global);
};
gitextract_wegyr1t_/
├── .gitignore
├── .travis.yml
├── Gruntfile.js
├── LICENSE
├── README.md
├── bower.json
├── exports.js
├── externs-commonjs.js
├── externs.js
├── fontfaceobserver.js
├── fontfaceobserver.standalone.js
├── package.json
├── src/
│ ├── descriptors.js
│ ├── observer.js
│ └── ruler.js
├── test/
│ ├── assets/
│ │ ├── index.html
│ │ └── late.css
│ ├── browser-test.html
│ ├── deps.js
│ ├── index.html
│ ├── observer-test.js
│ └── ruler-test.js
└── vendor/
└── google/
└── base.js
SYMBOL INDEX (42 symbols across 5 files)
FILE: fontfaceobserver.js
function l (line 1) | function l(a){g.push(a);1==g.length&&f()}
function m (line 1) | function m(){for(;g.length;)g[0](),g.shift()}
function n (line 1) | function n(a){this.a=p;this.b=void 0;this.f=[];var b=this;try{a(function...
function t (line 1) | function t(a){return new n(function(b,c){c(a)})}
function u (line 1) | function u(a){return new n(function(b){b(a)})}
function q (line 1) | function q(a,b){if(a.a==p){if(b==a)throw new TypeError;var c=!1;try{var ...
function r (line 2) | function r(a,b){if(a.a==p){if(b==a)throw new TypeError;a.a=1;a.b=b;v(a)}}
function v (line 2) | function v(a){l(function(){if(a.a!=p)for(;a.f.length;){var b=a.f.shift()...
function w (line 3) | function w(a){return new n(function(b,c){function d(c){return function(d...
function x (line 3) | function x(a){return new n(function(b,c){for(var d=0;d<a.length;d+=1)u(a...
function p (line 5) | function p(a,c){document.addEventListener?a.addEventListener("scroll",c,...
function u (line 5) | function u(a){document.body?a():document.addEventListener?document.addEv...
function w (line 5) | function w(a){this.g=document.createElement("div");this.g.setAttribute("...
function x (line 7) | function x(a,c){a.g.style.cssText="max-width:none;min-width:20px;min-hei...
function B (line 7) | function B(a){var c=a.g.offsetWidth,b=c+100;a.j.style.width=b+"px";a.i.s...
function C (line 7) | function C(a,c){function b(){var e=g;B(e)&&null!==e.g.parentNode&&c(e.l)...
function D (line 7) | function D(a,c,b){c=c||{};b=b||window;this.family=a;this.style=c.style||...
function I (line 7) | function I(a){null===F&&(M(a)&&/Apple/.test(window.navigator.vendor)?(a=...
function M (line 7) | function M(a){null===H&&(H=!!a.document.fonts);return H}
function N (line 8) | function N(a,c){var b=a.style,g=a.weight;if(null===G){var e=document.cre...
function h (line 9) | function h(){(new Date).getTime()-J>=q?t(Error(""+q+"ms timeout exceeded...
function r (line 10) | function r(){var d;if(d=-1!=k&&-1!=l||-1!=k&&-1!=m||-1!=l&&-1!=m)(d=k!=l...
function t (line 10) | function t(){if((new Date).getTime()-J>=q)null!==f.parentNode&&f.parentN...
FILE: fontfaceobserver.standalone.js
function p (line 1) | function p(a,c){document.addEventListener?a.addEventListener("scroll",c,...
function u (line 1) | function u(a){document.body?a():document.addEventListener?document.addEv...
function w (line 1) | function w(a){this.g=document.createElement("div");this.g.setAttribute("...
function x (line 3) | function x(a,c){a.g.style.cssText="max-width:none;min-width:20px;min-hei...
function B (line 3) | function B(a){var c=a.g.offsetWidth,b=c+100;a.j.style.width=b+"px";a.i.s...
function C (line 3) | function C(a,c){function b(){var e=g;B(e)&&null!==e.g.parentNode&&c(e.l)...
function D (line 3) | function D(a,c,b){c=c||{};b=b||window;this.family=a;this.style=c.style||...
function I (line 3) | function I(a){null===F&&(M(a)&&/Apple/.test(window.navigator.vendor)?(a=...
function M (line 3) | function M(a){null===H&&(H=!!a.document.fonts);return H}
function N (line 4) | function N(a,c){var b=a.style,g=a.weight;if(null===G){var e=document.cre...
function h (line 5) | function h(){(new Date).getTime()-J>=q?t(Error(""+q+"ms timeout exceeded...
function r (line 6) | function r(){var d;if(d=-1!=k&&-1!=l||-1!=k&&-1!=m||-1!=l&&-1!=m)(d=k!=l...
function t (line 6) | function t(){if((new Date).getTime()-J>=q)null!==f.parentNode&&f.parentN...
FILE: src/observer.js
function removeContainer (line 249) | function removeContainer() {
function check (line 270) | function check() {
function checkForTimeout (line 310) | function checkForTimeout() {
FILE: src/ruler.js
function onScroll (line 122) | function onScroll() {
FILE: vendor/google/base.js
function visitNode (line 594) | function visitNode(path) {
function tempCtor (line 1468) | function tempCtor() {}
Condensed preview — 23 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (114K chars).
[
{
"path": ".gitignore",
"chars": 19,
"preview": "build\nnode_modules\n"
},
{
"path": ".travis.yml",
"chars": 166,
"preview": "sudo: false\n\nlanguage: node_js\n\nbefore_install:\n - npm install -g grunt-cli\n\nnode_js:\n - \"4\"\n\nmatrix:\n fast_finish: t"
},
{
"path": "Gruntfile.js",
"chars": 2852,
"preview": "module.exports = function (grunt) {\n require('google-closure-compiler').grunt(grunt, {\n max_parallel_compilations: r"
},
{
"path": "LICENSE",
"chars": 1298,
"preview": "Copyright (c) 2014 - Bram Stein\r\n\r\nRedistribution and use in source and binary forms, with or without\r\nmodification, are"
},
{
"path": "README.md",
"chars": 5714,
"preview": "# Font Face Observer [](https://travi"
},
{
"path": "bower.json",
"chars": 463,
"preview": "{\n \"name\": \"fontfaceobserver\",\n \"description\": \"Fast and simple web font loading.\",\n \"main\": \"fontfaceobserver.standa"
},
{
"path": "exports.js",
"chars": 255,
"preview": "goog.require('fontface.Observer');\n\nif (typeof module === 'object') {\n module.exports = fontface.Observer;\n} else {\n w"
},
{
"path": "externs-commonjs.js",
"chars": 92,
"preview": "\n/**\n * @type {Object}\n */\nvar module = {};\n\n/**\n * @type {Object}\n */\nmodule.exports = {};\n"
},
{
"path": "externs.js",
"chars": 326,
"preview": "/**\n * @constructor\n *\n * @param {string} family\n * @param {Object} descriptors\n */\nvar FontFaceObserver = function (fam"
},
{
"path": "fontfaceobserver.js",
"chars": 6041,
"preview": "/* Font Face Observer v2.3.0 - © Bram Stein. License: BSD-3-Clause */(function(){'use strict';var f,g=[];function l(a){g"
},
{
"path": "fontfaceobserver.standalone.js",
"chars": 4454,
"preview": "/* Font Face Observer v2.3.0 - © Bram Stein. License: BSD-3-Clause */(function(){function p(a,c){document.addEventListen"
},
{
"path": "package.json",
"chars": 1451,
"preview": "{\n \"name\": \"fontfaceobserver\",\n \"version\": \"2.3.0\",\n \"description\": \"Detect if web fonts are available\",\n \"directori"
},
{
"path": "src/descriptors.js",
"chars": 188,
"preview": "goog.provide('fontface.Descriptors');\n\n/**\n * @typedef {{\n * style: (string|undefined),\n * weight: (string|undefined"
},
{
"path": "src/observer.js",
"chars": 11026,
"preview": "goog.provide('fontface.Observer');\n\ngoog.require('fontface.Ruler');\ngoog.require('dom');\n\ngoog.scope(function () {\n var"
},
{
"path": "src/ruler.js",
"chars": 3441,
"preview": "goog.provide('fontface.Ruler');\n\ngoog.require('dom');\n\ngoog.scope(function () {\n /**\n * @constructor\n * @param {str"
},
{
"path": "test/assets/index.html",
"chars": 79,
"preview": "<!doctype html>\n<html>\n <head>\n </head>\n <body>\n hello\n </body>\n</html>\n"
},
{
"path": "test/assets/late.css",
"chars": 148,
"preview": "@font-face { font-family: observer-test9; src: url(sourcesanspro-regular.woff) format('woff'), url(sourcesanspro-regular"
},
{
"path": "test/browser-test.html",
"chars": 516,
"preview": "<!doctype html>\n<html>\n <head>\n <title>Browser Test</title>\n <meta charset=\"utf-8\">\n <style>\n @font-face "
},
{
"path": "test/deps.js",
"chars": 367,
"preview": "// This file was autogenerated by calcdeps.js\ngoog.addDependency('../../node_modules/closure-dom/src/dom.js', ['dom'], ["
},
{
"path": "test/index.html",
"chars": 3045,
"preview": "<!doctype html>\n<html>\n <head>\n <meta charset=\"utf-8\">\n <title>FontFaceObserver</title>\n <meta name=\"viewport\""
},
{
"path": "test/observer-test.js",
"chars": 17244,
"preview": "describe('Observer', function () {\n var Observer = fontface.Observer,\n Ruler = fontface.Ruler;\n\n describe('#const"
},
{
"path": "test/ruler-test.js",
"chars": 2550,
"preview": "describe('Ruler', function () {\n var Ruler = fontface.Ruler,\n ruler = null;\n\n beforeEach(function () {\n ruler "
},
{
"path": "vendor/google/base.js",
"chars": 47979,
"preview": "// Copyright 2006 The Closure Library Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0"
}
]
About this extraction
This page contains the full source code of the bramstein/fontfaceobserver GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 23 files (107.1 KB), approximately 27.8k tokens, and a symbol index with 42 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.