This commit is contained in:
Éric Lemoine
2013-03-11 13:35:17 +01:00
parent 849774dceb
commit f150259eee
1189 changed files with 341774 additions and 2001 deletions
@@ -0,0 +1,2 @@
// This is a dummy file to trick genjsdeps into doing the right thing.
// TODO(nicksantos): fix this
@@ -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;
@@ -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
};
/**
* @inheritDoc
*/
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('>');
}
};
/**
* @inheritDoc
*/
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, '>');
}
};
/**
* @inheritDoc
*/
goog.string.html.HtmlSanitizer.prototype.pcdata = function(text) {
if (!this.ignoring_) {
this.stringBuffer_.append(text);
}
};
/**
* @inheritDoc
*/
goog.string.html.HtmlSanitizer.prototype.rcdata = function(text) {
if (!this.ignoring_) {
this.stringBuffer_.append(text);
}
};
/**
* @inheritDoc
*/
goog.string.html.HtmlSanitizer.prototype.cdata = function(text) {
if (!this.ignoring_) {
this.stringBuffer_.append(text);
}
};
/**
* @inheritDoc
*/
goog.string.html.HtmlSanitizer.prototype.startDoc = function() {
this.stack_ = [];
this.ignoring_ = false;
};
/**
* @inheritDoc
*/
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
@@ -0,0 +1,162 @@
goog.require('goog.dom');
goog.require('goog.dom.query');
goog.require('goog.testing.asserts'); // assertThrows
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);
}
@@ -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();
};
@@ -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_;
@@ -0,0 +1,652 @@
// 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.CancelledError');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.debug.Error');
/**
* Represents the results of an asynchronous operation. A Deferred object
* starts with no result, and then gets a result at some point in the future.
* @param {Function=} opt_canceller A function that will be called if the
* deferred is cancelled.
* @param {Object=} opt_defaultScope The default scope to call callbacks with.
* @constructor
*/
goog.async.Deferred = function(opt_canceller, opt_defaultScope) {
/**
* Entries in the chain are arrays containing a callback, errback, and
* optional scope. Callbacks or errbacks may be null.
* @type {!Array.<!Array>}
* @private
*/
this.chain_ = [];
/**
* If provided, this is the function to call when the deferred is cancelled.
* @type {Function|undefined}
* @private
*/
this.canceller_ = opt_canceller;
/**
* The default scope to execute callbacks in.
* @type {Object}
* @private
*/
this.defaultScope_ = opt_defaultScope || null;
};
/**
* Whether the deferred has been fired.
* @type {boolean}
* @private
*/
goog.async.Deferred.prototype.fired_ = false;
/**
* Whether the last result in the callback chain was an error.
* @type {boolean}
* @private
*/
goog.async.Deferred.prototype.hadError_ = false;
/**
* The current Deferred result, updated by registered callbacks and errbacks.
* @type {*}
* @private
*/
goog.async.Deferred.prototype.result_;
/**
* The number of times this deferred has been paused.
* @type {number}
* @private
*/
goog.async.Deferred.prototype.paused_ = 0;
/**
* If the deferred was cancelled but it did not have a canceller then this gets
* set to true.
* @type {boolean}
* @private
*/
goog.async.Deferred.prototype.silentlyCancelled_ = false;
/**
* If a callback returns a deferred then this deferred is considered a chained
* deferred and once it is chained we cannot add more callbacks.
* @type {boolean}
* @private
*/
goog.async.Deferred.prototype.chained_ = 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 cancelled.
* @type {number}
* @private
*/
goog.async.Deferred.prototype.branches_ = 0;
/**
* Cancels a deferred that has not yet received a value. If this Deferred is
* paused waiting for a chained Deferred to fire, the chained Deferred will also
* be cancelled.
*
* If this Deferred was created by calling branch() on a parent Deferred with
* opt_propagateCancel set to true, the parent may also be cancelled. 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 cancelled 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
// canceller callback to ensure that this cancellation doesn't get
// double-counted in any way.
var parent = this.parent_;
delete this.parent_;
if (opt_deepCancel) {
parent.cancel(opt_deepCancel);
} else {
parent.branchCancel_();
}
}
if (this.canceller_) {
// Call in user-specified scope.
this.canceller_.call(this.defaultScope_, this);
} else {
this.silentlyCancelled_ = true;
}
if (!this.hasFired()) {
this.errback(new goog.async.Deferred.CancelledError(this));
}
} else if (this.result_ instanceof goog.async.Deferred) {
this.result_.cancel();
}
};
/**
* Handle a single branch being cancelled. Once all branches are cancelled, this
* Deferred will be cancelled as well.
* @private
*/
goog.async.Deferred.prototype.branchCancel_ = function() {
this.branches_--;
if (this.branches_ <= 0) {
this.cancel();
}
};
/**
* Pauses the deferred.
* @private
*/
goog.async.Deferred.prototype.pause_ = function() {
this.paused_++;
};
/**
* Resumes a paused deferred.
* @private
*/
goog.async.Deferred.prototype.unpause_ = function() {
// TODO(arv): Rename
this.paused_--;
if (this.paused_ == 0 && this.hasFired()) {
this.fire_();
}
};
/**
* Called when a dependent deferred fires.
* @param {boolean} isSuccess Whether the result is a success or an error.
* @param {*} res The result of the dependent deferred.
* @private
*/
goog.async.Deferred.prototype.continue_ = function(isSuccess, res) {
this.resback_(isSuccess, res);
this.unpause_();
};
/**
* Called when either a success or a failure happens.
* @param {boolean} isSuccess Whether the result is a success or an error.
* @param {*} res The result.
* @private
*/
goog.async.Deferred.prototype.resback_ = 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.silentlyCancelled_) {
throw new goog.async.Deferred.AlreadyCalledError(this);
}
this.silentlyCancelled_ = false;
}
};
/**
* Record a successful result for this operation, and send the result
* to all registered callback functions.
* @param {*=} opt_result The result of the operation.
*/
goog.async.Deferred.prototype.callback = function(opt_result) {
this.check_();
this.assertNotDeferred_(opt_result);
this.resback_(true /* isSuccess */, opt_result);
};
/**
* Record that this operation failed with an error, and send the error
* to all registered errback functions.
* @param {*=} opt_result The error result of the operation.
*/
goog.async.Deferred.prototype.errback = function(opt_result) {
this.check_();
this.assertNotDeferred_(opt_result);
this.resback_(false /* isSuccess */, opt_result);
};
/**
* 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),
'Deferred instances can only be chained if they are the result of a ' +
'callback');
};
/**
* Register a callback function, to be called when a successful result
* is available.
* @param {!Function} cb The function to be called on a successful result.
* @param {Object=} opt_scope An optional scope to call the callback in.
* @return {!goog.async.Deferred} The deferred object for chaining.
*/
goog.async.Deferred.prototype.addCallback = function(cb, opt_scope) {
return this.addCallbacks(cb, null, opt_scope);
};
/**
* Register a callback function, to be called if this operation fails.
* @param {!Function} eb The function to be called on an unsuccessful result.
* @param {Object=} opt_scope An optional scope to call the errback in.
* @return {!goog.async.Deferred} The deferred object for chaining.
*/
goog.async.Deferred.prototype.addErrback = function(eb, opt_scope) {
return this.addCallbacks(null, eb, opt_scope);
};
/**
* Registers a callback function and errback function.
* @param {Function} cb The function to be called on a successful result.
* @param {Function} eb The function to be called on an unsuccessful result.
* @param {Object=} opt_scope An optional scope to call the callbacks in.
* @return {!goog.async.Deferred} The deferred object for chaining.
*/
goog.async.Deferred.prototype.addCallbacks = function(cb, eb, opt_scope) {
goog.asserts.assert(!this.chained_, 'Chained Deferreds can not be re-used');
this.chain_.push([cb, eb, opt_scope]);
if (this.hasFired()) {
this.fire_();
}
return this;
};
/**
* Adds another deferred to the end of this deferred's processing chain.
*
* Use this when you want otherDeferred to be called at the end of
* thisDeferred's previous callbacks.
*
* @param {!goog.async.Deferred} otherDeferred The Deferred to chain.
* @return {!goog.async.Deferred} The deferred object for chaining.
*/
goog.async.Deferred.prototype.chainDeferred = function(otherDeferred) {
this.addCallbacks(
otherDeferred.callback, otherDeferred.errback, otherDeferred);
return this;
};
/**
* Makes this Deferred wait for otherDeferred to be called, and its preceding
* callbacks to be executed, before continuing with the callback sequence.
*
* This is equivalent to adding a callback that returns otherDeferred, but
* doesn't prevent additional callbacks from being added to otherDeferred.
*
* @param {!goog.async.Deferred} otherDeferred The Deferred to wait for.
* @return {!goog.async.Deferred} The deferred object for chaining.
*/
goog.async.Deferred.prototype.awaitDeferred = function(otherDeferred) {
return this.addCallback(goog.bind(otherDeferred.branch, otherDeferred));
};
/**
* Create a branch off this Deferred's callback chain, and return it as a new
* Deferred. This means that the return value will have the value at the current
* point in the callback chain, regardless of any further callbacks added to
* this Deferred.
*
* Additional callbacks added to the original Deferred will not affect the value
* of any branches. All branches at the same stage in the callback chain 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 cancelled as
* well.
* @return {!goog.async.Deferred} The deferred value at this point in the
* callback chain.
*/
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;
};
/**
* Registers a function as both callback and errback.
* @param {!Function} f The function to be called on any result.
* @param {Object=} opt_scope An optional scope to call the callbacks in.
* @return {!goog.async.Deferred} The deferred object for chaining.
*/
goog.async.Deferred.prototype.addBoth = function(f, opt_scope) {
return this.addCallbacks(f, f, opt_scope);
};
/**
* @return {boolean} Whether callback or errback has been called on this
* deferred.
*/
goog.async.Deferred.prototype.hasFired = function() {
return this.fired_;
};
/**
* @param {*} res The current callback result.
* @return {boolean} Whether the current result is an error that should cause
* registered errbacks 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 has been registered.
* @private
*/
goog.async.Deferred.prototype.hasErrback_ = function() {
return goog.array.some(this.chain_, function(chainRow) {
// The errback is the second element in the array.
return goog.isFunction(chainRow[1]);
});
};
/**
* Exhausts the callback sequence once a result is available.
* @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 isChained = false;
while (this.chain_.length && this.paused_ == 0) {
var chainEntry = this.chain_.shift();
var callback = chainEntry[0];
var errback = chainEntry[1];
var scope = chainEntry[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) {
isChained = true;
this.pause_();
}
} catch (ex) {
res = ex;
this.hadError_ = true;
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 (isChained && this.paused_) {
res.addCallbacks(
goog.bind(this.continue_, this, true /* isSuccess */),
goog.bind(this.continue_, this, false /* isSuccess */));
res.chained_ = 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(function() {
throw res;
}, 0);
}
};
/**
* Creates a deferred that always succeeds.
* @param {*} res The result.
* @return {!goog.async.Deferred} The deferred object.
*/
goog.async.Deferred.succeed = function(res) {
var d = new goog.async.Deferred();
d.callback(res);
return d;
};
/**
* Creates a deferred that always fails.
* @param {*} res The error result.
* @return {!goog.async.Deferred} The deferred object.
*/
goog.async.Deferred.fail = function(res) {
var d = new goog.async.Deferred();
d.errback(res);
return d;
};
/**
* Creates a deferred that has already been cancelled.
* @return {!goog.async.Deferred} The deferred object.
*/
goog.async.Deferred.cancelled = function() {
var d = new goog.async.Deferred();
d.cancel();
return d;
};
/**
* Applies a callback to both deferred and non-deferred values, providing a
* mechanism to normalize synchronous and asynchronous behavior.
*
* If the value is non-deferred, the callback will be executed immediately and
* an already committed deferred returned.
*
* If the object is a deferred, it is branched (so the callback doesn't affect
* the previous chain) and the callback is added to the new deferred. The
* branched deferred is then returned.
*
* 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 non-deferred value to pass to the callback.
* @param {!Function} callback The callback to execute.
* @param {Object=} opt_scope An optional scope to call the callback in.
* @return {!goog.async.Deferred}
*/
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 object.
* @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 cancelled.
* @param {!goog.async.Deferred} deferred The deferred object.
* @constructor
* @extends {goog.debug.Error}
*/
goog.async.Deferred.CancelledError = 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.CancelledError, goog.debug.Error);
/** @override */
goog.async.Deferred.CancelledError.prototype.message = 'Deferred was cancelled';
/** @override */
goog.async.Deferred.CancelledError.prototype.name = 'CancelledError';
@@ -0,0 +1,200 @@
// 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.array');
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 sends results to its registered callback chain. Each instance is single
* use and may only fire once.
*
* Unless overridden by one of the options below, the <code>DeferredList</code>
* will wait for a result from every input <code>Deferred</code>. The results
* are stored in a list of two-element arrays as <code>[success, result]</code>,
* where <code>success</code> is whether that result was from a callback or
* errback. Once all results are available, the <code>DeferredList</code>'s
* callback chain is invoked with the full result list.
*
* @param {!Array.<!goog.async.Deferred>} list An array of deferred objects 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
* <code>result</code>.
* @param {boolean=} opt_fireOnOneErrback Whether to stop waiting as soon as one
* input reports an error. The error result is passed to the
* <code>DeferredList</code>'s error callback chain.
* @param {boolean=} opt_consumeErrors When true, will stop propagation of the
* error callback chain for input deferred objects. If the failing deferred
* has a registered callback after this <code>DeferredList</code>, it will
* be called with null instead of an <code>Error</code>.
* @param {Function=} opt_canceller A function that will be called if the
* deferred list is canceled.
* @param {Object=} opt_defaultScope The default scope to call callbacks with.
* @constructor
* @extends {goog.async.Deferred}
*/
goog.async.DeferredList = function(list,
opt_fireOnOneCallback,
opt_fireOnOneErrback,
opt_consumeErrors,
opt_canceller,
opt_defaultScope) {
goog.async.Deferred.call(this, opt_canceller, opt_defaultScope);
/**
* The list of Deferred objects to wait for.
* @type {!Array.<!goog.async.Deferred>}
* @private
*/
this.list_ = list;
/**
* The stored return values of the Deferred objects.
* @type {!Array}
* @private
*/
this.deferredResults_ = [];
/**
* Whether to fire on the first successful callback instead of waiting for
* every Deferred to complete.
* @type {boolean}
* @private
*/
this.fireOnOneCallback_ = !!opt_fireOnOneCallback;
/**
* Whether to fire on the first error result received instead of waiting for
* every Deferred to complete.
* @type {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.
* @type {boolean}
* @private
*/
this.consumeErrors_ = !!opt_consumeErrors;
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);
/**
* The number of input deferred objects that have fired.
* @type {number}
* @private
*/
goog.async.DeferredList.prototype.numFinished_ = 0;
/**
* 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;
};
/** @inheritDoc */
goog.async.DeferredList.prototype.errback = function(res) {
goog.async.DeferredList.superClass_.errback.call(this, res);
// On error, cancel any pending requests.
goog.array.forEach(this.list_, function(item) {
item.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 errback is
* fired with the error.
*
* @param {!Array.<!goog.async.Deferred>} list The list of deferred objects to
* wait for.
* @return {!goog.async.DeferredList} A new deferred list.
*/
goog.async.DeferredList.gatherResults = function(list) {
var d = new goog.async.DeferredList(list, false, true);
d.addCallback(function(results) {
return goog.array.map(results, function(res) {
return res[1];
});
});
return d;
};
@@ -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));
}
};
@@ -0,0 +1,280 @@
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A button that can access the native clipboard. Calls
* out to silverlight when it's possible to do so.
*
* If the current browser does not support native clipboard access,
* throw an error.
*
* @author nicksantos@google.com (Nick Santos)
*/
goog.provide('goog.silverlight.ClipboardButton');
goog.provide('goog.silverlight.ClipboardButtonType');
goog.provide('goog.silverlight.ClipboardEvent');
goog.provide('goog.silverlight.CopyButton');
goog.provide('goog.silverlight.PasteButton');
goog.provide('goog.silverlight.PasteButtonEvent');
goog.require('goog.asserts');
goog.require('goog.events.Event');
goog.require('goog.math.Size');
goog.require('goog.silverlight');
goog.require('goog.ui.Component');
/**
* A button that can access the native clipboard, via Silverlight.
*
* If this is not a browser that supports native clipboard access,
* throw an error.
*
* Clients should not instantiate this directly. Instead, use CopyButton
* or PasteButton.
*
* @param {goog.silverlight.ClipboardButtonType} type Copy or Paste.
* @param {Function} callback The callback function for copy or paste.
* @param {goog.Uri} slResource The URI of ClosureClipboardButton.xap (The
* silverlight resource included in this package.)
* @param {string} caption Text caption to display as the button's caption.
* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper, used for
* document interaction.
* @constructor
* @extends {goog.ui.Component}
*/
goog.silverlight.ClipboardButton =
function(type, callback, slResource, caption, opt_domHelper) {
// In an ideal world, we'd use some sort of transparent overlay over
// an HTML-based button. Silverlight doesn't appear to allow this.
// Silverlight only allows copy and paste in response to a
// silverlight-managed click on a silverlight-rendered button,
// and this button cannot be transparent.
goog.asserts.assert(
goog.silverlight.ClipboardButton.hasClipboardAccess(),
'no clipboard access');
goog.base(this, opt_domHelper);
/**
* The ID of the handler to pass to silverlight.
* @type {string}
* @private
*/
this.callbackId_ = goog.asserts.assertString(
goog.silverlight.getHandlerName(callback));
/**
* The button caption.
* @type {string}
* @private
*/
this.caption_ = caption;
/**
* The compiled silverlight bundle.
* @type {goog.Uri}
* @private
*/
this.slResource_ = slResource;
/**
* @type {goog.silverlight.ClipboardButtonType}
* @private
*/
this.type_ = type;
/**
* @type {goog.math.Size}
* @private
*/
this.size_ = goog.silverlight.ClipboardButton.DEFAULT_SIZE_;
};
goog.inherits(goog.silverlight.ClipboardButton, goog.ui.Component);
/**
* The default size of the button.
* @type {goog.math.Size}
* @private
*/
goog.silverlight.ClipboardButton.DEFAULT_SIZE_ = new goog.math.Size(100, 30);
/**
* Silverlight components need absolute sizing. Should be set before the
* component is rendered.
* @param {goog.math.Size} size The size of the component.
*/
goog.silverlight.ClipboardButton.prototype.setSize = function(size) {
this.size_ = size;
};
/**
* Creates the button's DOM.
* @override
*/
goog.silverlight.ClipboardButton.prototype.createDom = function() {
var dom = this.getDomHelper();
var element = dom.createDom('div', goog.getCssName('goog-inline-block'));
this.setElementInternal(element);
var source = this.slResource_.toString();
goog.silverlight.createObject(
source, element, null,
{version: '4.0',
width: this.size_.width, height: this.size_.height}, null,
['buttonType=' + this.type_,
'callbackName=' + this.callbackId_,
'Content=' + this.caption_].join(','));
};
/** @return {boolean} If clipboard access is supported. */
goog.silverlight.ClipboardButton.hasClipboardAccess = function() {
// TODO(nicksantos): Use IE execCommand if available.
return goog.silverlight.isInstalled('4.0');
};
/** @inheritDoc */
goog.silverlight.ClipboardButton.prototype.disposeInternal = function() {
goog.silverlight.disposeHandlerName(this.callbackId_);
goog.base(this, 'disposeInternal');
};
/**
* Whether the button should trigger a copy or a paste.
* @enum
* @private
*/
goog.silverlight.ClipboardButtonType = {
COPY: 1,
PASTE: 2
};
/**
* A clipboard button event. By design, replicates IE's clipboard API.
* @param {string} type The event type.
* @param {string=} opt_data The data pasted, if this is a paste event.
* @constructor
* @extends {goog.events.Event}
*/
goog.silverlight.ClipboardEvent = function(type, opt_data) {
goog.base(this, type);
/**
* @type {?string}
* @private
*/
this.data_ = opt_data || null;
};
goog.inherits(goog.silverlight.ClipboardEvent, goog.events.Event);
/** @return {?string} Data pasted from the clipboard. */
goog.silverlight.ClipboardEvent.prototype.getData = function() {
return this.data_;
};
/** @param {string} data Data to put on the clipboard. */
goog.silverlight.ClipboardEvent.prototype.setData = function(data) {
this.data_ = data;
};
/** @enum {string} */
goog.silverlight.ClipboardEventType = {
COPY: 'copy',
PASTE: 'paste'
};
/**
* A button that can retrieve contents from the native clipboard.
*
* @param {goog.Uri} slResource The URI of ClosureClipboardButton.xap (the
* silverlight resource included in this package.
* @param {string=} opt_caption Text caption to display as the button's caption.
* Defaults to 'Paste'.
* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper, used for
* document interaction.
* @constructor
* @extends {goog.silverlight.ClipboardButton}
*/
goog.silverlight.PasteButton =
function(slResource, opt_caption, opt_domHelper) {
/** @desc Label for the paste button */
var MSG_DEFAULT_PASTE_BUTTON_CAPTION = goog.getMsg('Paste');
var caption = opt_caption || MSG_DEFAULT_PASTE_BUTTON_CAPTION;
goog.base(this,
goog.silverlight.ClipboardButtonType.PASTE,
goog.bind(this.handlePaste_, this),
slResource,
caption,
opt_domHelper);
};
goog.inherits(goog.silverlight.PasteButton, goog.silverlight.ClipboardButton);
/** @param {string} content The native content that was pasted. */
goog.silverlight.PasteButton.prototype.handlePaste_ = function(content) {
this.dispatchEvent(
new goog.silverlight.ClipboardEvent(
goog.silverlight.ClipboardEventType.PASTE,
content));
};
/**
* A button that can copy contents.
*
* @param {goog.Uri} slResource The URI of ClosureClipboardButton.xap (the
* silverlight resource included in this package.
* @param {string=} opt_caption Text caption to display as the button's caption.
* Defaults to 'Copy'.
* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper, used for
* document interaction.
* @constructor
* @extends {goog.silverlight.ClipboardButton}
*/
goog.silverlight.CopyButton =
function(slResource, opt_caption, opt_domHelper) {
/** @desc Label for the copy button */
var MSG_DEFAULT_COPY_BUTTON_CAPTION = goog.getMsg('Copy');
var caption = opt_caption || MSG_DEFAULT_COPY_BUTTON_CAPTION;
goog.base(this,
goog.silverlight.ClipboardButtonType.COPY,
goog.bind(this.handleCopy_, this),
slResource,
caption,
opt_domHelper);
};
goog.inherits(goog.silverlight.CopyButton, goog.silverlight.ClipboardButton);
/** @return {string} The content to be copied to the clipboard. */
goog.silverlight.CopyButton.prototype.handleCopy_ = function() {
var event = new goog.silverlight.ClipboardEvent(
goog.silverlight.ClipboardEventType.COPY);
this.dispatchEvent(event);
return event.getData() || '';
};
@@ -0,0 +1,760 @@
/**
* @license
* Portions of this code are from Silverlight.js under the Ms-PL license and
* modified by The Closure Authors.
*
* Silverlight.js version 4.0.50401.0
*
* This file is provided by Microsoft as a helper file for websites that
* incorporate Silverlight Objects. This file is provided under the Microsoft
* Public License available at
* http://code.msdn.microsoft.com/silverlightjs/Project/License.aspx.
* You may not use or distribute this file or the code in this file except as
* expressly permitted under that license.
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*/
/**
* @fileoverview Modified version of Silverlight.js to work with
* the compiler's advanced modes. The API should be the same, except
* that they're namespaced under goog.silverlight instead of Silverlight.
*
* We've also modified this so that the silverlight bootstrapper
* does not run by default. If you need to start up silverlight, you
* should explicitly call goog.silverlight.startup().
*
* @author nicksantos@google.com (Nick Santos) (ported to Closure)
*/
goog.provide('goog.silverlight');
/**
* Counter of globalized event handlers
* @type {number}
*/
goog.silverlight._silverlightCount = 0;
/**
* Prevents onSilverlightInstalled from being called multiple
* times
* @type {boolean}
*/
goog.silverlight.__onSilverlightInstalledCalled = false;
/**
* Prefix for fwlink URL's
* @type {string}
*/
goog.silverlight.fwlinkRoot = 'http://go2.microsoft.com/fwlink/?LinkID=';
/**
* Ensures that only one Installation State event is fired.
* @type {boolean}
*/
goog.silverlight.__installationEventFired = false;
/**
* Called by goog.silverlight.getSilverlight to notify the page that a user
* has requested the Silverlight installer
* @type {Function}
*/
goog.silverlight.onGetSilverlight = null;
/**
* Called by Silverlight.waitForInstallCompletion when the page detects
* that Silverlight has been installed. The event handler is not called
* in upgrade scenarios.
*/
goog.silverlight.onSilverlightInstalled = function() {
window.location.reload(false);
};
/**
* Checks to see if the correct version is installed.
* @param {?string=} version The silverlight version.
* @return {boolean} Whether silverlight is installed.
*/
goog.silverlight.isInstalled = function(version) {
if (version == undefined)
version = null;
var isVersionSupported = false;
var container = null;
try {
var control = null;
var tryNS = false;
if (typeof ActiveXObject == 'undefined') {
try {
control = new ActiveXObject('AgControl.AgControl');
if (version === null) {
isVersionSupported = true;
} else if (control.isVersionSupported(version)) {
isVersionSupported = true;
}
control = null;
} catch (e) {
tryNS = true;
}
} else {
tryNS = true;
}
if (tryNS) {
var plugin = navigator.plugins['Silverlight Plug-In'];
if (plugin) {
if (version === null) {
isVersionSupported = true;
} else {
var actualVer = plugin.description;
if (actualVer === '1.0.30226.2')
actualVer = '2.0.30226.2';
var actualVerArray = actualVer.split('.');
while (actualVerArray.length > 3) {
actualVerArray.pop();
}
while (actualVerArray.length < 4) {
actualVerArray.push(0);
}
var reqVerArray = version.split('.');
while (reqVerArray.length > 4) {
reqVerArray.pop();
}
var requiredVersionPart;
var actualVersionPart;
var index = 0;
do {
requiredVersionPart = parseInt(reqVerArray[index], 10);
actualVersionPart = parseInt(actualVerArray[index], 10);
index++;
}
while (index < reqVerArray.length &&
requiredVersionPart === actualVersionPart);
if (requiredVersionPart <= actualVersionPart &&
!isNaN(requiredVersionPart)) {
isVersionSupported = true;
}
}
}
}
} catch (e) {
isVersionSupported = false;
}
return isVersionSupported;
};
/**
* Occasionally checks for Silverlight installation status. If it
* detects that Silverlight has been installed then it calls
* Silverlight.onSilverlightInstalled();. This is only supported
* if Silverlight was not previously installed on this computer.
*/
goog.silverlight.waitForInstallCompletion = function() {
if (!goog.silverlight.isBrowserRestartRequired &&
goog.silverlight.onSilverlightInstalled) {
try {
navigator.plugins.refresh();
} catch (e) {
}
if (goog.silverlight.isInstalled(null) &&
!goog.silverlight.__onSilverlightInstalledCalled) {
goog.silverlight.onSilverlightInstalled();
goog.silverlight.__onSilverlightInstalledCalled = true;
} else {
setTimeout(goog.silverlight.waitForInstallCompletion, 3000);
}
}
};
/**
* Performs startup tasks.
*/
goog.silverlight.startup = function() {
navigator.plugins.refresh();
goog.silverlight.isBrowserRestartRequired =
goog.silverlight.isInstalled(null);
if (!goog.silverlight.isBrowserRestartRequired) {
goog.silverlight.waitForInstallCompletion();
if (!goog.silverlight.__installationEventFired) {
goog.silverlight.onInstallRequired();
goog.silverlight.__installationEventFired = true;
}
} else if (window.navigator.mimeTypes) {
var mimeSL2 = navigator.mimeTypes['application/x-silverlight-2'];
var mimeSL2b2 = navigator.mimeTypes['application/x-silverlight-2-b2'];
var mimeSL2b1 = navigator.mimeTypes['application/x-silverlight-2-b1'];
var mimeHighestBeta = mimeSL2b1;
if (mimeSL2b2)
mimeHighestBeta = mimeSL2b2;
if (!mimeSL2 && (mimeSL2b1 || mimeSL2b2)) {
if (!goog.silverlight.__installationEventFired) {
goog.silverlight.onUpgradeRequired();
goog.silverlight.__installationEventFired = true;
}
} else if (mimeSL2 && mimeHighestBeta) {
if (mimeSL2.enabledPlugin &&
mimeHighestBeta.enabledPlugin) {
if (mimeSL2.enabledPlugin.description !=
mimeHighestBeta.enabledPlugin.description) {
if (!goog.silverlight.__installationEventFired) {
goog.silverlight.onRestartRequired();
goog.silverlight.__installationEventFired = true;
}
}
}
}
}
};
/**
* Inserts a Silverlight <object> tag or installation experience into
* the HTML DOM based on the current installed state of
* goog.silverlight.
* @param {string} source The source file.
* @param {Element} parentElement The place to render the tag.
* @param {(string|null)=} opt_id A DOM id for the tag.
* @param {Object=} opt_properties Properties for the tag. One of these should
* be version, to get the correct version of silverlight.
* @param {Array.<Function>=} opt_events An array of functions, keyed by
* the name of the event that they should be listening on (e.g.,
* "onload"). What a weird API. The keys should be quoted if you're
* compiling with property renaming on.
* @param {string=} opt_initParams The initParams property of SilverlightPlugin.
* @param {Object=} opt_userContext Context to fire the events in. doesn't
* really work correctly. Use goog.bind instead.
* @return {?string} The html for the object, if no rendering parent
* was specified.
*/
goog.silverlight.createObject =
function(source, parentElement, opt_id, opt_properties,
opt_events, opt_initParams, opt_userContext) {
var slPluginHelper = {};
var slProperties = opt_properties;
var slEvents = opt_events;
slPluginHelper.version = slProperties.version;
slProperties.source = source;
slPluginHelper.alt = slProperties.alt;
// Rename properties to their tag property names. For backwards compatibility
// with goog.silverlight.js version 1.0
if (opt_initParams)
slProperties.initParams = opt_initParams;
if (slProperties.isWindowless && !slProperties.windowless)
slProperties.windowless = slProperties.isWindowless;
if (slProperties.framerate && !slProperties.maxFramerate)
slProperties.maxFramerate = slProperties.framerate;
if (opt_id && !slProperties.id)
slProperties.id = opt_id;
// remove elements which are not to be added to the instantiation tag
delete slProperties.ignoreBrowserVer;
delete slProperties.inplaceInstallPrompt;
delete slProperties.version;
delete slProperties.isWindowless;
delete slProperties.framerate;
delete slProperties.data;
delete slProperties.src;
delete slProperties.alt;
// detect that the correct version of Silverlight is installed, else
// display install
var slPluginHtml;
if (goog.silverlight.isInstalled(slPluginHelper.version)) {
// move unknown events to the slProperties array
for (var name in slEvents) {
if (slEvents[name]) {
if (name == 'onLoad' && typeof slEvents[name] == 'function' &&
slEvents[name].length != 1) {
var onLoadHandler = slEvents[name];
slEvents[name] = function(sender) {
return onLoadHandler(
document.getElementById(/** @type {string} */ (opt_id)),
opt_userContext,
sender);
};
}
var handlerName = goog.silverlight.getHandlerName(slEvents[name]);
if (handlerName != null) {
slProperties[name] = handlerName;
slEvents[name] = null;
} else {
throw 'typeof events.' + name + " must be 'function' or 'string'";
}
}
}
slPluginHtml = goog.silverlight.buildHtml(slProperties);
} else {
// The control could not be instantiated. Show the installation prompt
slPluginHtml = goog.silverlight.buildPromptHtml(slPluginHelper);
}
// insert or return the HTML
if (parentElement) {
parentElement.innerHTML = slPluginHtml;
} else {
return slPluginHtml;
}
return null;
};
/**
* Create HTML that instantiates the control
* @param {Object} slProperties The properties of the object.
* @return {string} The HTML for the object.
*/
goog.silverlight.buildHtml = function(slProperties) {
var htmlBuilder = [];
htmlBuilder.push(
'<object type=\"application/x-silverlight\" ' +
'data="data:application/x-silverlight,"');
if (slProperties.id != null) {
htmlBuilder.push(
' id="' +
goog.silverlight.htmlAttributeEncode(slProperties.id) + '"');
}
if (slProperties.width != null) {
htmlBuilder.push(' width="' + slProperties.width + '"');
}
if (slProperties.height != null) {
htmlBuilder.push(' height="' + slProperties.height + '"');
}
htmlBuilder.push(' >');
delete slProperties.id;
delete slProperties.width;
delete slProperties.height;
for (var name in slProperties) {
if (slProperties[name]) {
htmlBuilder.push(
'<param name="' + goog.silverlight.htmlAttributeEncode(name) +
'" value="' +
goog.silverlight.htmlAttributeEncode(slProperties[name]) +
'" />');
}
}
htmlBuilder.push('<\/object>');
return htmlBuilder.join('');
};
/**
* takes a single parameter of all createObject
* parameters enclosed in {}
* @param {{source: string,
* parentElement: Element,
* id: string,
* properties: Object,
* events: Array.<Function>,
* initParams: string,
* context: Object}} params Named parameters of createObject.
* @return {?string} See #createObject.
*/
goog.silverlight.createObjectEx = function(params) {
// stupid hack to make compiler stop complaining.
var unused = {properties: 0, events: 0, context: 0};
var parameters = params;
var html = goog.silverlight.createObject(
parameters.source, parameters.parentElement, parameters.id,
parameters.properties, parameters.events, parameters.initParams,
parameters.context);
if (parameters.parentElement == null) {
return html;
}
return null;
};
/**
* Builds the HTML to prompt the user to download and install Silverlight
*
* @param {{version: string, alt: string}} slPluginHelper Alt is an alternate
* link to silverlight. If no alternate is specified, a default will
* be used with the given version number.
* @return {string} The UI.
*/
goog.silverlight.buildPromptHtml = function(slPluginHelper) {
var slPluginHtml = '';
var urlRoot = goog.silverlight.fwlinkRoot;
var version = slPluginHelper.version;
if (slPluginHelper.alt) {
slPluginHtml = slPluginHelper.alt;
} else {
if (!version) {
version = '';
}
slPluginHtml =
"<a href='javascript:goog.silverlight.getSilverlight(\"{1}\");' " +
"style='text-decoration: none;'><img src='{2}' " +
"alt='Get Microsoft Silverlight' style='border-style: none'/></a>";
slPluginHtml = slPluginHtml.replace('{1}', version);
slPluginHtml = slPluginHtml.replace('{2}', urlRoot + '108181');
}
return slPluginHtml;
};
/**
* Navigates the browser to the appropriate Silverlight installer
* @param {string} version The silverlight version.
*/
goog.silverlight.getSilverlight = function(version) {
if (goog.silverlight.onGetSilverlight) {
goog.silverlight.onGetSilverlight();
}
var shortVer = '';
var reqVerArray = String(version).split('.');
if (reqVerArray.length > 1) {
var majorNum = parseInt(reqVerArray[0], 10);
if (isNaN(majorNum) || majorNum < 2) {
shortVer = '1.0';
} else {
shortVer = reqVerArray[0] + '.' + reqVerArray[1];
}
}
var verArg = '';
if (shortVer.match(/^\d+\056\d+$/)) {
verArg = '&v=' + shortVer;
}
goog.silverlight.followFWLink('149156' + verArg);
};
/**
* Navigates to a url based on fwlinkid
* @param {string} linkid The link suffix.
*/
goog.silverlight.followFWLink = function(linkid) {
top.location = goog.silverlight.fwlinkRoot + String(linkid);
};
/**
* Encodes special characters in input strings as charcodes
* @param {?string} strInput String to encode, or null. If null
* is passed, we give null right back.
* @return {?string} The encoded string, or null.
*/
goog.silverlight.htmlAttributeEncode = function(strInput) {
var c;
var retVal = '';
if (strInput == null) {
return null;
}
for (var cnt = 0; cnt < strInput.length; cnt++) {
c = strInput.charCodeAt(cnt);
if (((c > 96) && (c < 123)) ||
((c > 64) && (c < 91)) ||
((c > 43) && (c < 58) && (c != 47)) ||
(c == 95)) {
retVal = retVal + String.fromCharCode(c);
} else {
retVal = retVal + '&#' + c + ';';
}
}
return retVal;
};
/**
* Default error handling function
* @param {SilverlightDependencyObject} sender dep object.
* @param {SilverlightErrorEventArgs} args The event.
*/
goog.silverlight.defaultErrorHandler = function(sender, args) {
var iErrorCode;
var errorType = args.errorType;
iErrorCode = args.errorCode;
var errMsg = '\nSilverlight error message \n';
errMsg += 'ErrorCode: ' + iErrorCode + '\n';
errMsg += 'ErrorType: ' + errorType + ' \n';
errMsg += 'Message: ' + args.errorMessage + ' \n';
if (errorType == 'ParserError') {
errMsg += 'XamlFile: ' + args.xamlFile + ' \n';
errMsg += 'Line: ' + args.lineNumber + ' \n';
errMsg += 'Position: ' + args.charPosition + ' \n';
} else if (errorType == 'RuntimeError') {
if (args.lineNumber != 0) {
errMsg += 'Line: ' + args.lineNumber + ' \n';
errMsg += 'Position: ' + args.charPosition + ' \n';
}
errMsg += 'MethodName: ' + args.methodName + ' \n';
}
alert(errMsg);
};
/**
* Releases event handler resources when the page is unloaded
*/
goog.silverlight.__cleanup = function() {
for (var i = goog.silverlight._silverlightCount - 1; i >= 0; i--) {
goog.global['__closure_slEvent' + i] = null;
}
goog.silverlight._silverlightCount = 0;
if (window.removeEventListener) {
window.removeEventListener('unload', goog.silverlight.__cleanup, false);
} else {
window.detachEvent('onunload', goog.silverlight.__cleanup);
}
};
/**
* Generates named event handlers for delegates.
*
* Attaches them to the global window with the name __closure_slEvent +
* a unique id.
*
* @param {Function|string} handler A function to export.
* @return {?string} The name of the handler.
*/
goog.silverlight.getHandlerName = function(handler) {
var handlerName = '';
if (typeof handler == 'string') {
handlerName = handler;
} else if (typeof handler == 'function') {
if (goog.silverlight._silverlightCount == 0) {
if (window.addEventListener) {
window.addEventListener('unload', goog.silverlight.__cleanup, false);
} else {
window.attachEvent('onunload', goog.silverlight.__cleanup);
}
}
var count = goog.silverlight._silverlightCount++;
handlerName = '__closure_slEvent' + count;
goog.global[handlerName] = handler;
} else {
handlerName = null;
}
return handlerName;
};
/**
* Frees a handler created by getHandlerName.
* @param {string} handlerName A handler name.
*/
goog.silverlight.disposeHandlerName = function(handlerName) {
delete goog.global[handlerName];
};
/**
* Called by version verification control to notify the page that
* an appropriate build of Silverlight is available. The page
* should respond by injecting the appropriate Silverlight control
*/
goog.silverlight.onRequiredVersionAvailable = function() {
};
/**
* Called by version verification control to notify the page that
* an appropriate build of Silverlight is installed but not loaded.
* The page should respond by injecting a clear and visible
* "Thanks for installing. Please restart your browser and return
* to mysite.com" or equivalent into the browser DOM
*/
goog.silverlight.onRestartRequired = function() {
};
/**
* Called by version verification control to notify the page that
* Silverlight must be upgraded. The page should respond by
* injecting a clear, visible, and actionable upgrade message into
* the DOM. The message must inform the user that they need to
* upgrade Silverlight to use the page. They are already somewhat
* familiar with the Silverlight product when they encounter this.
* Silverlight should be mentioned so the user expects to see that
* string in the installer UI. However, the Silverlight-powered
* application should be the focus of the solicitation. The user
* wants the app. Silverlight is a means to the app.
*
* The upgrade solicitation will have a button that directs
* the user to the Silverlight installer. Upon click the button
* should both kick off a download of the installer URL and replace
* the Upgrade text with "Thanks for downloading. When the upgarde
* is complete please restart your browser and return to
* mysite.com" or equivalent.
*
* Note: For a more interesting upgrade UX we can use Silverlight
* 1.0-style XAML for this upgrade experience. Contact PiotrP for
* details.
*/
goog.silverlight.onUpgradeRequired = function() {
};
/**
* Called by goog.silverlight.checkInstallStatus to notify the page
* that Silverlight has not been installed by this user.
* The page should respond by
* injecting a clear, visible, and actionable upgrade message into
* the DOM. The message must inform the user that they need to
* download and install components needed to use the page.
* Silverlight should be mentioned so the user expects to see that
* string in the installer UI. However, the Silverlight-powered
* application should be the focus of the solicitation. The user
* wants the app. Silverlight is a means to the app.
*
* The installation solicitation will have a button that directs
* the user to the Silverlight installer. Upon click the button
* should both kick off a download of the installer URL and replace
* the Upgrade text with "Thanks for downloading. When installation
* is complete you may need to refresh the page to view this
* content" or equivalent.
*/
goog.silverlight.onInstallRequired = function() {
};
/**
* This function should be called at the beginning of a web page's
* Silverlight error handler. It will determine if the required
* version of Silverlight is installed and available in the
* current process.
*
* During its execution the function will trigger one of the
* Silverlight installation state events, if appropriate.
*
* Sender and Args should be passed through from the calling
* onError handler's parameters.
*
* The associated Sivlerlight <object> tag must have
* minRuntimeVersion set and should have autoUpgrade set to false.
*
* @param {SilverlightDependencyObject} sender The sender.
* @param {SilverlightErrorEventArgs} args The event arguments.
* @return {boolean} Is the version available?
*/
goog.silverlight.isVersionAvailableOnError = function(sender, args) {
var retVal = false;
try {
if (args.errorCode == 8001 && !goog.silverlight.__installationEventFired) {
goog.silverlight.onUpgradeRequired();
goog.silverlight.__installationEventFired = true;
} else if (args.errorCode == 8002 &&
!goog.silverlight.__installationEventFired) {
goog.silverlight.onRestartRequired();
goog.silverlight.__installationEventFired = true;
}
// this handles upgrades from 1.0. That control did not
// understand the minRuntimeVerison parameter. It also
// did not know how to parse XAP files, so would throw
// Parse Error (5014). A Beta 2 control may throw 2106
else if (args.errorCode == 5014 || args.errorCode == 2106) {
if (goog.silverlight.__verifySilverlight2UpgradeSuccess(args.getHost())) {
retVal = true;
}
} else {
retVal = true;
}
} catch (e) {
}
return retVal;
};
/**
* This function should be called at the beginning of a web page's
* Silverlight onLoad handler. It will determine if the required
* version of Silverlight is installed and available in the
* current process.
*
* During its execution the function will trigger one of the
* Silverlight installation state events, if appropriate.
*
* Sender should be passed through from the calling
* onError handler's parameters.
*
* The associated Sivlerlight <object> tag must have
* minRuntimeVersion set and should have autoUpgrade set to false.
*
* @param {SilverlightDependencyObject} sender The dep object.
* @return {boolean} Is the version available?
*/
goog.silverlight.isVersionAvailableOnLoad = function(sender) {
var retVal = false;
try {
if (goog.silverlight.__verifySilverlight2UpgradeSuccess(sender.getHost())) {
retVal = true;
}
} catch (e) {
}
return retVal;
};
/**
* This internal function helps identify installation state by
* taking advantage of behavioral differences between the
* 1.0 and 2.0 releases of goog.silverlight.
* @param {SilverlightPlugin} host The dep object.
* @return {boolean} If verification succeeded.
*/
goog.silverlight.__verifySilverlight2UpgradeSuccess = function(host) {
var retVal = false;
var version = '4.0.50401';
var installationEvent = null;
try {
if (host.isVersionSupported(version + '.99')) {
installationEvent = goog.silverlight.onRequiredVersionAvailable;
retVal = true;
} else if (host.isVersionSupported(version + '.0')) {
installationEvent = goog.silverlight.onRestartRequired;
} else {
installationEvent = goog.silverlight.onUpgradeRequired;
}
if (installationEvent && !goog.silverlight.__installationEventFired) {
installationEvent();
goog.silverlight.__installationEventFired = true;
}
} catch (e) {
}
return retVal;
};
@@ -0,0 +1,138 @@
/**
* @license
* Portions of this code are from Silverlight.supportedUserAgent.js
* under the Ms-PL license.
*
* Silverlight.supportedUserAgent.js version 4.0.50401.0
*
* This file is provided by Microsoft as a helper file for websites that
* incorporate Silverlight Objects. This file is provided under the Microsoft
* Public License available at
* http://code.msdn.microsoft.com/SLsupportedUA/Project/License.aspx.
* You may not use or distribute this file or the code in this file except as
* expressly permitted under that license.
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*/
/**
* @fileoverview Modified version of Silverlight.supportedUserAgent.js
* to work with the compiler's advanced modes. The API should be the same,
* except that they're namespaced under goog.silverlight
* instead of Silverlight.
*
* @author nicksantos@google.com (Nick Santos) (ported to Closure)
*/
goog.provide('goog.silverlight.supportedUserAgent');
/**
* NOTE: This function is strongly tied to current implementations of web
* browsers. The implementation of this function will change over time to
* account for new Web browser developments. Visit
* http://code.msdn.microsoft.com/SLsupportedUA often to ensure that you have
* the latest version.
*
* Determines if the client browser is supported by Silverlight.
*
* @param {string} version
* determines if a particular version of Silverlight supports
* this browser. Acceptable values are "1.0" and "2.0".
* @param {string=} userAgent
* optional. User Agent string to be analized. If null then the
* current browsers user agent string will be used.
*
* @return {boolean} Whether the user agent is supported.
*/
goog.silverlight.supportedUserAgent = function(version, userAgent) {
try {
var ua = null;
if (userAgent) {
ua = userAgent;
} else {
ua = window.navigator.userAgent;
}
var slua = {OS: 'Unsupported', Browser: 'Unsupported'};
//Silverlight does not support pre-Windows NT platforms
if (ua.indexOf('Windows NT') >= 0 ||
ua.indexOf('Mozilla/4.0 (compatible; MSIE 6.0)') >= 0) {
slua.OS = 'Windows';
} else if (ua.indexOf('PPC Mac OS X') >= 0) {
slua.OS = 'MacPPC';
} else if (ua.indexOf('Intel Mac OS X') >= 0) {
slua.OS = 'MacIntel';
} else if (ua.indexOf('Linux') >= 0) {
slua.OS = 'Linux';
}
if (slua.OS != 'Unsupported') {
if (ua.indexOf('MSIE') >= 0) {
if (navigator.userAgent.indexOf('Win64') == -1) {
if (parseInt(ua.split('MSIE')[1], 10) >= 6) {
slua.Browser = 'MSIE';
}
}
} else if (ua.indexOf('Firefox') >= 0) {
var versionArr = ua.split('Firefox/')[1].split('.');
var major = parseInt(versionArr[0], 10);
if (major >= 2) {
slua.Browser = 'Firefox';
} else {
var minor = parseInt(versionArr[1], 10);
if ((major == 1) && (minor >= 5)) {
slua.Browser = 'Firefox';
}
}
} else if (ua.indexOf('Chrome') >= 0) {
slua.Browser = 'Chrome';
} else if (ua.indexOf('Safari') >= 0) {
slua.Browser = 'Safari';
}
}
var sl_version = parseInt(version, 10);
// detect all unsupported platform combinations (IE on Mac, Safari on Win)
var supUA = (!(// Unsupported OS
slua.OS == 'Unsupported' ||
// Unsupported Browser
slua.Browser == 'Unsupported' ||
// Safari is not supported on Windows
(slua.OS == 'Windows' && slua.Browser == 'Safari') ||
// IE is not supported on Mac
(slua.OS.indexOf('Mac') >= 0 && slua.Browser == 'MSIE') ||
// Google Chrome is not supported on Mac
(slua.OS.indexOf('Mac') >= 0 && slua.Browser == 'Chrome')));
// Detection of specific platform/versions
// Google Chrome only on Windows and only in SL4+
if ((slua.OS.indexOf('Windows') >= 0 &&
slua.Browser == 'Chrome' && sl_version < 4)) {
return false;
}
// detect unsupported MacPPC platforms (basically anything but 1.0)
if ((slua.OS == 'MacPPC') && (sl_version > 1)) {
return ((supUA && (slua.OS != 'MacPPC')));
}
// detect unsupported Linux platforms per Moonlight
if ((slua.OS == 'Linux') && (sl_version > 2)) {
return ((supUA && (slua.OS != 'Linux')));
}
// detect other unsupported 1.0 platforms (win2k)
if (version == '1.0') {
return (supUA && (ua.indexOf('Windows NT 5.0') < 0));
} else {
return (supUA);
}
} catch (e) {
return false;
}
};