Update wmts-hidpi, add nicer-api-docs

This commit is contained in:
Andreas Hocevar
2014-05-06 13:02:46 -05:00
parent b3ac1afd00
commit 1e25fc5585
2239 changed files with 3726515 additions and 37010 deletions

View File

@@ -0,0 +1,2 @@
// This is a dummy file to trick genjsdeps into doing the right thing.
// TODO(nicksantos): fix this

View File

@@ -0,0 +1,611 @@
// Copyright 2006-2008, The Google Caja project.
// Modifications Copyright 2009 The Closure Library Authors. All Rights Reserved.
// All Rights Reserved
/**
* @license Portions of this code are from the google-caja project, received by
* Google under the Apache license (http://code.google.com/p/google-caja/).
* All other code is Copyright 2009 Google, Inc. All Rights Reserved.
// Copyright (C) 2006 Google Inc.
//
// 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 A Html SAX parser.
*
* Examples of usage of the {@code goog.string.html.HtmlParser}:
* <pre>
* var handler = new MyCustomHtmlVisitorHandlerThatExtendsHtmlSaxHandler();
* var parser = new goog.string.html.HtmlParser();
* parser.parse(handler, '<html><a href="google.com">link found!</a></html>');
* </pre>
*
* TODO(user, msamuel): validate sanitizer regex against the HTML5 grammar at
* http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html
* http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html
* http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html
* http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html
*
* @supported IE6, IE7, IE8, FF1.5, FF2, FF3, Chrome 3.0, Safari and Opera 10.
*/
goog.provide('goog.string.html.HtmlParser');
goog.provide('goog.string.html.HtmlParser.EFlags');
goog.provide('goog.string.html.HtmlParser.Elements');
goog.provide('goog.string.html.HtmlParser.Entities');
goog.provide('goog.string.html.HtmlSaxHandler');
/**
* An Html parser: {@code parse} takes a string and calls methods on
* {@code goog.string.html.HtmlSaxHandler} while it is visiting it.
*
* @constructor
*/
goog.string.html.HtmlParser = function() {
};
/**
* HTML entities that are encoded/decoded.
* TODO(user): use {@code goog.string.htmlEncode} instead.
* @enum {string}
*/
goog.string.html.HtmlParser.Entities = {
lt: '<',
gt: '>',
amp: '&',
nbsp: '\240',
quot: '"',
apos: '\''
};
/**
* The html eflags, used internally on the parser.
* @enum {number}
*/
goog.string.html.HtmlParser.EFlags = {
OPTIONAL_ENDTAG: 1,
EMPTY: 2,
CDATA: 4,
RCDATA: 8,
UNSAFE: 16,
FOLDABLE: 32
};
/**
* A map of element to a bitmap of flags it has, used internally on the parser.
* @type {Object}
*/
goog.string.html.HtmlParser.Elements = {
'a': 0,
'abbr': 0,
'acronym': 0,
'address': 0,
'applet': goog.string.html.HtmlParser.EFlags.UNSAFE,
'area': goog.string.html.HtmlParser.EFlags.EMPTY,
'b': 0,
'base': goog.string.html.HtmlParser.EFlags.EMPTY |
goog.string.html.HtmlParser.EFlags.UNSAFE,
'basefont': goog.string.html.HtmlParser.EFlags.EMPTY |
goog.string.html.HtmlParser.EFlags.UNSAFE,
'bdo': 0,
'big': 0,
'blockquote': 0,
'body': goog.string.html.HtmlParser.EFlags.OPTIONAL_ENDTAG |
goog.string.html.HtmlParser.EFlags.UNSAFE |
goog.string.html.HtmlParser.EFlags.FOLDABLE,
'br': goog.string.html.HtmlParser.EFlags.EMPTY,
'button': 0,
'caption': 0,
'center': 0,
'cite': 0,
'code': 0,
'col': goog.string.html.HtmlParser.EFlags.EMPTY,
'colgroup': goog.string.html.HtmlParser.EFlags.OPTIONAL_ENDTAG,
'dd': goog.string.html.HtmlParser.EFlags.OPTIONAL_ENDTAG,
'del': 0,
'dfn': 0,
'dir': 0,
'div': 0,
'dl': 0,
'dt': goog.string.html.HtmlParser.EFlags.OPTIONAL_ENDTAG,
'em': 0,
'fieldset': 0,
'font': 0,
'form': 0,
'frame': goog.string.html.HtmlParser.EFlags.EMPTY |
goog.string.html.HtmlParser.EFlags.UNSAFE,
'frameset': goog.string.html.HtmlParser.EFlags.UNSAFE,
'h1': 0,
'h2': 0,
'h3': 0,
'h4': 0,
'h5': 0,
'h6': 0,
'head': goog.string.html.HtmlParser.EFlags.OPTIONAL_ENDTAG |
goog.string.html.HtmlParser.EFlags.UNSAFE |
goog.string.html.HtmlParser.EFlags.FOLDABLE,
'hr': goog.string.html.HtmlParser.EFlags.EMPTY,
'html': goog.string.html.HtmlParser.EFlags.OPTIONAL_ENDTAG |
goog.string.html.HtmlParser.EFlags.UNSAFE |
goog.string.html.HtmlParser.EFlags.FOLDABLE,
'i': 0,
'iframe': goog.string.html.HtmlParser.EFlags.UNSAFE |
goog.string.html.HtmlParser.EFlags.CDATA,
'img': goog.string.html.HtmlParser.EFlags.EMPTY,
'input': goog.string.html.HtmlParser.EFlags.EMPTY,
'ins': 0,
'isindex': goog.string.html.HtmlParser.EFlags.EMPTY |
goog.string.html.HtmlParser.EFlags.UNSAFE,
'kbd': 0,
'label': 0,
'legend': 0,
'li': goog.string.html.HtmlParser.EFlags.OPTIONAL_ENDTAG,
'link': goog.string.html.HtmlParser.EFlags.EMPTY |
goog.string.html.HtmlParser.EFlags.UNSAFE,
'map': 0,
'menu': 0,
'meta': goog.string.html.HtmlParser.EFlags.EMPTY |
goog.string.html.HtmlParser.EFlags.UNSAFE,
'noframes': goog.string.html.HtmlParser.EFlags.UNSAFE |
goog.string.html.HtmlParser.EFlags.CDATA,
'noscript': goog.string.html.HtmlParser.EFlags.UNSAFE |
goog.string.html.HtmlParser.EFlags.CDATA,
'object': goog.string.html.HtmlParser.EFlags.UNSAFE,
'ol': 0,
'optgroup': 0,
'option': goog.string.html.HtmlParser.EFlags.OPTIONAL_ENDTAG,
'p': goog.string.html.HtmlParser.EFlags.OPTIONAL_ENDTAG,
'param': goog.string.html.HtmlParser.EFlags.EMPTY |
goog.string.html.HtmlParser.EFlags.UNSAFE,
'pre': 0,
'q': 0,
's': 0,
'samp': 0,
'script': goog.string.html.HtmlParser.EFlags.UNSAFE |
goog.string.html.HtmlParser.EFlags.CDATA,
'select': 0,
'small': 0,
'span': 0,
'strike': 0,
'strong': 0,
'style': goog.string.html.HtmlParser.EFlags.UNSAFE |
goog.string.html.HtmlParser.EFlags.CDATA,
'sub': 0,
'sup': 0,
'table': 0,
'tbody': goog.string.html.HtmlParser.EFlags.OPTIONAL_ENDTAG,
'td': goog.string.html.HtmlParser.EFlags.OPTIONAL_ENDTAG,
'textarea': goog.string.html.HtmlParser.EFlags.RCDATA,
'tfoot': goog.string.html.HtmlParser.EFlags.OPTIONAL_ENDTAG,
'th': goog.string.html.HtmlParser.EFlags.OPTIONAL_ENDTAG,
'thead': goog.string.html.HtmlParser.EFlags.OPTIONAL_ENDTAG,
'title': goog.string.html.HtmlParser.EFlags.RCDATA |
goog.string.html.HtmlParser.EFlags.UNSAFE,
'tr': goog.string.html.HtmlParser.EFlags.OPTIONAL_ENDTAG,
'tt': 0,
'u': 0,
'ul': 0,
'var': 0
};
/**
* Regular expression that matches &s.
* @type {RegExp}
* @private
*/
goog.string.html.HtmlParser.AMP_RE_ = /&/g;
/**
* Regular expression that matches loose &s.
* @type {RegExp}
* @private
*/
goog.string.html.HtmlParser.LOOSE_AMP_RE_ =
/&([^a-z#]|#(?:[^0-9x]|x(?:[^0-9a-f]|$)|$)|$)/gi;
/**
* Regular expression that matches <.
* @type {RegExp}
* @private
*/
goog.string.html.HtmlParser.LT_RE_ = /</g;
/**
* Regular expression that matches >.
* @type {RegExp}
* @private
*/
goog.string.html.HtmlParser.GT_RE_ = />/g;
/**
* Regular expression that matches ".
* @type {RegExp}
* @private
*/
goog.string.html.HtmlParser.QUOTE_RE_ = /\"/g;
/**
* Regular expression that matches =.
* @type {RegExp}
* @private
*/
goog.string.html.HtmlParser.EQUALS_RE_ = /=/g;
/**
* Regular expression that matches null characters.
* @type {RegExp}
* @private
*/
goog.string.html.HtmlParser.NULL_RE_ = /\0/g;
/**
* Regular expression that matches entities.
* @type {RegExp}
* @private
*/
goog.string.html.HtmlParser.ENTITY_RE_ = /&(#\d+|#x[0-9A-Fa-f]+|\w+);/g;
/**
* Regular expression that matches decimal numbers.
* @type {RegExp}
* @private
*/
goog.string.html.HtmlParser.DECIMAL_ESCAPE_RE_ = /^#(\d+)$/;
/**
* Regular expression that matches hexadecimal numbers.
* @type {RegExp}
* @private
*/
goog.string.html.HtmlParser.HEX_ESCAPE_RE_ = /^#x([0-9A-Fa-f]+)$/;
/**
* Regular expression that matches the next token to be processed.
* @type {RegExp}
* @private
*/
goog.string.html.HtmlParser.INSIDE_TAG_TOKEN_ = new RegExp(
// Don't capture space.
'^\\s*(?:' +
// Capture an attribute name in group 1, and value in group 3.
// We capture the fact that there was an attribute in group 2, since
// interpreters are inconsistent in whether a group that matches nothing
// is null, undefined, or the empty string.
('(?:' +
'([a-z][a-z-]*)' + // attribute name
('(' + // optionally followed
'\\s*=\\s*' +
('(' +
// A double quoted string.
'\"[^\"]*\"' +
// A single quoted string.
'|\'[^\']*\'' +
// The positive lookahead is used to make sure that in
// <foo bar= baz=boo>, the value for bar is blank, not "baz=boo".
'|(?=[a-z][a-z-]*\\s*=)' +
// An unquoted value that is not an attribute name.
// We know it is not an attribute name because the previous
// zero-width match would've eliminated that possibility.
'|[^>\"\'\\s]*' +
')'
) +
')'
) + '?' +
')'
) +
// End of tag captured in group 3.
'|(/?>)' +
// Don't capture cruft
'|[^a-z\\s>]+)',
'i');
/**
* Regular expression that matches the next token to be processed when we are
* outside a tag.
* @type {RegExp}
* @private
*/
goog.string.html.HtmlParser.OUTSIDE_TAG_TOKEN_ = new RegExp(
'^(?:' +
// Entity captured in group 1.
'&(\\#[0-9]+|\\#[x][0-9a-f]+|\\w+);' +
// Comment, doctypes, and processing instructions not captured.
'|<[!]--[\\s\\S]*?-->|<!\\w[^>]*>|<\\?[^>*]*>' +
// '/' captured in group 2 for close tags, and name captured in group 3.
'|<(/)?([a-z][a-z0-9]*)' +
// Text captured in group 4.
'|([^<&>]+)' +
// Cruft captured in group 5.
'|([<&>]))',
'i');
/**
* Given a SAX-like {@code goog.string.html.HtmlSaxHandler} parses a
* {@code htmlText} and lets the {@code handler} know the structure while
* visiting the nodes.
*
* @param {goog.string.html.HtmlSaxHandler} handler The HtmlSaxHandler that will
* receive the events.
* @param {string} htmlText The html text.
*/
goog.string.html.HtmlParser.prototype.parse = function(handler, htmlText) {
var htmlLower = null;
var inTag = false; // True iff we're currently processing a tag.
var attribs = []; // Accumulates attribute names and values.
var tagName; // The name of the tag currently being processed.
var eflags; // The element flags for the current tag.
var openTag; // True if the current tag is an open tag.
// Lets the handler know that we are starting to parse the document.
handler.startDoc();
// Consumes tokens from the htmlText and stops once all tokens are processed.
while (htmlText) {
var regex = inTag ?
goog.string.html.HtmlParser.INSIDE_TAG_TOKEN_ :
goog.string.html.HtmlParser.OUTSIDE_TAG_TOKEN_;
// Gets the next token
var m = htmlText.match(regex);
// And removes it from the string
htmlText = htmlText.substring(m[0].length);
// TODO(goto): cleanup this code breaking it into separate methods.
if (inTag) {
if (m[1]) { // Attribute.
// SetAttribute with uppercase names doesn't work on IE6.
var attribName = goog.string.html.toLowerCase(m[1]);
var decodedValue;
if (m[2]) {
var encodedValue = m[3];
switch (encodedValue.charCodeAt(0)) { // Strip quotes.
case 34: case 39:
encodedValue = encodedValue.substring(
1, encodedValue.length - 1);
break;
}
decodedValue = this.unescapeEntities_(this.stripNULs_(encodedValue));
} else {
// Use name as value for valueless attribs, so
// <input type=checkbox checked>
// gets attributes ['type', 'checkbox', 'checked', 'checked']
decodedValue = attribName;
}
attribs.push(attribName, decodedValue);
} else if (m[4]) {
if (eflags !== void 0) { // False if not in whitelist.
if (openTag) {
if (handler.startTag) {
handler.startTag(/** @type {string} */ (tagName), attribs);
}
} else {
if (handler.endTag) {
handler.endTag(/** @type {string} */ (tagName));
}
}
}
if (openTag && (eflags &
(goog.string.html.HtmlParser.EFlags.CDATA |
goog.string.html.HtmlParser.EFlags.RCDATA))) {
if (htmlLower === null) {
htmlLower = goog.string.html.toLowerCase (htmlText);
} else {
htmlLower = htmlLower.substring(
htmlLower.length - htmlText.length);
}
var dataEnd = htmlLower.indexOf('</' + tagName);
if (dataEnd < 0) {
dataEnd = htmlText.length;
}
if (eflags & goog.string.html.HtmlParser.EFlags.CDATA) {
if (handler.cdata) {
handler.cdata(htmlText.substring(0, dataEnd));
}
} else if (handler.rcdata) {
handler.rcdata(
this.normalizeRCData_(htmlText.substring(0, dataEnd)));
}
htmlText = htmlText.substring(dataEnd);
}
tagName = eflags = openTag = void 0;
attribs.length = 0;
inTag = false;
}
} else {
if (m[1]) { // Entity.
handler.pcdata(m[0]);
} else if (m[3]) { // Tag.
openTag = !m[2];
inTag = true;
tagName = goog.string.html.toLowerCase (m[3]);
eflags = goog.string.html.HtmlParser.Elements.hasOwnProperty(tagName) ?
goog.string.html.HtmlParser.Elements[tagName] : void 0;
} else if (m[4]) { // Text.
handler.pcdata(m[4]);
} else if (m[5]) { // Cruft.
switch (m[5]) {
case '<': handler.pcdata('&lt;'); break;
case '>': handler.pcdata('&gt;'); break;
default: handler.pcdata('&amp;'); break;
}
}
}
}
// Lets the handler know that we are done parsing the document.
handler.endDoc();
};
/**
* Decodes an HTML entity.
*
* @param {string} name The content between the '&' and the ';'.
* @return {string} A single unicode code-point as a string.
* @private
*/
goog.string.html.HtmlParser.prototype.lookupEntity_ = function(name) {
// TODO(goto): use {goog.string.htmlDecode} instead ?
// TODO(goto): &pi; is different from &Pi;
name = goog.string.html.toLowerCase(name);
if (goog.string.html.HtmlParser.Entities.hasOwnProperty(name)) {
return goog.string.html.HtmlParser.Entities[name];
}
var m = name.match(goog.string.html.HtmlParser.DECIMAL_ESCAPE_RE_);
if (m) {
return String.fromCharCode(parseInt(m[1], 10));
} else if (
!!(m = name.match(goog.string.html.HtmlParser.HEX_ESCAPE_RE_))) {
return String.fromCharCode(parseInt(m[1], 16));
}
return '';
};
/**
* Removes null characters on the string.
* @param {string} s The string to have the null characters removed.
* @return {string} A string without null characters.
* @private
*/
goog.string.html.HtmlParser.prototype.stripNULs_ = function(s) {
return s.replace(goog.string.html.HtmlParser.NULL_RE_, '');
};
/**
* The plain text of a chunk of HTML CDATA which possibly containing.
*
* TODO(goto): use {@code goog.string.unescapeEntities} instead ?
* @param {string} s A chunk of HTML CDATA. It must not start or end inside
* an HTML entity.
* @return {string} The unescaped entities.
* @private
*/
goog.string.html.HtmlParser.prototype.unescapeEntities_ = function(s) {
return s.replace(
goog.string.html.HtmlParser.ENTITY_RE_,
goog.bind(this.lookupEntity_, this));
};
/**
* Escape entities in RCDATA that can be escaped without changing the meaning.
* @param {string} rcdata The RCDATA string we want to normalize.
* @return {string} A normalized version of RCDATA.
* @private
*/
goog.string.html.HtmlParser.prototype.normalizeRCData_ = function(rcdata) {
return rcdata.
replace(goog.string.html.HtmlParser.LOOSE_AMP_RE_, '&amp;$1').
replace(goog.string.html.HtmlParser.LT_RE_, '&lt;').
replace(goog.string.html.HtmlParser.GT_RE_, '&gt;');
};
/**
* TODO(goto): why isn't this in the string package ? does this solves any
* real problem ? move it to the goog.string package if it does.
*
* @param {string} str The string to lower case.
* @return {string} The str in lower case format.
*/
goog.string.html.toLowerCase = function(str) {
// The below may not be true on browsers in the Turkish locale.
if ('script' === 'SCRIPT'.toLowerCase()) {
return str.toLowerCase();
} else {
return str.replace(/[A-Z]/g, function(ch) {
return String.fromCharCode(ch.charCodeAt(0) | 32);
});
}
};
/**
* An interface to the {@code goog.string.html.HtmlParser} visitor, that gets
* called while the HTML is being parsed.
*
* @constructor
*/
goog.string.html.HtmlSaxHandler = function() {
};
/**
* Handler called when the parser found a new tag.
* @param {string} name The name of the tag that is starting.
* @param {Array.<string>} attributes The attributes of the tag.
*/
goog.string.html.HtmlSaxHandler.prototype.startTag = goog.abstractMethod;
/**
* Handler called when the parser found a closing tag.
* @param {string} name The name of the tag that is ending.
*/
goog.string.html.HtmlSaxHandler.prototype.endTag = goog.abstractMethod;
/**
* Handler called when PCDATA is found.
* @param {string} text The PCDATA text found.
*/
goog.string.html.HtmlSaxHandler.prototype.pcdata = goog.abstractMethod;
/**
* Handler called when RCDATA is found.
* @param {string} text The RCDATA text found.
*/
goog.string.html.HtmlSaxHandler.prototype.rcdata = goog.abstractMethod;
/**
* Handler called when CDATA is found.
* @param {string} text The CDATA text found.
*/
goog.string.html.HtmlSaxHandler.prototype.cdata = goog.abstractMethod;
/**
* Handler called when the parser is starting to parse the document.
*/
goog.string.html.HtmlSaxHandler.prototype.startDoc = goog.abstractMethod;
/**
* Handler called when the parsing is done.
*/
goog.string.html.HtmlSaxHandler.prototype.endDoc = goog.abstractMethod;

View File

@@ -0,0 +1,605 @@
// Copyright 2006-2008, The Google Caja project.
// Modifications Copyright 2009 The Closure Library Authors. All Rights Reserved.
// All Rights Reserved
/**
* @license Portions of this code are from the google-caja project, received by
* Google under the Apache license (http://code.google.com/p/google-caja/).
* All other code is Copyright 2009 Google, Inc. All Rights Reserved.
// Copyright (C) 2006 Google Inc.
//
// 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 An HTML sanitizer that can satisfy a variety of security
* policies. The HTML sanitizer is built around a SAX parser and HTML element
* and attributes schemas.
*
* This package provides html sanitizing and parsing functions.
* {@code goog.string.htmlSanitize} is basically just using a custom written
* {@code goog.string.HtmlSaxHandler} that outputs safe html as the unsafe
* html content is parsed by {@code goog.string.HtmlParser}.
*
* Examples of usage of the static {@code goog.string.htmlSanitize}:
* <pre>
* var safeHtml = goog.string.html.htmlSanitize('<script src="xss.js" />');
* el.innerHTML = safeHtml;
* </pre>
*
* We use {@code goog.string.StringBuffer} for fast string concatenation, since
* htmlSanitize is relatively heavy considering that it is designed to parse
* large html files.
*
* @supported IE6, IE7, IE8, FF1.5, FF2, FF3, Chrome 4.0, Safari and Opera 10.
*/
goog.provide('goog.string.html.HtmlSanitizer');
goog.provide('goog.string.html.HtmlSanitizer.AttributeType');
goog.provide('goog.string.html.HtmlSanitizer.Attributes');
goog.provide('goog.string.html.htmlSanitize');
goog.require('goog.string.StringBuffer');
goog.require('goog.string.html.HtmlParser');
goog.require('goog.string.html.HtmlParser.EFlags');
goog.require('goog.string.html.HtmlParser.Elements');
goog.require('goog.string.html.HtmlSaxHandler');
/**
* Strips unsafe tags and attributes from HTML.
*
* @param {string} htmlText The HTML text to sanitize.
* @param {function(string) : string} opt_urlPolicy A transform to apply to URL
* attribute values.
* @param {function(string) : string} opt_nmTokenPolicy A transform to apply to
* names, IDs, and classes.
* @return {string} A sanitized HTML, safe to be embedded on the page.
*/
goog.string.html.htmlSanitize = function(
htmlText, opt_urlPolicy, opt_nmTokenPolicy) {
var stringBuffer = new goog.string.StringBuffer();
var handler = new goog.string.html.HtmlSanitizer(
stringBuffer, opt_urlPolicy, opt_nmTokenPolicy);
var parser = new goog.string.html.HtmlParser();
parser.parse(handler, htmlText);
return stringBuffer.toString();
};
/**
* An implementation of the {@code goog.string.HtmlSaxHandler} interface that
* will take each of the html tags and sanitize it.
*
* @param {goog.string.StringBuffer} stringBuffer A string buffer, used to
* output the html as we sanitize it.
* @param {?function(string):string} opt_urlPolicy An optional function to be
* applied in URLs.
* @param {?function(string):string} opt_nmTokenPolicy An optional function to
* be applied in names.
* @constructor
* @extends {goog.string.html.HtmlSaxHandler}
*/
goog.string.html.HtmlSanitizer = function(
stringBuffer, opt_urlPolicy, opt_nmTokenPolicy) {
goog.string.html.HtmlSaxHandler.call(this);
/**
* The string buffer that holds the sanitized version of the html. Used
* during the parse time.
* @type {goog.string.StringBuffer}
* @private
*/
this.stringBuffer_ = stringBuffer;
/**
* A stack that holds how the handler is being called.
* @type {Array}
* @private
*/
this.stack_ = [];
/**
* Whether we are ignoring what is being processed or not.
* @type {boolean}
* @private
*/
this.ignoring_ = false;
/**
* A function to be applied to urls found on the parsing process.
* @type {?function(string):string}
* @private
*/
this.urlPolicy_ = opt_urlPolicy;
/**
* A function to be applied to names fround on the parsing process.
* @type {?function(string):string}
* @private
*/
this.nmTokenPolicy_ = opt_nmTokenPolicy;
};
goog.inherits(
goog.string.html.HtmlSanitizer,
goog.string.html.HtmlSaxHandler);
/**
* The HTML types the parser supports.
* @enum {number}
*/
goog.string.html.HtmlSanitizer.AttributeType = {
NONE: 0,
URI: 1,
URI_FRAGMENT: 11,
SCRIPT: 2,
STYLE: 3,
ID: 4,
IDREF: 5,
IDREFS: 6,
GLOBAL_NAME: 7,
LOCAL_NAME: 8,
CLASSES: 9,
FRAME_TARGET: 10
};
/**
* A map of attributes to types it has.
* @enum {number}
*/
goog.string.html.HtmlSanitizer.Attributes = {
'*::class': goog.string.html.HtmlSanitizer.AttributeType.CLASSES,
'*::dir': 0,
'*::id': goog.string.html.HtmlSanitizer.AttributeType.ID,
'*::lang': 0,
'*::onclick': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'*::ondblclick': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'*::onkeydown': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'*::onkeypress': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'*::onkeyup': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'*::onload': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'*::onmousedown': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'*::onmousemove': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'*::onmouseout': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'*::onmouseover': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'*::onmouseup': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'*::style': goog.string.html.HtmlSanitizer.AttributeType.STYLE,
'*::title': 0,
'*::accesskey': 0,
'*::tabindex': 0,
'*::onfocus': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'*::onblur': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'a::coords': 0,
'a::href': goog.string.html.HtmlSanitizer.AttributeType.URI,
'a::hreflang': 0,
'a::name': goog.string.html.HtmlSanitizer.AttributeType.GLOBAL_NAME,
'a::onblur': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'a::rel': 0,
'a::rev': 0,
'a::shape': 0,
'a::target': goog.string.html.HtmlSanitizer.AttributeType.FRAME_TARGET,
'a::type': 0,
'area::accesskey': 0,
'area::alt': 0,
'area::coords': 0,
'area::href': goog.string.html.HtmlSanitizer.AttributeType.URI,
'area::nohref': 0,
'area::onfocus': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'area::shape': 0,
'area::tabindex': 0,
'area::target': goog.string.html.HtmlSanitizer.AttributeType.FRAME_TARGET,
'bdo::dir': 0,
'blockquote::cite': goog.string.html.HtmlSanitizer.AttributeType.URI,
'br::clear': 0,
'button::accesskey': 0,
'button::disabled': 0,
'button::name': goog.string.html.HtmlSanitizer.AttributeType.LOCAL_NAME,
'button::onblur': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'button::onfocus': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'button::tabindex': 0,
'button::type': 0,
'button::value': 0,
'caption::align': 0,
'col::align': 0,
'col::char': 0,
'col::charoff': 0,
'col::span': 0,
'col::valign': 0,
'col::width': 0,
'colgroup::align': 0,
'colgroup::char': 0,
'colgroup::charoff': 0,
'colgroup::span': 0,
'colgroup::valign': 0,
'colgroup::width': 0,
'del::cite': goog.string.html.HtmlSanitizer.AttributeType.URI,
'del::datetime': 0,
'dir::compact': 0,
'div::align': 0,
'dl::compact': 0,
'font::color': 0,
'font::face': 0,
'font::size': 0,
'form::accept': 0,
'form::action': goog.string.html.HtmlSanitizer.AttributeType.URI,
'form::autocomplete': 0,
'form::enctype': 0,
'form::method': 0,
'form::name': goog.string.html.HtmlSanitizer.AttributeType.GLOBAL_NAME,
'form::onreset': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'form::onsubmit': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'form::target': goog.string.html.HtmlSanitizer.AttributeType.FRAME_TARGET,
'h1::align': 0,
'h2::align': 0,
'h3::align': 0,
'h4::align': 0,
'h5::align': 0,
'h6::align': 0,
'hr::align': 0,
'hr::noshade': 0,
'hr::size': 0,
'hr::width': 0,
'img::align': 0,
'img::alt': 0,
'img::border': 0,
'img::height': 0,
'img::hspace': 0,
'img::ismap': 0,
'img::longdesc': goog.string.html.HtmlSanitizer.AttributeType.URI,
'img::name': goog.string.html.HtmlSanitizer.AttributeType.GLOBAL_NAME,
'img::src': goog.string.html.HtmlSanitizer.AttributeType.URI,
'img::usemap': goog.string.html.HtmlSanitizer.AttributeType.URI_FRAGMENT,
'img::vspace': 0,
'img::width': 0,
'input::accept': 0,
'input::accesskey': 0,
'input::autocomplete': 0,
'input::align': 0,
'input::alt': 0,
'input::checked': 0,
'input::disabled': 0,
'input::ismap': 0,
'input::maxlength': 0,
'input::name': goog.string.html.HtmlSanitizer.AttributeType.LOCAL_NAME,
'input::onblur': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'input::onchange': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'input::onfocus': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'input::onselect': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'input::readonly': 0,
'input::size': 0,
'input::src': goog.string.html.HtmlSanitizer.AttributeType.URI,
'input::tabindex': 0,
'input::type': 0,
'input::usemap': goog.string.html.HtmlSanitizer.AttributeType.URI_FRAGMENT,
'input::value': 0,
'ins::cite': goog.string.html.HtmlSanitizer.AttributeType.URI,
'ins::datetime': 0,
'label::accesskey': 0,
'label::for': goog.string.html.HtmlSanitizer.AttributeType.IDREF,
'label::onblur': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'label::onfocus': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'legend::accesskey': 0,
'legend::align': 0,
'li::type': 0,
'li::value': 0,
'map::name': goog.string.html.HtmlSanitizer.AttributeType.GLOBAL_NAME,
'menu::compact': 0,
'ol::compact': 0,
'ol::start': 0,
'ol::type': 0,
'optgroup::disabled': 0,
'optgroup::label': 0,
'option::disabled': 0,
'option::label': 0,
'option::selected': 0,
'option::value': 0,
'p::align': 0,
'pre::width': 0,
'q::cite': goog.string.html.HtmlSanitizer.AttributeType.URI,
'select::disabled': 0,
'select::multiple': 0,
'select::name': goog.string.html.HtmlSanitizer.AttributeType.LOCAL_NAME,
'select::onblur': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'select::onchange': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'select::onfocus': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'select::size': 0,
'select::tabindex': 0,
'table::align': 0,
'table::bgcolor': 0,
'table::border': 0,
'table::cellpadding': 0,
'table::cellspacing': 0,
'table::frame': 0,
'table::rules': 0,
'table::summary': 0,
'table::width': 0,
'tbody::align': 0,
'tbody::char': 0,
'tbody::charoff': 0,
'tbody::valign': 0,
'td::abbr': 0,
'td::align': 0,
'td::axis': 0,
'td::bgcolor': 0,
'td::char': 0,
'td::charoff': 0,
'td::colspan': 0,
'td::headers': goog.string.html.HtmlSanitizer.AttributeType.IDREFS,
'td::height': 0,
'td::nowrap': 0,
'td::rowspan': 0,
'td::scope': 0,
'td::valign': 0,
'td::width': 0,
'textarea::accesskey': 0,
'textarea::cols': 0,
'textarea::disabled': 0,
'textarea::name': goog.string.html.HtmlSanitizer.AttributeType.LOCAL_NAME,
'textarea::onblur': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'textarea::onchange': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'textarea::onfocus': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'textarea::onselect': goog.string.html.HtmlSanitizer.AttributeType.SCRIPT,
'textarea::readonly': 0,
'textarea::rows': 0,
'textarea::tabindex': 0,
'tfoot::align': 0,
'tfoot::char': 0,
'tfoot::charoff': 0,
'tfoot::valign': 0,
'th::abbr': 0,
'th::align': 0,
'th::axis': 0,
'th::bgcolor': 0,
'th::char': 0,
'th::charoff': 0,
'th::colspan': 0,
'th::headers': goog.string.html.HtmlSanitizer.AttributeType.IDREFS,
'th::height': 0,
'th::nowrap': 0,
'th::rowspan': 0,
'th::scope': 0,
'th::valign': 0,
'th::width': 0,
'thead::align': 0,
'thead::char': 0,
'thead::charoff': 0,
'thead::valign': 0,
'tr::align': 0,
'tr::bgcolor': 0,
'tr::char': 0,
'tr::charoff': 0,
'tr::valign': 0,
'ul::compact': 0,
'ul::type': 0
};
/**
* @override
*/
goog.string.html.HtmlSanitizer.prototype.startTag =
function(tagName, attribs) {
if (this.ignoring_) {
return;
}
if (!goog.string.html.HtmlParser.Elements.hasOwnProperty(tagName)) {
return;
}
var eflags = goog.string.html.HtmlParser.Elements[tagName];
if (eflags & goog.string.html.HtmlParser.EFlags.FOLDABLE) {
return;
} else if (eflags & goog.string.html.HtmlParser.EFlags.UNSAFE) {
this.ignoring_ = !(eflags & goog.string.html.HtmlParser.EFlags.EMPTY);
return;
}
attribs = this.sanitizeAttributes_(tagName, attribs);
if (attribs) {
if (!(eflags & goog.string.html.HtmlParser.EFlags.EMPTY)) {
this.stack_.push(tagName);
}
this.stringBuffer_.append('<', tagName);
for (var i = 0, n = attribs.length; i < n; i += 2) {
var attribName = attribs[i],
value = attribs[i + 1];
if (value !== null && value !== void 0) {
this.stringBuffer_.append(' ', attribName, '="',
this.escapeAttrib_(value), '"');
}
}
this.stringBuffer_.append('>');
}
};
/**
* @override
*/
goog.string.html.HtmlSanitizer.prototype.endTag = function(tagName) {
if (this.ignoring_) {
this.ignoring_ = false;
return;
}
if (!goog.string.html.HtmlParser.Elements.hasOwnProperty(tagName)) {
return;
}
var eflags = goog.string.html.HtmlParser.Elements[tagName];
if (!(eflags & (goog.string.html.HtmlParser.EFlags.UNSAFE |
goog.string.html.HtmlParser.EFlags.EMPTY |
goog.string.html.HtmlParser.EFlags.FOLDABLE))) {
var index;
if (eflags & goog.string.html.HtmlParser.EFlags.OPTIONAL_ENDTAG) {
for (index = this.stack_.length; --index >= 0;) {
var stackEl = this.stack_[index];
if (stackEl === tagName) {
break;
}
if (!(goog.string.html.HtmlParser.Elements[stackEl] &
goog.string.html.HtmlParser.EFlags.OPTIONAL_ENDTAG)) {
// Don't pop non optional end tags looking for a match.
return;
}
}
} else {
for (index = this.stack_.length; --index >= 0;) {
if (this.stack_[index] === tagName) {
break;
}
}
}
if (index < 0) { return; } // Not opened.
for (var i = this.stack_.length; --i > index;) {
var stackEl = this.stack_[i];
if (!(goog.string.html.HtmlParser.Elements[stackEl] &
goog.string.html.HtmlParser.EFlags.OPTIONAL_ENDTAG)) {
this.stringBuffer_.append('</', stackEl, '>');
}
}
this.stack_.length = index;
this.stringBuffer_.append('</', tagName, '>');
}
};
/**
* @override
*/
goog.string.html.HtmlSanitizer.prototype.pcdata = function(text) {
if (!this.ignoring_) {
this.stringBuffer_.append(text);
}
};
/**
* @override
*/
goog.string.html.HtmlSanitizer.prototype.rcdata = function(text) {
if (!this.ignoring_) {
this.stringBuffer_.append(text);
}
};
/**
* @override
*/
goog.string.html.HtmlSanitizer.prototype.cdata = function(text) {
if (!this.ignoring_) {
this.stringBuffer_.append(text);
}
};
/**
* @override
*/
goog.string.html.HtmlSanitizer.prototype.startDoc = function() {
this.stack_ = [];
this.ignoring_ = false;
};
/**
* @override
*/
goog.string.html.HtmlSanitizer.prototype.endDoc = function() {
for (var i = this.stack_.length; --i >= 0;) {
this.stringBuffer_.append('</', this.stack_[i], '>');
}
this.stack_.length = 0;
};
/**
* Escapes HTML special characters in attribute values as HTML entities.
*
* TODO(user): use {@code goog.string.htmlEscape} instead ?
* @param {string} s The string to be escaped.
* @return {string} An escaped version of {@code s}.
* @private
*/
goog.string.html.HtmlSanitizer.prototype.escapeAttrib_ = function(s) {
// Escaping '=' defangs many UTF-7 and SGML short-tag attacks.
return s.replace(goog.string.html.HtmlParser.AMP_RE_, '&amp;').
replace(goog.string.html.HtmlParser.LT_RE_, '&lt;').
replace(goog.string.html.HtmlParser.GT_RE_, '&gt;').
replace(goog.string.html.HtmlParser.QUOTE_RE_, '&#34;').
replace(goog.string.html.HtmlParser.EQUALS_RE_, '&#61;');
};
/**
* Sanitizes attributes found on html entities.
* @param {string} tagName The name of the tag in which the {@code attribs} were
* found.
* @param {Array.<?string>} attribs An array of attributes.
* @return {Array.<?string>} A sanitized version of the {@code attribs}.
* @private
*/
goog.string.html.HtmlSanitizer.prototype.sanitizeAttributes_ =
function(tagName, attribs) {
for (var i = 0; i < attribs.length; i += 2) {
var attribName = attribs[i];
var value = attribs[i + 1];
var atype = null, attribKey;
if ((attribKey = tagName + '::' + attribName,
goog.string.html.HtmlSanitizer.Attributes.hasOwnProperty(attribKey)) ||
(attribKey = '*::' + attribName,
goog.string.html.HtmlSanitizer.Attributes.hasOwnProperty(attribKey))) {
atype = goog.string.html.HtmlSanitizer.Attributes[attribKey];
}
if (atype !== null) {
switch (atype) {
case 0: break;
case goog.string.html.HtmlSanitizer.AttributeType.SCRIPT:
case goog.string.html.HtmlSanitizer.AttributeType.STYLE:
value = null;
break;
case goog.string.html.HtmlSanitizer.AttributeType.ID:
case goog.string.html.HtmlSanitizer.AttributeType.IDREF:
case goog.string.html.HtmlSanitizer.AttributeType.IDREFS:
case goog.string.html.HtmlSanitizer.AttributeType.GLOBAL_NAME:
case goog.string.html.HtmlSanitizer.AttributeType.LOCAL_NAME:
case goog.string.html.HtmlSanitizer.AttributeType.CLASSES:
value = this.nmTokenPolicy_ ?
this.nmTokenPolicy_(/** @type {string} */ (value)) : value;
break;
case goog.string.html.HtmlSanitizer.AttributeType.URI:
value = this.urlPolicy_ && this.urlPolicy_(
/** @type {string} */ (value));
break;
case goog.string.html.HtmlSanitizer.AttributeType.URI_FRAGMENT:
if (value && '#' === value.charAt(0)) {
value = this.nmTokenPolicy_ ? this.nmTokenPolicy_(value) : value;
if (value) { value = '#' + value; }
} else {
value = null;
}
break;
default:
value = null;
break;
}
} else {
value = null;
}
attribs[i + 1] = value;
}
return attribs;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,158 @@
goog.setTestOnly('query_test');
function testBasicSelectors() {
assertQuery(4, 'h3');
assertQuery(1, 'h1:first-child');
assertQuery(2, 'h3:first-child');
assertQuery(1, '#t');
assertQuery(1, '#bug');
assertQuery(4, '#t h3');
assertQuery(1, 'div#t');
assertQuery(4, 'div#t h3');
assertQuery(0, 'span#t');
assertQuery(1, '#t div > h3');
assertQuery(2, '.foo');
assertQuery(1, '.foo.bar');
assertQuery(2, '.baz');
assertQuery(3, '#t > h3');
}
function testSyntacticEquivalents() {
// syntactic equivalents
assertQuery(12, '#t > *');
assertQuery(12, '#t >');
assertQuery(3, '.foo > *');
assertQuery(3, '.foo >');
}
function testWithARootById() {
// with a root, by ID
assertQuery(3, '> *', 'container');
assertQuery(3, '> h3', 't');
}
function testCompoundQueries() {
// compound queries
assertQuery(2, '.foo, .bar');
assertQuery(2, '.foo,.bar');
}
function testMultipleClassAttributes() {
// multiple class attribute
assertQuery(1, '.foo.bar');
assertQuery(2, '.foo');
assertQuery(2, '.baz');
}
function testCaseSensitivity() {
// case sensitivity
assertQuery(1, 'span.baz');
assertQuery(1, 'sPaN.baz');
assertQuery(1, 'SPAN.baz');
assertQuery(1, '[class = \"foo bar\"]');
assertQuery(2, '[foo~=\"bar\"]');
assertQuery(2, '[ foo ~= \"bar\" ]');
}
function testAttributes() {
assertQuery(3, '[foo]');
assertQuery(1, '[foo$=\"thud\"]');
assertQuery(1, '[foo$=thud]');
assertQuery(1, '[foo$=\"thudish\"]');
assertQuery(1, '#t [foo$=thud]');
assertQuery(1, '#t [ title $= thud ]');
assertQuery(0, '#t span[ title $= thud ]');
assertQuery(2, '[foo|=\"bar\"]');
assertQuery(1, '[foo|=\"bar-baz\"]');
assertQuery(0, '[foo|=\"baz\"]');
}
function testDescendantSelectors() {
assertQuery(3, '>', 'container');
assertQuery(3, '> *', 'container');
assertQuery(2, '> [qux]', 'container');
assertEquals('child1', goog.dom.query('> [qux]', 'container')[0].id);
assertEquals('child3', goog.dom.query('> [qux]', 'container')[1].id);
assertQuery(3, '>', 'container');
assertQuery(3, '> *', 'container');
}
function testSiblingSelectors() {
assertQuery(1, '+', 'container');
assertQuery(3, '~', 'container');
assertQuery(1, '.foo + span');
assertQuery(4, '.foo ~ span');
assertQuery(1, '#foo ~ *');
assertQuery(1, '#foo ~');
}
function testSubSelectors() {
// sub-selector parsing
assertQuery(1, '#t span.foo:not(span:first-child)');
assertQuery(1, '#t span.foo:not(:first-child)');
}
function testNthChild() {
assertEquals(goog.dom.$('_foo'), goog.dom.query('.foo:nth-child(2)')[0]);
assertQuery(2, '#t > h3:nth-child(odd)');
assertQuery(3, '#t h3:nth-child(odd)');
assertQuery(3, '#t h3:nth-child(2n+1)');
assertQuery(1, '#t h3:nth-child(even)');
assertQuery(1, '#t h3:nth-child(2n)');
assertQuery(1, '#t h3:nth-child(2n+3)');
assertQuery(2, '#t h3:nth-child(1)');
assertQuery(1, '#t > h3:nth-child(1)');
assertQuery(3, '#t :nth-child(3)');
assertQuery(0, '#t > div:nth-child(1)');
assertQuery(7, '#t span');
assertQuery(3, '#t > *:nth-child(n+10)');
assertQuery(1, '#t > *:nth-child(n+12)');
assertQuery(10, '#t > *:nth-child(-n+10)');
assertQuery(5, '#t > *:nth-child(-2n+10)');
assertQuery(6, '#t > *:nth-child(2n+2)');
assertQuery(5, '#t > *:nth-child(2n+4)');
assertQuery(5, '#t > *:nth-child(2n+4)');
assertQuery(12, '#t > *:nth-child(n-5)');
assertQuery(6, '#t > *:nth-child(2n-5)');
}
function testEmptyPseudoSelector() {
assertQuery(4, '#t > span:empty');
assertQuery(6, '#t span:empty');
assertQuery(0, 'h3 span:empty');
assertQuery(1, 'h3 :not(:empty)');
}
function testIdsWithColons() {
assertQuery(1, '#silly\\:id\\:\\:with\\:colons');
}
function testOrder() {
var els = goog.dom.query('.myupperclass .myclass input');
assertEquals('myid1', els[0].id);
assertEquals('myid2', els[1].id);
}
function testCorrectDocumentInFrame() {
var frameDocument = window.frames['ifr'].document;
frameDocument.body.innerHTML =
document.getElementById('iframe-test').innerHTML;
var els = goog.dom.query('#if1 .if2 div', document);
var frameEls = goog.dom.query('#if1 .if2 div', frameDocument);
assertEquals(els.length, frameEls.length);
assertEquals(1, frameEls.length);
assertNotEquals(document.getElementById('if3'),
frameDocument.getElementById('if3'));
}
/**
* @param {number} expectedNumberOfNodes
* @param {...*} var_args
*/
function assertQuery(expectedNumberOfNodes, var_args) {
var args = Array.prototype.slice.call(arguments, 1);
assertEquals(expectedNumberOfNodes,
goog.dom.query.apply(null, args).length);
}

View File

@@ -0,0 +1,751 @@
/**
* @license
Copyright (c) 2008, Adobe Systems Incorporated
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* 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.
* Neither the name of Adobe Systems Incorporated nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
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 OWNER 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.
*/
/**
* @license
JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009
Basic GUI blocking jpeg encoder
v 0.9
*/
/**
* @fileoverview This code was ported from
* http://www.bytestrom.eu/blog/2009/1120a_jpeg_encoder_for_javascript and
* modified slightly for Closure.
*/
goog.provide('goog.crypt.JpegEncoder');
goog.require('goog.crypt.base64');
/**
* Initializes the JpegEncoder.
*
* @constructor
* @param {number=} opt_quality The compression quality. Default 50.
*/
goog.crypt.JpegEncoder = function(opt_quality) {
var self = this;
var fround = Math.round;
var ffloor = Math.floor;
var YTable = new Array(64);
var UVTable = new Array(64);
var fdtbl_Y = new Array(64);
var fdtbl_UV = new Array(64);
var YDC_HT;
var UVDC_HT;
var YAC_HT;
var UVAC_HT;
var bitcode = new Array(65535);
var category = new Array(65535);
var outputfDCTQuant = new Array(64);
var DU = new Array(64);
var byteout = [];
var bytenew = 0;
var bytepos = 7;
var YDU = new Array(64);
var UDU = new Array(64);
var VDU = new Array(64);
var clt = new Array(256);
var RGB_YUV_TABLE = new Array(2048);
var currentQuality;
var ZigZag = [
0, 1, 5, 6,14,15,27,28,
2, 4, 7,13,16,26,29,42,
3, 8,12,17,25,30,41,43,
9,11,18,24,31,40,44,53,
10,19,23,32,39,45,52,54,
20,22,33,38,46,51,55,60,
21,34,37,47,50,56,59,61,
35,36,48,49,57,58,62,63
];
var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0];
var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11];
var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d];
var std_ac_luminance_values = [
0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,
0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,
0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,
0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,
0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,
0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,
0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,
0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,
0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,
0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,
0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,
0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,
0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,
0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,
0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,
0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,
0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,
0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,
0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,
0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
0xf9,0xfa
];
var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0];
var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11];
var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77];
var std_ac_chrominance_values = [
0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,
0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,
0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,
0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,
0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,
0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,
0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,
0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,
0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,
0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,
0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,
0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,
0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,
0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,
0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,
0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,
0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,
0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,
0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,
0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
0xf9,0xfa
];
function initQuantTables(sf){
var YQT = [
16, 11, 10, 16, 24, 40, 51, 61,
12, 12, 14, 19, 26, 58, 60, 55,
14, 13, 16, 24, 40, 57, 69, 56,
14, 17, 22, 29, 51, 87, 80, 62,
18, 22, 37, 56, 68,109,103, 77,
24, 35, 55, 64, 81,104,113, 92,
49, 64, 78, 87,103,121,120,101,
72, 92, 95, 98,112,100,103, 99
];
for (var i = 0; i < 64; i++) {
var t = ffloor((YQT[i]*sf+50)/100);
if (t < 1) {
t = 1;
} else if (t > 255) {
t = 255;
}
YTable[ZigZag[i]] = t;
}
var UVQT = [
17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99
];
for (var j = 0; j < 64; j++) {
var u = ffloor((UVQT[j]*sf+50)/100);
if (u < 1) {
u = 1;
} else if (u > 255) {
u = 255;
}
UVTable[ZigZag[j]] = u;
}
var aasf = [
1.0, 1.387039845, 1.306562965, 1.175875602,
1.0, 0.785694958, 0.541196100, 0.275899379
];
var k = 0;
for (var row = 0; row < 8; row++)
{
for (var col = 0; col < 8; col++)
{
fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
k++;
}
}
}
function computeHuffmanTbl(nrcodes, std_table){
var codevalue = 0;
var pos_in_table = 0;
var HT = new Array();
for (var k = 1; k <= 16; k++) {
for (var j = 1; j <= nrcodes[k]; j++) {
HT[std_table[pos_in_table]] = [];
HT[std_table[pos_in_table]][0] = codevalue;
HT[std_table[pos_in_table]][1] = k;
pos_in_table++;
codevalue++;
}
codevalue*=2;
}
return HT;
}
function initHuffmanTbl()
{
YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values);
UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values);
YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values);
UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values);
}
function initCategoryNumber()
{
var nrlower = 1;
var nrupper = 2;
for (var cat = 1; cat <= 15; cat++) {
//Positive numbers
for (var nr = nrlower; nr<nrupper; nr++) {
category[32767+nr] = cat;
bitcode[32767+nr] = [];
bitcode[32767+nr][1] = cat;
bitcode[32767+nr][0] = nr;
}
//Negative numbers
for (var nrneg =-(nrupper-1); nrneg<=-nrlower; nrneg++) {
category[32767+nrneg] = cat;
bitcode[32767+nrneg] = [];
bitcode[32767+nrneg][1] = cat;
bitcode[32767+nrneg][0] = nrupper-1+nrneg;
}
nrlower <<= 1;
nrupper <<= 1;
}
}
function initRGBYUVTable() {
for(var i = 0; i < 256;i++) {
RGB_YUV_TABLE[i] = 19595 * i;
RGB_YUV_TABLE[(i+ 256)>>0] = 38470 * i;
RGB_YUV_TABLE[(i+ 512)>>0] = 7471 * i + 0x8000;
RGB_YUV_TABLE[(i+ 768)>>0] = -11059 * i;
RGB_YUV_TABLE[(i+1024)>>0] = -21709 * i;
RGB_YUV_TABLE[(i+1280)>>0] = 32768 * i + 0x807FFF;
RGB_YUV_TABLE[(i+1536)>>0] = -27439 * i;
RGB_YUV_TABLE[(i+1792)>>0] = - 5329 * i;
}
}
// IO functions
function writeBits(bs)
{
var value = bs[0];
var posval = bs[1]-1;
while ( posval >= 0 ) {
if (value & (1 << posval) ) {
bytenew |= (1 << bytepos);
}
posval--;
bytepos--;
if (bytepos < 0) {
if (bytenew == 0xFF) {
writeByte(0xFF);
writeByte(0);
}
else {
writeByte(bytenew);
}
bytepos=7;
bytenew=0;
}
}
}
function writeByte(value)
{
byteout.push(clt[value]); // write char directly instead of converting later
}
function writeWord(value)
{
writeByte((value>>8)&0xFF);
writeByte((value )&0xFF);
}
// DCT & quantization core
function fDCTQuant(data, fdtbl)
{
var d0, d1, d2, d3, d4, d5, d6, d7;
/* Pass 1: process rows. */
var dataOff=0;
var i;
var I8 = 8;
var I64 = 64;
for (i=0; i<I8; ++i)
{
d0 = data[dataOff];
d1 = data[dataOff+1];
d2 = data[dataOff+2];
d3 = data[dataOff+3];
d4 = data[dataOff+4];
d5 = data[dataOff+5];
d6 = data[dataOff+6];
d7 = data[dataOff+7];
var tmp0 = d0 + d7;
var tmp7 = d0 - d7;
var tmp1 = d1 + d6;
var tmp6 = d1 - d6;
var tmp2 = d2 + d5;
var tmp5 = d2 - d5;
var tmp3 = d3 + d4;
var tmp4 = d3 - d4;
/* Even part */
var tmp10 = tmp0 + tmp3; /* phase 2 */
var tmp13 = tmp0 - tmp3;
var tmp11 = tmp1 + tmp2;
var tmp12 = tmp1 - tmp2;
data[dataOff] = tmp10 + tmp11; /* phase 3 */
data[dataOff+4] = tmp10 - tmp11;
var z1 = (tmp12 + tmp13) * 0.707106781; /* c4 */
data[dataOff+2] = tmp13 + z1; /* phase 5 */
data[dataOff+6] = tmp13 - z1;
/* Odd part */
tmp10 = tmp4 + tmp5; /* phase 2 */
tmp11 = tmp5 + tmp6;
tmp12 = tmp6 + tmp7;
/* The rotator is modified from fig 4-8 to avoid extra negations. */
var z5 = (tmp10 - tmp12) * 0.382683433; /* c6 */
var z2 = 0.541196100 * tmp10 + z5; /* c2-c6 */
var z4 = 1.306562965 * tmp12 + z5; /* c2+c6 */
var z3 = tmp11 * 0.707106781; /* c4 */
var z11 = tmp7 + z3; /* phase 5 */
var z13 = tmp7 - z3;
data[dataOff+5] = z13 + z2; /* phase 6 */
data[dataOff+3] = z13 - z2;
data[dataOff+1] = z11 + z4;
data[dataOff+7] = z11 - z4;
dataOff += 8; /* advance pointer to next row */
}
/* Pass 2: process columns. */
dataOff = 0;
for (i=0; i<I8; ++i)
{
d0 = data[dataOff];
d1 = data[dataOff + 8];
d2 = data[dataOff + 16];
d3 = data[dataOff + 24];
d4 = data[dataOff + 32];
d5 = data[dataOff + 40];
d6 = data[dataOff + 48];
d7 = data[dataOff + 56];
var tmp0p2 = d0 + d7;
var tmp7p2 = d0 - d7;
var tmp1p2 = d1 + d6;
var tmp6p2 = d1 - d6;
var tmp2p2 = d2 + d5;
var tmp5p2 = d2 - d5;
var tmp3p2 = d3 + d4;
var tmp4p2 = d3 - d4;
/* Even part */
var tmp10p2 = tmp0p2 + tmp3p2; /* phase 2 */
var tmp13p2 = tmp0p2 - tmp3p2;
var tmp11p2 = tmp1p2 + tmp2p2;
var tmp12p2 = tmp1p2 - tmp2p2;
data[dataOff] = tmp10p2 + tmp11p2; /* phase 3 */
data[dataOff+32] = tmp10p2 - tmp11p2;
var z1p2 = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */
data[dataOff+16] = tmp13p2 + z1p2; /* phase 5 */
data[dataOff+48] = tmp13p2 - z1p2;
/* Odd part */
tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */
tmp11p2 = tmp5p2 + tmp6p2;
tmp12p2 = tmp6p2 + tmp7p2;
/* The rotator is modified from fig 4-8 to avoid extra negations. */
var z5p2 = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */
var z2p2 = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */
var z4p2 = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */
var z3p2 = tmp11p2 * 0.707106781; /* c4 */
var z11p2 = tmp7p2 + z3p2; /* phase 5 */
var z13p2 = tmp7p2 - z3p2;
data[dataOff+40] = z13p2 + z2p2; /* phase 6 */
data[dataOff+24] = z13p2 - z2p2;
data[dataOff+ 8] = z11p2 + z4p2;
data[dataOff+56] = z11p2 - z4p2;
dataOff++; /* advance pointer to next column */
}
// Quantize/descale the coefficients
var fDCTQuant;
for (i=0; i<I64; ++i)
{
// Apply the quantization and scaling factor & Round to nearest integer
fDCTQuant = data[i]*fdtbl[i];
outputfDCTQuant[i] = (fDCTQuant > 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0);
//outputfDCTQuant[i] = fround(fDCTQuant);
}
return outputfDCTQuant;
}
function writeAPP0()
{
writeWord(0xFFE0); // marker
writeWord(16); // length
writeByte(0x4A); // J
writeByte(0x46); // F
writeByte(0x49); // I
writeByte(0x46); // F
writeByte(0); // = "JFIF",'\0'
writeByte(1); // versionhi
writeByte(1); // versionlo
writeByte(0); // xyunits
writeWord(1); // xdensity
writeWord(1); // ydensity
writeByte(0); // thumbnwidth
writeByte(0); // thumbnheight
}
function writeSOF0(width, height)
{
writeWord(0xFFC0); // marker
writeWord(17); // length, truecolor YUV JPG
writeByte(8); // precision
writeWord(height);
writeWord(width);
writeByte(3); // nrofcomponents
writeByte(1); // IdY
writeByte(0x11); // HVY
writeByte(0); // QTY
writeByte(2); // IdU
writeByte(0x11); // HVU
writeByte(1); // QTU
writeByte(3); // IdV
writeByte(0x11); // HVV
writeByte(1); // QTV
}
function writeDQT()
{
writeWord(0xFFDB); // marker
writeWord(132); // length
writeByte(0);
for (var i=0; i<64; i++) {
writeByte(YTable[i]);
}
writeByte(1);
for (var j=0; j<64; j++) {
writeByte(UVTable[j]);
}
}
function writeDHT()
{
writeWord(0xFFC4); // marker
writeWord(0x01A2); // length
writeByte(0); // HTYDCinfo
for (var i=0; i<16; i++) {
writeByte(std_dc_luminance_nrcodes[i+1]);
}
for (var j=0; j<=11; j++) {
writeByte(std_dc_luminance_values[j]);
}
writeByte(0x10); // HTYACinfo
for (var k=0; k<16; k++) {
writeByte(std_ac_luminance_nrcodes[k+1]);
}
for (var l=0; l<=161; l++) {
writeByte(std_ac_luminance_values[l]);
}
writeByte(1); // HTUDCinfo
for (var m=0; m<16; m++) {
writeByte(std_dc_chrominance_nrcodes[m+1]);
}
for (var n=0; n<=11; n++) {
writeByte(std_dc_chrominance_values[n]);
}
writeByte(0x11); // HTUACinfo
for (var o=0; o<16; o++) {
writeByte(std_ac_chrominance_nrcodes[o+1]);
}
for (var p=0; p<=161; p++) {
writeByte(std_ac_chrominance_values[p]);
}
}
function writeSOS()
{
writeWord(0xFFDA); // marker
writeWord(12); // length
writeByte(3); // nrofcomponents
writeByte(1); // IdY
writeByte(0); // HTY
writeByte(2); // IdU
writeByte(0x11); // HTU
writeByte(3); // IdV
writeByte(0x11); // HTV
writeByte(0); // Ss
writeByte(0x3f); // Se
writeByte(0); // Bf
}
function processDU(CDU, fdtbl, DC, HTDC, HTAC){
var EOB = HTAC[0x00];
var M16zeroes = HTAC[0xF0];
var pos;
var I16 = 16;
var I63 = 63;
var I64 = 64;
var DU_DCT = fDCTQuant(CDU, fdtbl);
//ZigZag reorder
for (var j=0;j<I64;++j) {
DU[ZigZag[j]]=DU_DCT[j];
}
var Diff = DU[0] - DC; DC = DU[0];
//Encode DC
if (Diff==0) {
writeBits(HTDC[0]); // Diff might be 0
} else {
pos = 32767+Diff;
writeBits(HTDC[category[pos]]);
writeBits(bitcode[pos]);
}
//Encode ACs
var end0pos = 63; // was const... which is crazy
for (; (end0pos>0)&&(DU[end0pos]==0); end0pos--) {};
//end0pos = first element in reverse order !=0
if ( end0pos == 0) {
writeBits(EOB);
return DC;
}
var i = 1;
var lng;
while ( i <= end0pos ) {
var startpos = i;
for (; (DU[i]==0) && (i<=end0pos); ++i) {}
var nrzeroes = i-startpos;
if ( nrzeroes >= I16 ) {
lng = nrzeroes>>4;
for (var nrmarker=1; nrmarker <= lng; ++nrmarker)
writeBits(M16zeroes);
nrzeroes = nrzeroes&0xF;
}
pos = 32767+DU[i];
writeBits(HTAC[(nrzeroes<<4)+category[pos]]);
writeBits(bitcode[pos]);
i++;
}
if ( end0pos != I63 ) {
writeBits(EOB);
}
return DC;
}
function initCharLookupTable(){
var sfcc = String.fromCharCode;
for(var i=0; i < 256; i++){ ///// ACHTUNG // 255
clt[i] = sfcc(i);
}
}
/**
* Encodes ImageData to JPEG.
*
* @param {ImageData} image
* @param {number=} opt_quality The compression quality.
* @return {string} base64-encoded JPEG data.
*/
this.encode = function(image,opt_quality) // image data object
{
if(opt_quality) setQuality(opt_quality);
// Initialize bit writer
byteout = new Array();
bytenew=0;
bytepos=7;
// Add JPEG headers
writeWord(0xFFD8); // SOI
writeAPP0();
writeDQT();
writeSOF0(image.width,image.height);
writeDHT();
writeSOS();
// Encode 8x8 macroblocks
var _DCY=0;
var _DCU=0;
var _DCV=0;
bytenew=0;
bytepos=7;
this.encode.displayName = "_encode_";
var imageData = image.data;
var width = image.width;
var height = image.height;
var quadWidth = width*4;
var tripleWidth = width*3;
var x, y = 0;
var r, g, b;
var start,p, col,row,pos;
while(y < height){
x = 0;
while(x < quadWidth){
start = quadWidth * y + x;
p = start;
col = -1;
row = 0;
for(pos=0; pos < 64; pos++){
row = pos >> 3;// /8
col = ( pos & 7 ) * 4; // %8
p = start + ( row * quadWidth ) + col;
if(y+row >= height){ // padding bottom
p-= (quadWidth*(y+1+row-height));
}
if(x+col >= quadWidth){ // padding right
p-= ((x+col) - quadWidth +4)
}
r = imageData[ p++ ];
g = imageData[ p++ ];
b = imageData[ p++ ];
/* // calculate YUV values dynamically
YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80
UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b));
VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b));
*/
// use lookup table (slightly faster)
YDU[pos] = ((RGB_YUV_TABLE[r] + RGB_YUV_TABLE[(g + 256)>>0] + RGB_YUV_TABLE[(b + 512)>>0]) >> 16)-128;
UDU[pos] = ((RGB_YUV_TABLE[(r + 768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128;
VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128;
}
_DCY = processDU(YDU, fdtbl_Y, _DCY, YDC_HT, YAC_HT);
_DCU = processDU(UDU, fdtbl_UV, _DCU, UVDC_HT, UVAC_HT);
_DCV = processDU(VDU, fdtbl_UV, _DCV, UVDC_HT, UVAC_HT);
x+=32;
}
y+=8;
}
////////////////////////////////////////////////////////////////
// Do the bit alignment of the EOI marker
if ( bytepos >= 0 ) {
var fillbits = [];
fillbits[1] = bytepos+1;
fillbits[0] = (1<<(bytepos+1))-1;
writeBits(fillbits);
}
writeWord(0xFFD9); //EOI
var jpegDataUri = 'data:image/jpeg;base64,'
+ goog.crypt.base64.encodeString(byteout.join(''));
byteout = [];
return jpegDataUri
}
function setQuality(quality){
if (quality <= 0) {
quality = 1;
}
if (quality > 100) {
quality = 100;
}
if(currentQuality == quality) return // don't recalc if unchanged
var sf = 0;
if (quality < 50) {
sf = Math.floor(5000 / quality);
} else {
sf = Math.floor(200 - quality*2);
}
initQuantTables(sf);
currentQuality = quality;
}
function init(){
if(!opt_quality) opt_quality = 50;
// Create tables
initCharLookupTable()
initHuffmanTbl();
initCategoryNumber();
initRGBYUVTable();
setQuality(opt_quality);
}
init();
};

View File

@@ -0,0 +1,712 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
/**
* @fileoverview A generator of lorem ipsum text based on the python
* implementation at http://code.google.com/p/lorem-ipsum-generator/.
*
*/
goog.provide('goog.text.LoremIpsum');
goog.require('goog.array');
goog.require('goog.math');
goog.require('goog.string');
goog.require('goog.structs.Map');
goog.require('goog.structs.Set');
/**
* Generates random strings of "lorem ipsum" text, based on the word
* distribution of a sample text, using the words in a dictionary.
* @constructor
*/
goog.text.LoremIpsum = function() {
this.generateChains_(this.sample_);
this.generateStatistics_(this.sample_);
this.initializeDictionary_(this.dictionary_);
};
/**
* Delimiters that end sentences.
* @type {Array.<string>}
* @private
*/
goog.text.LoremIpsum.DELIMITERS_SENTENCES_ = ['.', '?', '!'];
/**
* Regular expression for spliting a text into sentences.
* @type {RegExp}
* @private
*/
goog.text.LoremIpsum.SENTENCE_SPLIT_REGEX_ = /[\.\?\!]/;
/**
* Delimiters that end words.
* @type {Array.<string>}
* @private
*/
goog.text.LoremIpsum.DELIMITERS_WORDS_ = [',', '.', '?', '!'];
/**
* Regular expression for spliting text into words.
* @type {RegExp}
* @private
*/
goog.text.LoremIpsum.WORD_SPLIT_REGEX_ = /\s/;
/**
* Words that can be used in the generated output.
* Maps a word-length to a list of words of that length.
* @type {goog.structs.Map}
* @private
*/
goog.text.LoremIpsum.prototype.words_;
/**
* Chains of three words that appear in the sample text
* Maps a pair of word-lengths to a third word-length and an optional
* piece of trailing punctuation (for example, a period, comma, etc.).
* @type {goog.structs.Map}
* @private
*/
goog.text.LoremIpsum.prototype.chains_;
/**
* Pairs of word-lengths that can appear at the beginning of sentences.
* @type {Array}
*/
goog.text.LoremIpsum.prototype.starts_;
/**
* Averange sentence length in words.
* @type {number}
* @private
*/
goog.text.LoremIpsum.prototype.sentenceMean_;
/**
* Sigma (sqrt of variance) for the sentence length in words.
* @type {number}
* @private
*/
goog.text.LoremIpsum.prototype.sentenceSigma_;
/**
* Averange paragraph length in sentences.
* @type {number}
* @private
*/
goog.text.LoremIpsum.prototype.paragraphMean_;
/**
* Sigma (sqrt of variance) for the paragraph length in sentences.
* @type {number}
* @private
*/
goog.text.LoremIpsum.prototype.paragraphSigma_;
/**
* Generates the chains and starts values required for sentence generation.
* @param {string} sample The same text.
* @private
*/
goog.text.LoremIpsum.prototype.generateChains_ = function(sample) {
var words = goog.text.LoremIpsum.splitWords_(sample);
var wordInfo = goog.array.map(words, goog.text.LoremIpsum.getWordInfo_);
var previous = [0, 0];
var previousKey = previous.join('-');
var chains = new goog.structs.Map();
var starts = [previousKey];
var chainKeys = {};
goog.array.forEach(wordInfo, function(pair) {
var chain = chains.get(previousKey);
if (chain) {
chain.push(pair);
} else {
chain = [pair];
chains.set(previousKey, chain);
}
if (goog.array.contains(
goog.text.LoremIpsum.DELIMITERS_SENTENCES_, pair[1])) {
starts.push(previousKey);
}
chainKeys[previousKey] = previous;
previous = [previous[1], pair[0]];
previousKey = previous.join('-');
});
if (chains.getCount() > 0) {
this.chains_ = chains;
this.starts_ = starts;
this.chainKeys_ = chainKeys;
} else {
throw Error('Could not generate chains from sample text.');
}
};
/**
* Calculates the mean and standard deviation of sentence and paragraph lengths.
* @param {string} sample The same text.
* @private
*/
goog.text.LoremIpsum.prototype.generateStatistics_ = function(sample) {
this.generateSentenceStatistics_(sample);
this.generateParagraphStatistics_(sample);
};
/**
* Calculates the mean and standard deviation of the lengths of sentences
* (in words) in a sample text.
* @param {string} sample The same text.
* @private
*/
goog.text.LoremIpsum.prototype.generateSentenceStatistics_ = function(sample) {
var sentences = goog.array.filter(
goog.text.LoremIpsum.splitSentences_(sample),
goog.text.LoremIpsum.isNotEmptyOrWhitepace_);
var sentenceLengths = goog.array.map(
goog.array.map(sentences, goog.text.LoremIpsum.splitWords_),
goog.text.LoremIpsum.arrayLength_);
this.sentenceMean_ = goog.math.average.apply(null, sentenceLengths);
this.sentenceSigma_ = goog.math.standardDeviation.apply(
null, sentenceLengths);
};
/**
* Calculates the mean and standard deviation of the lengths of paragraphs
* (in sentences) in a sample text.
* @param {string} sample The same text.
* @private
*/
goog.text.LoremIpsum.prototype.generateParagraphStatistics_ = function(sample) {
var paragraphs = goog.array.filter(
goog.text.LoremIpsum.splitParagraphs_(sample),
goog.text.LoremIpsum.isNotEmptyOrWhitepace_);
var paragraphLengths = goog.array.map(
goog.array.map(paragraphs, goog.text.LoremIpsum.splitSentences_),
goog.text.LoremIpsum.arrayLength_);
this.paragraphMean_ = goog.math.average.apply(null, paragraphLengths);
this.paragraphSigma_ = goog.math.standardDeviation.apply(
null, paragraphLengths);
};
/**
* Sets the generator to use a given selection of words for generating
* sentences with.
* @param {string} dictionary The dictionary to use.
*/
goog.text.LoremIpsum.prototype.initializeDictionary_ = function(dictionary) {
var dictionaryWords = goog.text.LoremIpsum.splitWords_(dictionary);
var words = new goog.structs.Map();
goog.array.forEach(dictionaryWords, function(word) {
var set = words.get(word.length);
if (!set) {
set = new goog.structs.Set();
words.set(word.length, set);
}
set.add(word);
});
this.words_ = words;
};
/**
* Picks a random starting chain.
* @return {Array.<string>} The starting key.
* @private
*/
goog.text.LoremIpsum.prototype.chooseRandomStart_ = function() {
var key = goog.text.LoremIpsum.randomChoice_(this.starts_);
return this.chainKeys_[key];
};
/**
* Generates a single sentence, of random length.
* @param {boolean} opt_startWithLorem Whether to start the setnence with the
* standard "Lorem ipsum..." first sentence.
* @return {string} The generated sentence.
*/
goog.text.LoremIpsum.prototype.generateSentence = function(opt_startWithLorem) {
if (this.chains_.getCount() == 0 || this.starts_.length == 0) {
throw Error('No chains created');
}
if (this.words_.getCount() == 0) {
throw Error('No dictionary');
}
// The length of the sentence is a normally distributed random variable.
var sentenceLength = goog.text.LoremIpsum.randomNormal_(
this.sentenceMean_, this.sentenceSigma_)
sentenceLength = Math.max(Math.floor(sentenceLength), 1);
var wordDelimiter = ''; // Defined here in case while loop doesn't run
// Start the sentence with "Lorem ipsum...", if desired
var sentence;
if (opt_startWithLorem) {
var lorem = 'lorem ipsum dolor sit amet, consecteteur adipiscing elit';
sentence = goog.text.LoremIpsum.splitWords_(lorem);
if (sentence.length > sentenceLength) {
sentence.length = sentenceLength;
}
var lastWord = sentence[sentence.length - 1];
var lastChar = lastWord.substring(lastWord.length - 1);
if (goog.array.contains(goog.text.LoremIpsum.DELIMITERS_WORDS_, lastChar)) {
wordDelimiter = lastChar;
}
} else {
sentence = [];
}
var previous = [];
var previousKey = '';
// Generate a sentence from the "chains"
while (sentence.length < sentenceLength) {
// If the current starting point is invalid, choose another randomly
if (!this.chains_.containsKey(previousKey)) {
previous = this.chooseRandomStart_();
previousKey = previous.join('-');
}
// Choose the next "chain" to go to. This determines the next word
// length we'll use, and whether there is e.g. a comma at the end of
// the word.
var chain = /** @type {Array} */ (goog.text.LoremIpsum.randomChoice_(
/** @type {Array} */ (this.chains_.get(previousKey))));
var wordLength = chain[0];
// If the word delimiter contained in the chain is also a sentence
// delimiter, then we don't include it because we don't want the
// sentence to end prematurely (we want the length to match the
// sentence_length value).
//debugger;
if (goog.array.contains(goog.text.LoremIpsum.DELIMITERS_SENTENCES_,
chain[1])) {
wordDelimiter = '';
} else {
wordDelimiter = chain[1];
}
// Choose a word randomly that matches (or closely matches) the
// length we're after.
var closestLength = goog.text.LoremIpsum.chooseClosest(
this.words_.getKeys(), wordLength);
var word = goog.text.LoremIpsum.randomChoice_(
this.words_.get(closestLength).getValues());
sentence.push(word + wordDelimiter);
previous = [previous[1], wordLength];
previousKey = previous.join('-');
}
// Finish the sentence off with capitalisation, a period and
// form it into a string
sentence = sentence.join(' ');
sentence = sentence.slice(0, 1).toUpperCase() + sentence.slice(1);
if (sentence.substring(sentence.length - 1) == wordDelimiter) {
sentence = sentence.slice(0, sentence.length - 1);
}
return sentence + '.';
};
/**
* Generates a single lorem ipsum paragraph, of random length.
* @param {boolean} opt_startWithLorem Whether to start the sentence with the
* standard "Lorem ipsum..." first sentence.
* @return {string} The generated sentence.
*/
goog.text.LoremIpsum.prototype.generateParagraph = function(
opt_startWithLorem) {
// The length of the paragraph is a normally distributed random variable.
var paragraphLength = goog.text.LoremIpsum.randomNormal_(
this.paragraphMean_, this.paragraphSigma_);
paragraphLength = Math.max(Math.floor(paragraphLength), 1);
// Construct a paragraph from a number of sentences.
var paragraph = []
var startWithLorem = opt_startWithLorem;
while (paragraph.length < paragraphLength) {
var sentence = this.generateSentence(startWithLorem);
paragraph.push(sentence);
startWithLorem = false;
}
// Form the paragraph into a string.
paragraph = paragraph.join(' ')
return paragraph
};
/**
* Splits a piece of text into paragraphs.
* @param {string} text The text to split.
* @return {Array.<string>} An array of paragraphs.
* @private
*/
goog.text.LoremIpsum.splitParagraphs_ = function(text) {
return text.split('\n')
};
/**
* Splits a piece of text into sentences.
* @param {string} text The text to split.
* @return {Array.<string>} An array of sentences.
* @private
*/
goog.text.LoremIpsum.splitSentences_ = function(text) {
return goog.array.filter(
text.split(goog.text.LoremIpsum.SENTENCE_SPLIT_REGEX_),
goog.text.LoremIpsum.isNotEmptyOrWhitepace_);
};
/**
* Splits a piece of text into words..
* @param {string} text The text to split.
* @return {Array.<string>} An array of words.
* @private
*/
goog.text.LoremIpsum.splitWords_ = function(text) {
return goog.array.filter(
text.split(goog.text.LoremIpsum.WORD_SPLIT_REGEX_),
goog.text.LoremIpsum.isNotEmptyOrWhitepace_);
};
/**
* Returns the text is not empty or just whitespace.
* @param {string} text The text to check.
* @return {boolean} Whether the text is nether empty nor whitespace.
* @private
*/
goog.text.LoremIpsum.isNotEmptyOrWhitepace_ = function(text) {
return goog.string.trim(text).length > 0;
};
/**
* Returns the length of an array. Written as a function so it can be used
* as a function parameter.
* @param {Array} array The array to check.
* @return {number} The length of the array.
*/
goog.text.LoremIpsum.arrayLength_ = function(array) {
return array.length;
};
/**
* Find the number in the list of values that is closest to the target.
* @param {Array.<number>} values The values.
* @param {number} target The target value.
* @return {number} The closest value.
*/
goog.text.LoremIpsum.chooseClosest = function(values, target) {
var closest = values[0];
goog.array.forEach(values, function(value) {
if (Math.abs(target - value) < Math.abs(target - closest)) {
closest = value;
}
});
return closest;
};
/**
* Gets info about a word used as part of the lorem ipsum algorithm.
* @param {string} word The word to check.
* @return {Array} A two element array. The first element is the size of the
* word. The second element is the delimter used in the word.
* @private
*/
goog.text.LoremIpsum.getWordInfo_ = function(word) {
var ret;
goog.array.some(goog.text.LoremIpsum.DELIMITERS_WORDS_,
function (delimiter) {
if (goog.string.endsWith(word, delimiter)) {
ret = [word.length - delimiter.length, delimiter];
return true;
}
return false;
}
);
return ret || [word.length, ''];
};
/**
* Constant used for {@link #randomNormal_}.
* @type {number}
* @private
*/
goog.text.LoremIpsum.NV_MAGICCONST_ = 4 * Math.exp(-0.5) / Math.sqrt(2.0);
/**
* Generates a random number for a normal distribution with the specified
* mean and sigma.
* @param {number} mu The mean of the distribution.
* @param {number} sigma The sigma of the distribution.
* @private
*/
goog.text.LoremIpsum.randomNormal_ = function(mu, sigma) {
while (true) {
var u1 = Math.random();
var u2 = 1.0 - Math.random();
var z = goog.text.LoremIpsum.NV_MAGICCONST_ * (u1 - 0.5) / u2;
var zz = z * z / 4.0;
if (zz <= -Math.log(u2)) {
break;
}
}
return mu + z * sigma;
};
/**
* Picks a random element of the array.
* @param {Array} array The array to pick from.
* @return {*} An element from the array.
*/
goog.text.LoremIpsum.randomChoice_ = function(array) {
return array[goog.math.randomInt(array.length)];
};
/**
* Dictionary of words for lorem ipsum.
* @type {string}
* @private
*/
goog.text.LoremIpsum.DICT_ =
'a ac accumsan ad adipiscing aenean aliquam aliquet amet ante ' +
'aptent arcu at auctor augue bibendum blandit class commodo ' +
'condimentum congue consectetuer consequat conubia convallis cras ' +
'cubilia cum curabitur curae cursus dapibus diam dictum dictumst ' +
'dignissim dis dolor donec dui duis egestas eget eleifend elementum ' +
'elit eni enim erat eros est et etiam eu euismod facilisi facilisis ' +
'fames faucibus felis fermentum feugiat fringilla fusce gravida ' +
'habitant habitasse hac hendrerit hymenaeos iaculis id imperdiet ' +
'in inceptos integer interdum ipsum justo lacinia lacus laoreet ' +
'lectus leo libero ligula litora lobortis lorem luctus maecenas ' +
'magna magnis malesuada massa mattis mauris metus mi molestie ' +
'mollis montes morbi mus nam nascetur natoque nec neque netus ' +
'nibh nisi nisl non nonummy nostra nulla nullam nunc odio orci ' +
'ornare parturient pede pellentesque penatibus per pharetra ' +
'phasellus placerat platea porta porttitor posuere potenti praesent ' +
'pretium primis proin pulvinar purus quam quis quisque rhoncus ' +
'ridiculus risus rutrum sagittis sapien scelerisque sed sem semper ' +
'senectus sit sociis sociosqu sodales sollicitudin suscipit ' +
'suspendisse taciti tellus tempor tempus tincidunt torquent tortor ' +
'tristique turpis ullamcorper ultrices ultricies urna ut varius ve ' +
'vehicula vel velit venenatis vestibulum vitae vivamus viverra ' +
'volutpat vulputate';
/**
* A sample to use for generating the distribution of word and sentence lengths
* in lorem ipsum.
* @type {string}
* @private
*/
goog.text.LoremIpsum.SAMPLE_ =
'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean ' +
'commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus ' +
'et magnis dis parturient montes, nascetur ridiculus mus. Donec quam ' +
'felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla ' +
'consequat massa quis enim. Donec pede justo, fringilla vel, aliquet ' +
'nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, ' +
'venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. ' +
'Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean ' +
'vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat ' +
'vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra ' +
'quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius ' +
'laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel ' +
'augue. Curabitur ullamcorper ultricies nisi. Nam eget dui.\n\n' +
'Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem ' +
'quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam ' +
'nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec ' +
'odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis ' +
'faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus ' +
'tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales ' +
'sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit ' +
'cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend ' +
'sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, ' +
'metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis ' +
'hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci ' +
'luctus et ultrices posuere cubilia Curae; In ac dui quis mi ' +
'consectetuer lacinia.\n\n' +
'Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet ' +
'nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ' +
'ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent ' +
'adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy ' +
'metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros ' +
'et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, ' +
'nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit ' +
'risus. Phasellus nec sem in justo pellentesque facilisis. Etiam ' +
'imperdiet imperdiet orci. Nunc nec neque. Phasellus leo dolor, tempus ' +
'non, auctor et, hendrerit quis, nisi.\n\n' +
'Curabitur ligula sapien, tincidunt non, euismod vitae, posuere ' +
'imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed ' +
'cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus ' +
'accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci ' +
'luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis ' +
'porttitor congue, elit erat euismod orci, ac placerat dolor lectus quis ' +
'orci. Phasellus consectetuer vestibulum elit. Aenean tellus metus, ' +
'bibendum sed, posuere ac, mattis non, nunc. Vestibulum fringilla pede ' +
'sit amet augue. In turpis. Pellentesque posuere. Praesent turpis.\n\n' +
'Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, eu ' +
'sollicitudin urna dolor sagittis lacus. Donec elit libero, sodales ' +
'nec, volutpat a, suscipit non, turpis. Nullam sagittis. Suspendisse ' +
'pulvinar, augue ac venenatis condimentum, sem libero volutpat nibh, ' +
'nec pellentesque velit pede quis nunc. Vestibulum ante ipsum primis in ' +
'faucibus orci luctus et ultrices posuere cubilia Curae; Fusce id ' +
'purus. Ut varius tincidunt libero. Phasellus dolor. Maecenas vestibulum ' +
'mollis diam. Pellentesque ut neque. Pellentesque habitant morbi ' +
'tristique senectus et netus et malesuada fames ac turpis egestas.\n\n' +
'In dui magna, posuere eget, vestibulum et, tempor auctor, justo. In ac ' +
'felis quis tortor malesuada pretium. Pellentesque auctor neque nec ' +
'urna. Proin sapien ipsum, porta a, auctor quis, euismod ut, mi. Aenean ' +
'viverra rhoncus pede. Pellentesque habitant morbi tristique senectus et ' +
'netus et malesuada fames ac turpis egestas. Ut non enim eleifend felis ' +
'pretium feugiat. Vivamus quis mi. Phasellus a est. Phasellus magna.\n\n' +
'In hac habitasse platea dictumst. Curabitur at lacus ac velit ornare ' +
'lobortis. Curabitur a felis in nunc fringilla tristique. Morbi mattis ' +
'ullamcorper velit. Phasellus gravida semper nisi. Nullam vel sem. ' +
'Pellentesque libero tortor, tincidunt et, tincidunt eget, semper nec, ' +
'quam. Sed hendrerit. Morbi ac felis. Nunc egestas, augue at ' +
'pellentesque laoreet, felis eros vehicula leo, at malesuada velit leo ' +
'quis pede. Donec interdum, metus et hendrerit aliquet, dolor diam ' +
'sagittis ligula, eget egestas libero turpis vel mi. Nunc nulla. Fusce ' +
'risus nisl, viverra et, tempor et, pretium in, sapien. Donec venenatis ' +
'vulputate lorem.\n\n' +
'Morbi nec metus. Phasellus blandit leo ut odio. Maecenas ullamcorper, ' +
'dui et placerat feugiat, eros pede varius nisi, condimentum viverra ' +
'felis nunc et lorem. Sed magna purus, fermentum eu, tincidunt eu, ' +
'varius ut, felis. In auctor lobortis lacus. Quisque libero metus, ' +
'condimentum nec, tempor a, commodo mollis, magna. Vestibulum ' +
'ullamcorper mauris at ligula. Fusce fermentum. Nullam cursus lacinia ' +
'erat. Praesent blandit laoreet nibh.\n\n' +
'Fusce convallis metus id felis luctus adipiscing. Pellentesque egestas, ' +
'neque sit amet convallis pulvinar, justo nulla eleifend augue, ac ' +
'auctor orci leo non est. Quisque id mi. Ut tincidunt tincidunt erat. ' +
'Etiam feugiat lorem non metus. Vestibulum dapibus nunc ac augue. ' +
'Curabitur vestibulum aliquam leo. Praesent egestas neque eu enim. In ' +
'hac habitasse platea dictumst. Fusce a quam. Etiam ut purus mattis ' +
'mauris sodales aliquam. Curabitur nisi. Quisque malesuada placerat ' +
'nisl. Nam ipsum risus, rutrum vitae, vestibulum eu, molestie vel, ' +
'lacus.\n\n' +
'Sed augue ipsum, egestas nec, vestibulum et, malesuada adipiscing, ' +
'dui. Vestibulum facilisis, purus nec pulvinar iaculis, ligula mi ' +
'congue nunc, vitae euismod ligula urna in dolor. Mauris sollicitudin ' +
'fermentum libero. Praesent nonummy mi in odio. Nunc interdum lacus sit ' +
'amet orci. Vestibulum rutrum, mi nec elementum vehicula, eros quam ' +
'gravida nisl, id fringilla neque ante vel mi. Morbi mollis tellus ac ' +
'sapien. Phasellus volutpat, metus eget egestas mollis, lacus lacus ' +
'blandit dui, id egestas quam mauris ut lacus. Fusce vel dui. Sed in ' +
'libero ut nibh placerat accumsan. Proin faucibus arcu quis ante. In ' +
'consectetuer turpis ut velit. Nulla sit amet est. Praesent metus ' +
'tellus, elementum eu, semper a, adipiscing nec, purus. Cras risus ' +
'ipsum, faucibus ut, ullamcorper id, varius ac, leo. Suspendisse ' +
'feugiat. Suspendisse enim turpis, dictum sed, iaculis a, condimentum ' +
'nec, nisi. Praesent nec nisl a purus blandit viverra. Praesent ac ' +
'massa at ligula laoreet iaculis. Nulla neque dolor, sagittis eget, ' +
'iaculis quis, molestie non, velit.\n\n' +
'Mauris turpis nunc, blandit et, volutpat molestie, porta ut, ligula. ' +
'Fusce pharetra convallis urna. Quisque ut nisi. Donec mi odio, faucibus ' +
'at, scelerisque quis, convallis in, nisi. Suspendisse non nisl sit amet ' +
'velit hendrerit rutrum. Ut leo. Ut a nisl id ante tempus hendrerit. ' +
'Proin pretium, leo ac pellentesque mollis, felis nunc ultrices eros, ' +
'sed gravida augue augue mollis justo. Suspendisse eu ligula. Nulla ' +
'facilisi. Donec id justo. Praesent porttitor, nulla vitae posuere ' +
'iaculis, arcu nisl dignissim dolor, a pretium mi sem ut ipsum. ' +
'Curabitur suscipit suscipit tellus.\n\n' +
'Praesent vestibulum dapibus nibh. Etiam iaculis nunc ac metus. Ut id ' +
'nisl quis enim dignissim sagittis. Etiam sollicitudin, ipsum eu ' +
'pulvinar rutrum, tellus ipsum laoreet sapien, quis venenatis ante ' +
'odio sit amet eros. Proin magna. Duis vel nibh at velit scelerisque ' +
'suscipit. Curabitur turpis. Vestibulum suscipit nulla quis orci. Fusce ' +
'ac felis sit amet ligula pharetra condimentum. Maecenas egestas arcu ' +
'quis ligula mattis placerat. Duis lobortis massa imperdiet quam. ' +
'Suspendisse potenti.\n\n' +
'Pellentesque commodo eros a enim. Vestibulum turpis sem, aliquet eget, ' +
'lobortis pellentesque, rutrum eu, nisl. Sed libero. Aliquam erat ' +
'volutpat. Etiam vitae tortor. Morbi vestibulum volutpat enim. Aliquam ' +
'eu nunc. Nunc sed turpis. Sed mollis, eros et ultrices tempus, mauris ' +
'ipsum aliquam libero, non adipiscing dolor urna a orci. Nulla porta ' +
'dolor. Class aptent taciti sociosqu ad litora torquent per conubia ' +
'nostra, per inceptos hymenaeos.\n\n' +
'Pellentesque dapibus hendrerit tortor. Praesent egestas tristique nibh. ' +
'Sed a libero. Cras varius. Donec vitae orci sed dolor rutrum auctor. ' +
'Fusce egestas elit eget lorem. Suspendisse nisl elit, rhoncus eget, ' +
'elementum ac, condimentum eget, diam. Nam at tortor in tellus interdum ' +
'sagittis. Aliquam lobortis. Donec orci lectus, aliquam ut, faucibus ' +
'non, euismod id, nulla. Curabitur blandit mollis lacus. Nam adipiscing. ' +
'Vestibulum eu odio.\n\n' +
'Vivamus laoreet. Nullam tincidunt adipiscing enim. Phasellus tempus. ' +
'Proin viverra, ligula sit amet ultrices semper, ligula arcu tristique ' +
'sapien, a accumsan nisi mauris ac eros. Fusce neque. Suspendisse ' +
'faucibus, nunc et pellentesque egestas, lacus ante convallis tellus, ' +
'vitae iaculis lacus elit id tortor. Vivamus aliquet elit ac nisl. Fusce ' +
'fermentum odio nec arcu. Vivamus euismod mauris. In ut quam vitae ' +
'odio lacinia tincidunt. Praesent ut ligula non mi varius sagittis. ' +
'Cras sagittis. Praesent ac sem eget est egestas volutpat. Vivamus ' +
'consectetuer hendrerit lacus. Cras non dolor. Vivamus in erat ut urna ' +
'cursus vestibulum. Fusce commodo aliquam arcu. Nam commodo suscipit ' +
'quam. Quisque id odio. Praesent venenatis metus at tortor pulvinar ' +
'varius.\n\n';
/**
* Sample that the generated text is based on .
* @type {string}
*/
goog.text.LoremIpsum.prototype.sample_ = goog.text.LoremIpsum.SAMPLE_;
/**
* Dictionary of words.
* @type {string}
*/
goog.text.LoremIpsum.prototype.dictionary_ = goog.text.LoremIpsum.DICT_;

View File

@@ -0,0 +1,764 @@
// Copyright 2007 Bob Ippolito. All Rights Reserved.
// Modifications Copyright 2009 The Closure Library Authors. All Rights
// Reserved.
/**
* @license Portions of this code are from MochiKit, received by
* The Closure Authors under the MIT license. All other code is Copyright
* 2005-2009 The Closure Authors. All Rights Reserved.
*/
/**
* @fileoverview Classes for tracking asynchronous operations and handling the
* results. The Deferred object here is patterned after the Deferred object in
* the Twisted python networking framework.
*
* See: http://twistedmatrix.com/projects/core/documentation/howto/defer.html
*
* Based on the Dojo code which in turn is based on the MochiKit code.
*
*/
goog.provide('goog.async.Deferred');
goog.provide('goog.async.Deferred.AlreadyCalledError');
goog.provide('goog.async.Deferred.CanceledError');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.debug.Error');
goog.require('goog.functions');
/**
* A Deferred represents the result of an asynchronous operation. A Deferred
* instance has no result when it is created, and is "fired" (given an initial
* result) by calling {@code callback} or {@code errback}.
*
* Once fired, the result is passed through a sequence of callback functions
* registered with {@code addCallback} or {@code addErrback}. The functions may
* mutate the result before it is passed to the next function in the sequence.
*
* Callbacks and errbacks may be added at any time, including after the Deferred
* has been "fired". If there are no pending actions in the execution sequence
* of a fired Deferred, any new callback functions will be called with the last
* computed result. Adding a callback function is the only way to access the
* result of the Deferred.
*
* If a Deferred operation is canceled, an optional user-provided cancellation
* function is invoked which may perform any special cleanup, followed by firing
* the Deferred's errback sequence with a {@code CanceledError}. If the
* Deferred has already fired, cancellation is ignored.
*
* @param {Function=} opt_onCancelFunction A function that will be called if the
* Deferred is canceled. If provided, this function runs before the
* Deferred is fired with a {@code CanceledError}.
* @param {Object=} opt_defaultScope The default object context to call
* callbacks and errbacks in.
* @constructor
*/
goog.async.Deferred = function(opt_onCancelFunction, opt_defaultScope) {
/**
* Entries in the sequence are arrays containing a callback, an errback, and
* an optional scope. The callback or errback in an entry may be null.
* @type {!Array.<!Array>}
* @private
*/
this.sequence_ = [];
/**
* Optional function that will be called if the Deferred is canceled.
* @type {Function|undefined}
* @private
*/
this.onCancelFunction_ = opt_onCancelFunction;
/**
* The default scope to execute callbacks and errbacks in.
* @type {Object}
* @private
*/
this.defaultScope_ = opt_defaultScope || null;
if (goog.async.Deferred.LONG_STACK_TRACES) {
/**
* Holds the stack trace at time of deferred creation if the JS engine
* provides the Error.captureStackTrace API.
* @private {?string}
*/
this.constructorStack_ = null;
if (Error.captureStackTrace) {
var target = { stack: '' };
Error.captureStackTrace(target, goog.async.Deferred);
// Check if Error.captureStackTrace worked. It fails in gjstest.
if (typeof target.stack == 'string') {
// Remove first line and force stringify to prevent memory leak due to
// holding on to actual stack frames.
this.constructorStack_ = target.stack.replace(/^[^\n]*\n/, '');
}
}
}
};
/**
* Whether the Deferred has been fired.
* @type {boolean}
* @private
*/
goog.async.Deferred.prototype.fired_ = false;
/**
* Whether the last result in the execution sequence was an error.
* @type {boolean}
* @private
*/
goog.async.Deferred.prototype.hadError_ = false;
/**
* The current Deferred result, updated as callbacks and errbacks are executed.
* @type {*}
* @private
*/
goog.async.Deferred.prototype.result_;
/**
* Whether the Deferred is blocked waiting on another Deferred to fire. If a
* callback or errback returns a Deferred as a result, the execution sequence is
* blocked until that Deferred result becomes available.
* @type {boolean}
* @private
*/
goog.async.Deferred.prototype.blocked_ = false;
/**
* Whether this Deferred is blocking execution of another Deferred. If this
* instance was returned as a result in another Deferred's execution sequence,
* that other Deferred becomes blocked until this instance's execution sequence
* completes. No additional callbacks may be added to a Deferred once it
* is blocking another instance.
* @type {boolean}
* @private
*/
goog.async.Deferred.prototype.blocking_ = false;
/**
* Whether the Deferred has been canceled without having a custom cancel
* function.
* @type {boolean}
* @private
*/
goog.async.Deferred.prototype.silentlyCanceled_ = false;
/**
* If an error is thrown during Deferred execution with no errback to catch it,
* the error is rethrown after a timeout. Reporting the error after a timeout
* allows execution to continue in the calling context.
* @type {number}
* @private
*/
goog.async.Deferred.prototype.unhandledExceptionTimeoutId_;
/**
* If this Deferred was created by branch(), this will be the "parent" Deferred.
* @type {goog.async.Deferred}
* @private
*/
goog.async.Deferred.prototype.parent_;
/**
* The number of Deferred objects that have been branched off this one. This
* will be decremented whenever a branch is fired or canceled.
* @type {number}
* @private
*/
goog.async.Deferred.prototype.branches_ = 0;
/**
* @define {boolean} Whether unhandled errors should always get rethrown to the
* global scope. Defaults to the value of goog.DEBUG.
*/
goog.define('goog.async.Deferred.STRICT_ERRORS', false);
/**
* @define {boolean} Whether to attempt to make stack traces long. Defaults to
* the value of goog.DEBUG.
*/
goog.define('goog.async.Deferred.LONG_STACK_TRACES', goog.DEBUG);
/**
* Cancels a Deferred that has not yet been fired, or is blocked on another
* deferred operation. If this Deferred is waiting for a blocking Deferred to
* fire, the blocking Deferred will also be canceled.
*
* If this Deferred was created by calling branch() on a parent Deferred with
* opt_propagateCancel set to true, the parent may also be canceled. If
* opt_deepCancel is set, cancel() will be called on the parent (as well as any
* other ancestors if the parent is also a branch). If one or more branches were
* created with opt_propagateCancel set to true, the parent will be canceled if
* cancel() is called on all of those branches.
*
* @param {boolean=} opt_deepCancel If true, cancels this Deferred's parent even
* if cancel() hasn't been called on some of the parent's branches. Has no
* effect on a branch without opt_propagateCancel set to true.
*/
goog.async.Deferred.prototype.cancel = function(opt_deepCancel) {
if (!this.hasFired()) {
if (this.parent_) {
// Get rid of the parent reference before potentially running the parent's
// canceler function to ensure that this cancellation isn't
// double-counted.
var parent = this.parent_;
delete this.parent_;
if (opt_deepCancel) {
parent.cancel(opt_deepCancel);
} else {
parent.branchCancel_();
}
}
if (this.onCancelFunction_) {
// Call in user-specified scope.
this.onCancelFunction_.call(this.defaultScope_, this);
} else {
this.silentlyCanceled_ = true;
}
if (!this.hasFired()) {
this.errback(new goog.async.Deferred.CanceledError(this));
}
} else if (this.result_ instanceof goog.async.Deferred) {
this.result_.cancel();
}
};
/**
* Handle a single branch being canceled. Once all branches are canceled, this
* Deferred will be canceled as well.
*
* @private
*/
goog.async.Deferred.prototype.branchCancel_ = function() {
this.branches_--;
if (this.branches_ <= 0) {
this.cancel();
}
};
/**
* Called after a blocking Deferred fires. Unblocks this Deferred and resumes
* its execution sequence.
*
* @param {boolean} isSuccess Whether the result is a success or an error.
* @param {*} res The result of the blocking Deferred.
* @private
*/
goog.async.Deferred.prototype.continue_ = function(isSuccess, res) {
this.blocked_ = false;
this.updateResult_(isSuccess, res);
};
/**
* Updates the current result based on the success or failure of the last action
* in the execution sequence.
*
* @param {boolean} isSuccess Whether the new result is a success or an error.
* @param {*} res The result.
* @private
*/
goog.async.Deferred.prototype.updateResult_ = function(isSuccess, res) {
this.fired_ = true;
this.result_ = res;
this.hadError_ = !isSuccess;
this.fire_();
};
/**
* Verifies that the Deferred has not yet been fired.
*
* @private
* @throws {Error} If this has already been fired.
*/
goog.async.Deferred.prototype.check_ = function() {
if (this.hasFired()) {
if (!this.silentlyCanceled_) {
throw new goog.async.Deferred.AlreadyCalledError(this);
}
this.silentlyCanceled_ = false;
}
};
/**
* Fire the execution sequence for this Deferred by passing the starting result
* to the first registered callback.
* @param {*=} opt_result The starting result.
*/
goog.async.Deferred.prototype.callback = function(opt_result) {
this.check_();
this.assertNotDeferred_(opt_result);
this.updateResult_(true /* isSuccess */, opt_result);
};
/**
* Fire the execution sequence for this Deferred by passing the starting error
* result to the first registered errback.
* @param {*=} opt_result The starting error.
*/
goog.async.Deferred.prototype.errback = function(opt_result) {
this.check_();
this.assertNotDeferred_(opt_result);
this.makeStackTraceLong_(opt_result);
this.updateResult_(false /* isSuccess */, opt_result);
};
/**
* Attempt to make the error's stack trace be long in that it contains the
* stack trace from the point where the deferred was created on top of the
* current stack trace to give additional context.
* @param {*} error
* @private
*/
goog.async.Deferred.prototype.makeStackTraceLong_ = function(error) {
if (!goog.async.Deferred.LONG_STACK_TRACES) {
return;
}
if (this.constructorStack_ && goog.isObject(error) && error.stack &&
// Stack looks like it was system generated. See
// https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
(/^[^\n]+(\n [^\n]+)+/).test(error.stack)) {
error.stack = error.stack + '\nDEFERRED OPERATION:\n' +
this.constructorStack_;
}
};
/**
* Asserts that an object is not a Deferred.
* @param {*} obj The object to test.
* @throws {Error} Throws an exception if the object is a Deferred.
* @private
*/
goog.async.Deferred.prototype.assertNotDeferred_ = function(obj) {
goog.asserts.assert(
!(obj instanceof goog.async.Deferred),
'An execution sequence may not be initiated with a blocking Deferred.');
};
/**
* Register a callback function to be called with a successful result. If no
* value is returned by the callback function, the result value is unchanged. If
* a new value is returned, it becomes the Deferred result and will be passed to
* the next callback in the execution sequence.
*
* If the function throws an error, the error becomes the new result and will be
* passed to the next errback in the execution chain.
*
* If the function returns a Deferred, the execution sequence will be blocked
* until that Deferred fires. Its result will be passed to the next callback (or
* errback if it is an error result) in this Deferred's execution sequence.
*
* @param {!function(this:T,?):?} cb The function to be called with a successful
* result.
* @param {T=} opt_scope An optional scope to call the callback in.
* @return {!goog.async.Deferred} This Deferred.
* @template T
*/
goog.async.Deferred.prototype.addCallback = function(cb, opt_scope) {
return this.addCallbacks(cb, null, opt_scope);
};
/**
* Register a callback function to be called with an error result. If no value
* is returned by the function, the error result is unchanged. If a new error
* value is returned or thrown, that error becomes the Deferred result and will
* be passed to the next errback in the execution sequence.
*
* If the errback function handles the error by returning a non-error value,
* that result will be passed to the next normal callback in the sequence.
*
* If the function returns a Deferred, the execution sequence will be blocked
* until that Deferred fires. Its result will be passed to the next callback (or
* errback if it is an error result) in this Deferred's execution sequence.
*
* @param {!function(this:T,?):?} eb The function to be called on an
* unsuccessful result.
* @param {T=} opt_scope An optional scope to call the errback in.
* @return {!goog.async.Deferred} This Deferred.
* @template T
*/
goog.async.Deferred.prototype.addErrback = function(eb, opt_scope) {
return this.addCallbacks(null, eb, opt_scope);
};
/**
* Registers one function as both a callback and errback.
*
* @param {!function(this:T,?):?} f The function to be called on any result.
* @param {T=} opt_scope An optional scope to call the function in.
* @return {!goog.async.Deferred} This Deferred.
* @template T
*/
goog.async.Deferred.prototype.addBoth = function(f, opt_scope) {
return this.addCallbacks(f, f, opt_scope);
};
/**
* Registers a callback function and an errback function at the same position
* in the execution sequence. Only one of these functions will execute,
* depending on the error state during the execution sequence.
*
* NOTE: This is not equivalent to {@code def.addCallback().addErrback()}! If
* the callback is invoked, the errback will be skipped, and vice versa.
*
* @param {(function(this:T,?):?)|null} cb The function to be called on a
* successful result.
* @param {(function(this:T,?):?)|null} eb The function to be called on an
* unsuccessful result.
* @param {T=} opt_scope An optional scope to call the functions in.
* @return {!goog.async.Deferred} This Deferred.
* @template T
*/
goog.async.Deferred.prototype.addCallbacks = function(cb, eb, opt_scope) {
goog.asserts.assert(!this.blocking_, 'Blocking Deferreds can not be re-used');
this.sequence_.push([cb, eb, opt_scope]);
if (this.hasFired()) {
this.fire_();
}
return this;
};
/**
* Links another Deferred to the end of this Deferred's execution sequence. The
* result of this execution sequence will be passed as the starting result for
* the chained Deferred, invoking either its first callback or errback.
*
* @param {!goog.async.Deferred} otherDeferred The Deferred to chain.
* @return {!goog.async.Deferred} This Deferred.
*/
goog.async.Deferred.prototype.chainDeferred = function(otherDeferred) {
this.addCallbacks(
otherDeferred.callback, otherDeferred.errback, otherDeferred);
return this;
};
/**
* Makes this Deferred wait for another Deferred's execution sequence to
* complete before continuing.
*
* This is equivalent to adding a callback that returns {@code otherDeferred},
* but doesn't prevent additional callbacks from being added to
* {@code otherDeferred}.
*
* @param {!goog.async.Deferred} otherDeferred The Deferred to wait for.
* @return {!goog.async.Deferred} This Deferred.
*/
goog.async.Deferred.prototype.awaitDeferred = function(otherDeferred) {
return this.addCallback(goog.bind(otherDeferred.branch, otherDeferred));
};
/**
* Creates a branch off this Deferred's execution sequence, and returns it as a
* new Deferred. The branched Deferred's starting result will be shared with the
* parent at the point of the branch, even if further callbacks are added to the
* parent.
*
* All branches at the same stage in the execution sequence will receive the
* same starting value.
*
* @param {boolean=} opt_propagateCancel If cancel() is called on every child
* branch created with opt_propagateCancel, the parent will be canceled as
* well.
* @return {!goog.async.Deferred} A Deferred that will be started with the
* computed result from this stage in the execution sequence.
*/
goog.async.Deferred.prototype.branch = function(opt_propagateCancel) {
var d = new goog.async.Deferred();
this.chainDeferred(d);
if (opt_propagateCancel) {
d.parent_ = this;
this.branches_++;
}
return d;
};
/**
* @return {boolean} Whether the execution sequence has been started on this
* Deferred by invoking {@code callback} or {@code errback}.
*/
goog.async.Deferred.prototype.hasFired = function() {
return this.fired_;
};
/**
* @param {*} res The latest result in the execution sequence.
* @return {boolean} Whether the current result is an error that should cause
* the next errback to fire. May be overridden by subclasses to handle
* special error types.
* @protected
*/
goog.async.Deferred.prototype.isError = function(res) {
return res instanceof Error;
};
/**
* @return {boolean} Whether an errback exists in the remaining sequence.
* @private
*/
goog.async.Deferred.prototype.hasErrback_ = function() {
return goog.array.some(this.sequence_, function(sequenceRow) {
// The errback is the second element in the array.
return goog.isFunction(sequenceRow[1]);
});
};
/**
* Exhausts the execution sequence while a result is available. The result may
* be modified by callbacks or errbacks, and execution will block if the
* returned result is an incomplete Deferred.
*
* @private
*/
goog.async.Deferred.prototype.fire_ = function() {
if (this.unhandledExceptionTimeoutId_ && this.hasFired() &&
this.hasErrback_()) {
// It is possible to add errbacks after the Deferred has fired. If a new
// errback is added immediately after the Deferred encountered an unhandled
// error, but before that error is rethrown, cancel the rethrow.
goog.global.clearTimeout(this.unhandledExceptionTimeoutId_);
delete this.unhandledExceptionTimeoutId_;
}
if (this.parent_) {
this.parent_.branches_--;
delete this.parent_;
}
var res = this.result_;
var unhandledException = false;
var isNewlyBlocked = false;
while (this.sequence_.length && !this.blocked_) {
var sequenceEntry = this.sequence_.shift();
var callback = sequenceEntry[0];
var errback = sequenceEntry[1];
var scope = sequenceEntry[2];
var f = this.hadError_ ? errback : callback;
if (f) {
/** @preserveTry */
try {
var ret = f.call(scope || this.defaultScope_, res);
// If no result, then use previous result.
if (goog.isDef(ret)) {
// Bubble up the error as long as the return value hasn't changed.
this.hadError_ = this.hadError_ && (ret == res || this.isError(ret));
this.result_ = res = ret;
}
if (res instanceof goog.async.Deferred) {
isNewlyBlocked = true;
this.blocked_ = true;
}
} catch (ex) {
res = ex;
this.hadError_ = true;
this.makeStackTraceLong_(res);
if (!this.hasErrback_()) {
// If an error is thrown with no additional errbacks in the queue,
// prepare to rethrow the error.
unhandledException = true;
}
}
}
}
this.result_ = res;
if (isNewlyBlocked) {
res.addCallbacks(
goog.bind(this.continue_, this, true /* isSuccess */),
goog.bind(this.continue_, this, false /* isSuccess */));
res.blocking_ = true;
} else if (goog.async.Deferred.STRICT_ERRORS && this.isError(res) &&
!(res instanceof goog.async.Deferred.CanceledError)) {
this.hadError_ = true;
unhandledException = true;
}
if (unhandledException) {
// Rethrow the unhandled error after a timeout. Execution will continue, but
// the error will be seen by global handlers and the user. The throw will
// be canceled if another errback is appended before the timeout executes.
// The error's original stack trace is preserved where available.
this.unhandledExceptionTimeoutId_ = goog.global.setTimeout(
goog.functions.fail(res), 0);
}
};
/**
* Creates a Deferred that has an initial result.
*
* @param {*=} opt_result The result.
* @return {!goog.async.Deferred} The new Deferred.
*/
goog.async.Deferred.succeed = function(opt_result) {
var d = new goog.async.Deferred();
d.callback(opt_result);
return d;
};
/**
* Creates a Deferred that has an initial error result.
*
* @param {*} res The error result.
* @return {!goog.async.Deferred} The new Deferred.
*/
goog.async.Deferred.fail = function(res) {
var d = new goog.async.Deferred();
d.errback(res);
return d;
};
/**
* Creates a Deferred that has already been canceled.
*
* @return {!goog.async.Deferred} The new Deferred.
*/
goog.async.Deferred.canceled = function() {
var d = new goog.async.Deferred();
d.cancel();
return d;
};
/**
* Normalizes values that may or may not be Deferreds.
*
* If the input value is a Deferred, the Deferred is branched (so the original
* execution sequence is not modified) and the input callback added to the new
* branch. The branch is returned to the caller.
*
* If the input value is not a Deferred, the callback will be executed
* immediately and an already firing Deferred will be returned to the caller.
*
* In the following (contrived) example, if <code>isImmediate</code> is true
* then 3 is alerted immediately, otherwise 6 is alerted after a 2-second delay.
*
* <pre>
* var value;
* if (isImmediate) {
* value = 3;
* } else {
* value = new goog.async.Deferred();
* setTimeout(function() { value.callback(6); }, 2000);
* }
*
* var d = goog.async.Deferred.when(value, alert);
* </pre>
*
* @param {*} value Deferred or normal value to pass to the callback.
* @param {!function(this:T, ?):?} callback The callback to execute.
* @param {T=} opt_scope An optional scope to call the callback in.
* @return {!goog.async.Deferred} A new Deferred that will call the input
* callback with the input value.
* @template T
*/
goog.async.Deferred.when = function(value, callback, opt_scope) {
if (value instanceof goog.async.Deferred) {
return value.branch(true).addCallback(callback, opt_scope);
} else {
return goog.async.Deferred.succeed(value).addCallback(callback, opt_scope);
}
};
/**
* An error sub class that is used when a Deferred has already been called.
* @param {!goog.async.Deferred} deferred The Deferred.
*
* @constructor
* @extends {goog.debug.Error}
*/
goog.async.Deferred.AlreadyCalledError = function(deferred) {
goog.debug.Error.call(this);
/**
* The Deferred that raised this error.
* @type {goog.async.Deferred}
*/
this.deferred = deferred;
};
goog.inherits(goog.async.Deferred.AlreadyCalledError, goog.debug.Error);
/** @override */
goog.async.Deferred.AlreadyCalledError.prototype.message =
'Deferred has already fired';
/** @override */
goog.async.Deferred.AlreadyCalledError.prototype.name = 'AlreadyCalledError';
/**
* An error sub class that is used when a Deferred is canceled.
*
* @param {!goog.async.Deferred} deferred The Deferred object.
* @constructor
* @extends {goog.debug.Error}
*/
goog.async.Deferred.CanceledError = function(deferred) {
goog.debug.Error.call(this);
/**
* The Deferred that raised this error.
* @type {goog.async.Deferred}
*/
this.deferred = deferred;
};
goog.inherits(goog.async.Deferred.CanceledError, goog.debug.Error);
/** @override */
goog.async.Deferred.CanceledError.prototype.message = 'Deferred was canceled';
/** @override */
goog.async.Deferred.CanceledError.prototype.name = 'CanceledError';

View File

@@ -0,0 +1,205 @@
// Copyright 2005 Bob Ippolito. All Rights Reserved.
// Modifications Copyright 2009 The Closure Library Authors.
// All Rights Reserved.
/**
* Portions of this code are from MochiKit, received by The Closure
* Library Authors under the MIT license. All other code is Copyright
* 2005-2009 The Closure Library Authors. All Rights Reserved.
*/
/**
* @fileoverview Class for tracking multiple asynchronous operations and
* handling the results. The DeferredList object here is patterned after the
* DeferredList object in the Twisted python networking framework.
*
* Based on the MochiKit code.
*
* See: http://twistedmatrix.com/projects/core/documentation/howto/defer.html
*
* @author brenneman@google.com (Shawn Brenneman)
*/
goog.provide('goog.async.DeferredList');
goog.require('goog.async.Deferred');
/**
* Constructs an object that waits on the results of multiple asynchronous
* operations and marshals the results. It is itself a <code>Deferred</code>,
* and may have an execution sequence of callback functions added to it. Each
* <code>DeferredList</code> instance is single use and may be fired only once.
*
* The default behavior of a <code>DeferredList</code> is to wait for a success
* or error result from every <code>Deferred</code> in its input list. Once
* every result is available, the <code>DeferredList</code>'s execution sequence
* is fired with a list of <code>[success, result]</code> array pairs, where
* <code>success</code> is a boolean indicating whether <code>result</code> was
* the product of a callback or errback. The list's completion criteria and
* result list may be modified by setting one or more of the boolean options
* documented below.
*
* <code>Deferred</code> instances passed into a <code>DeferredList</code> are
* independent, and may have additional callbacks and errbacks added to their
* execution sequences after they are passed as inputs to the list.
*
* @param {!Array.<!goog.async.Deferred>} list An array of deferred results to
* wait for.
* @param {boolean=} opt_fireOnOneCallback Whether to stop waiting as soon as
* one input completes successfully. In this case, the
* <code>DeferredList</code>'s callback chain will be called with a two
* element array, <code>[index, result]</code>, where <code>index</code>
* identifies which input <code>Deferred</code> produced the successful
* <code>result</code>.
* @param {boolean=} opt_fireOnOneErrback Whether to stop waiting as soon as one
* input reports an error. The failing result is passed to the
* <code>DeferredList</code>'s errback sequence.
* @param {boolean=} opt_consumeErrors When true, any errors fired by a
* <code>Deferred</code> in the input list will be captured and replaced
* with a succeeding null result. Any callbacks added to the
* <code>Deferred</code> after its use in the <code>DeferredList</code> will
* receive null instead of the error.
* @param {Function=} opt_canceler A function that will be called if the
* <code>DeferredList</code> is canceled. @see goog.async.Deferred#cancel
* @param {Object=} opt_defaultScope The default scope to invoke callbacks or
* errbacks in.
* @constructor
* @extends {goog.async.Deferred}
*/
goog.async.DeferredList = function(
list, opt_fireOnOneCallback, opt_fireOnOneErrback, opt_consumeErrors,
opt_canceler, opt_defaultScope) {
goog.base(this, opt_canceler, opt_defaultScope);
/**
* The list of Deferred objects to wait for.
* @const {!Array.<!goog.async.Deferred>}
* @private
*/
this.list_ = list;
/**
* The stored return values of the Deferred objects.
* @const {!Array}
* @private
*/
this.deferredResults_ = [];
/**
* Whether to fire on the first successful callback instead of waiting for
* every Deferred to complete.
* @const {boolean}
* @private
*/
this.fireOnOneCallback_ = !!opt_fireOnOneCallback;
/**
* Whether to fire on the first error result received instead of waiting for
* every Deferred to complete.
* @const {boolean}
* @private
*/
this.fireOnOneErrback_ = !!opt_fireOnOneErrback;
/**
* Whether to stop error propagation on the input Deferred objects. If the
* DeferredList sees an error from one of the Deferred inputs, the error will
* be captured, and the Deferred will be returned to success state with a null
* return value.
* @const {boolean}
* @private
*/
this.consumeErrors_ = !!opt_consumeErrors;
/**
* The number of input deferred objects that have fired.
* @private {number}
*/
this.numFinished_ = 0;
for (var i = 0; i < list.length; i++) {
var d = list[i];
d.addCallbacks(goog.bind(this.handleCallback_, this, i, true),
goog.bind(this.handleCallback_, this, i, false));
}
if (list.length == 0 && !this.fireOnOneCallback_) {
this.callback(this.deferredResults_);
}
};
goog.inherits(goog.async.DeferredList, goog.async.Deferred);
/**
* Registers the result from an input deferred callback or errback. The result
* is returned and may be passed to additional handlers in the callback chain.
*
* @param {number} index The index of the firing deferred object in the input
* list.
* @param {boolean} success Whether the result is from a callback or errback.
* @param {*} result The result of the callback or errback.
* @return {*} The result, to be handled by the next handler in the deferred's
* callback chain (if any). If consumeErrors is set, an error result is
* replaced with null.
* @private
*/
goog.async.DeferredList.prototype.handleCallback_ = function(
index, success, result) {
this.numFinished_++;
this.deferredResults_[index] = [success, result];
if (!this.hasFired()) {
if (this.fireOnOneCallback_ && success) {
this.callback([index, result]);
} else if (this.fireOnOneErrback_ && !success) {
this.errback(result);
} else if (this.numFinished_ == this.list_.length) {
this.callback(this.deferredResults_);
}
}
if (this.consumeErrors_ && !success) {
result = null;
}
return result;
};
/** @override */
goog.async.DeferredList.prototype.errback = function(res) {
goog.base(this, 'errback', res);
// On error, cancel any pending requests.
for (var i = 0; i < this.list_.length; i++) {
this.list_[i].cancel();
}
};
/**
* Creates a <code>DeferredList</code> that gathers results from multiple
* <code>Deferred</code> inputs. If all inputs succeed, the callback is fired
* with the list of results as a flat array. If any input fails, the list's
* errback is fired immediately with the offending error, and all other pending
* inputs are canceled.
*
* @param {!Array.<!goog.async.Deferred>} list The list of <code>Deferred</code>
* inputs to wait for.
* @return {!goog.async.Deferred} The deferred list of results from the inputs
* if they all succeed, or the error result of the first input to fail.
*/
goog.async.DeferredList.gatherResults = function(list) {
return new goog.async.DeferredList(list, false, true).
addCallback(function(results) {
var output = [];
for (var i = 0; i < results.length; i++) {
output[i] = results[i][1];
}
return output;
});
};

View File

@@ -0,0 +1,95 @@
/**
* @license
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 Base OSAPI binding.
* This file was copied from
* http://svn.apache.org/repos/asf/shindig/trunk/features/src/main/javascript/features/shindig.container/osapi.js
* and it's slightly modified for Closure.
*/
goog.provide('goog.osapi');
// Expose osapi from container side.
var osapi = osapi || {};
goog.exportSymbol('osapi', osapi);
/** @type {Function} */
osapi.callback;
/**
* Dispatch a JSON-RPC batch request to services defined in the osapi namespace
* @param {Array.<Object>} requests an array of rpc requests.
*/
goog.osapi.handleGadgetRpcMethod = function(requests) {
var responses = new Array(requests.length);
var callCount = 0;
var callback = osapi.callback;
var dummy = function(params, apiCallback) {
apiCallback({});
};
for (var i = 0; i < requests.length; i++) {
// Don't allow underscores in any part of the method name as a
// convention for restricted methods
var current = osapi;
if (requests[i]['method'].indexOf('_') == -1) {
var path = requests[i]['method'].split('.');
for (var j = 0; j < path.length; j++) {
if (current.hasOwnProperty(path[j])) {
current = current[path[j]];
} else {
// No matching api
current = dummy;
break;
}
}
} else {
current = dummy;
}
// Execute the call and latch the rpc callback until all
// complete
current(requests[i]['params'], function(i) {
return function(response) {
// Put back in json-rpc format
responses[i] = {'id': requests[i].id, 'data': response};
callCount++;
if (callCount == requests.length) {
callback(responses);
}
};
}(i));
}
};
/**
* Initializes container side osapi binding.
*/
goog.osapi.init = function() {
// Container-side binding for the gadgetsrpctransport used by osapi.
// Containers add services to the client-side osapi implementation by
// defining them in the osapi namespace
if (gadgets && gadgets.rpc) { // Only define if gadgets rpc exists.
// Register the osapi RPC dispatcher.
gadgets.rpc.register('osapi._handleGadgetRpcMethod',
/** @type {!Function} */ (goog.osapi.handleGadgetRpcMethod));
}
};