Adding float-no-zero branch hosted build

This commit is contained in:
ahocevar
2014-03-07 10:55:12 +01:00
parent 84cad42f6d
commit bd9092199b
1664 changed files with 731463 additions and 0 deletions

View File

@@ -0,0 +1,108 @@
// Copyright 2007 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 Announcer that allows messages to be spoken by assistive
* technologies.
*/
goog.provide('goog.a11y.aria.Announcer');
goog.require('goog.Disposable');
goog.require('goog.a11y.aria');
goog.require('goog.a11y.aria.LivePriority');
goog.require('goog.a11y.aria.State');
goog.require('goog.dom');
goog.require('goog.object');
/**
* Class that allows messages to be spoken by assistive technologies that the
* user may have active.
*
* @param {goog.dom.DomHelper=} opt_domHelper DOM helper.
* @constructor
* @extends {goog.Disposable}
*/
goog.a11y.aria.Announcer = function(opt_domHelper) {
goog.base(this);
/**
* @type {goog.dom.DomHelper}
* @private
*/
this.domHelper_ = opt_domHelper || goog.dom.getDomHelper();
/**
* Map of priority to live region elements to use for communicating updates.
* Elements are created on demand.
* @type {Object.<goog.a11y.aria.LivePriority, Element>}
* @private
*/
this.liveRegions_ = {};
};
goog.inherits(goog.a11y.aria.Announcer, goog.Disposable);
/** @override */
goog.a11y.aria.Announcer.prototype.disposeInternal = function() {
goog.object.forEach(
this.liveRegions_, this.domHelper_.removeNode, this.domHelper_);
this.liveRegions_ = null;
this.domHelper_ = null;
goog.base(this, 'disposeInternal');
};
/**
* Announce a message to be read by any assistive technologies the user may
* have active.
* @param {string} message The message to announce to screen readers.
* @param {goog.a11y.aria.LivePriority=} opt_priority The priority of the
* message. Defaults to POLITE.
*/
goog.a11y.aria.Announcer.prototype.say = function(message, opt_priority) {
goog.dom.setTextContent(this.getLiveRegion_(
opt_priority || goog.a11y.aria.LivePriority.POLITE), message);
};
/**
* Returns an aria-live region that can be used to communicate announcements.
* @param {!goog.a11y.aria.LivePriority} priority The required priority.
* @return {Element} A live region of the requested priority.
* @private
*/
goog.a11y.aria.Announcer.prototype.getLiveRegion_ = function(priority) {
if (this.liveRegions_[priority]) {
return this.liveRegions_[priority];
}
var liveRegion;
liveRegion = this.domHelper_.createElement('div');
// Note that IE has a habit of declaring things that aren't display:none as
// invisible to third-party tools like JAWs, so we can't just use height:0.
liveRegion.style.position = 'absolute';
liveRegion.style.top = '-1000px';
liveRegion.style.height = '1px';
liveRegion.style.overflow = 'hidden';
goog.a11y.aria.setState(liveRegion, goog.a11y.aria.State.LIVE,
priority);
goog.a11y.aria.setState(liveRegion, goog.a11y.aria.State.ATOMIC,
'true');
this.domHelper_.getDocument().body.appendChild(liveRegion);
this.liveRegions_[priority] = liveRegion;
return liveRegion;
};

View File

@@ -0,0 +1,362 @@
// Copyright 2007 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 Utilities for adding, removing and setting ARIA roles and
* states as defined by W3C ARIA standard: http://www.w3.org/TR/wai-aria/
* All modern browsers have some form of ARIA support, so no browser checks are
* performed when adding ARIA to components.
*
*/
goog.provide('goog.a11y.aria');
goog.require('goog.a11y.aria.Role');
goog.require('goog.a11y.aria.State');
goog.require('goog.a11y.aria.datatables');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.object');
goog.require('goog.string');
/**
* ARIA states/properties prefix.
* @private
*/
goog.a11y.aria.ARIA_PREFIX_ = 'aria-';
/**
* ARIA role attribute.
* @private
*/
goog.a11y.aria.ROLE_ATTRIBUTE_ = 'role';
/**
* A list of tag names for which we don't need to set ARIA role and states
* because they have well supported semantics for screen readers or because
* they don't contain content to be made accessible.
* @private
*/
goog.a11y.aria.TAGS_WITH_ASSUMED_ROLES_ = [
goog.dom.TagName.A,
goog.dom.TagName.AREA,
goog.dom.TagName.BUTTON,
goog.dom.TagName.HEAD,
goog.dom.TagName.INPUT,
goog.dom.TagName.LINK,
goog.dom.TagName.MENU,
goog.dom.TagName.META,
goog.dom.TagName.OPTGROUP,
goog.dom.TagName.OPTION,
goog.dom.TagName.PROGRESS,
goog.dom.TagName.STYLE,
goog.dom.TagName.SELECT,
goog.dom.TagName.SOURCE,
goog.dom.TagName.TEXTAREA,
goog.dom.TagName.TITLE,
goog.dom.TagName.TRACK
];
/**
* Sets the role of an element. If the roleName is
* empty string or null, the role for the element is removed.
* We encourage clients to call the goog.a11y.aria.removeRole
* method instead of setting null and empty string values.
* Special handling for this case is added to ensure
* backword compatibility with existing code.
*
* @param {!Element} element DOM node to set role of.
* @param {!goog.a11y.aria.Role|string} roleName role name(s).
*/
goog.a11y.aria.setRole = function(element, roleName) {
if (!roleName) {
// Setting the ARIA role to empty string is not allowed
// by the ARIA standard.
goog.a11y.aria.removeRole(element);
} else {
if (goog.asserts.ENABLE_ASSERTS) {
goog.asserts.assert(goog.object.containsValue(
goog.a11y.aria.Role, roleName), 'No such ARIA role ' + roleName);
}
element.setAttribute(goog.a11y.aria.ROLE_ATTRIBUTE_, roleName);
}
};
/**
* Gets role of an element.
* @param {!Element} element DOM element to get role of.
* @return {?goog.a11y.aria.Role} ARIA Role name.
*/
goog.a11y.aria.getRole = function(element) {
var role = element.getAttribute(goog.a11y.aria.ROLE_ATTRIBUTE_);
return /** @type {goog.a11y.aria.Role} */ (role) || null;
};
/**
* Removes role of an element.
* @param {!Element} element DOM element to remove the role from.
*/
goog.a11y.aria.removeRole = function(element) {
element.removeAttribute(goog.a11y.aria.ROLE_ATTRIBUTE_);
};
/**
* Sets the state or property of an element.
* @param {!Element} element DOM node where we set state.
* @param {!(goog.a11y.aria.State|string)} stateName State attribute being set.
* Automatically adds prefix 'aria-' to the state name if the attribute is
* not an extra attribute.
* @param {string|boolean|number|!goog.array.ArrayLike.<string>} value Value
* for the state attribute.
*/
goog.a11y.aria.setState = function(element, stateName, value) {
if (goog.isArrayLike(value)) {
var array = /** @type {!goog.array.ArrayLike.<string>} */ (value);
value = array.join(' ');
}
var attrStateName = goog.a11y.aria.getAriaAttributeName_(stateName);
if (value === '' || value == undefined) {
var defaultValueMap = goog.a11y.aria.datatables.getDefaultValuesMap();
// Work around for browsers that don't properly support ARIA.
// According to the ARIA W3C standard, user agents should allow
// setting empty value which results in setting the default value
// for the ARIA state if such exists. The exact text from the ARIA W3C
// standard (http://www.w3.org/TR/wai-aria/states_and_properties):
// "When a value is indicated as the default, the user agent
// MUST follow the behavior prescribed by this value when the state or
// property is empty or undefined."
// The defaultValueMap contains the default values for the ARIA states
// and has as a key the goog.a11y.aria.State constant for the state.
if (stateName in defaultValueMap) {
element.setAttribute(attrStateName, defaultValueMap[stateName]);
} else {
element.removeAttribute(attrStateName);
}
} else {
element.setAttribute(attrStateName, value);
}
};
/**
* Remove the state or property for the element.
* @param {!Element} element DOM node where we set state.
* @param {!goog.a11y.aria.State} stateName State name.
*/
goog.a11y.aria.removeState = function(element, stateName) {
element.removeAttribute(goog.a11y.aria.getAriaAttributeName_(stateName));
};
/**
* Gets value of specified state or property.
* @param {!Element} element DOM node to get state from.
* @param {!goog.a11y.aria.State|string} stateName State name.
* @return {string} Value of the state attribute.
*/
goog.a11y.aria.getState = function(element, stateName) {
// TODO(user): return properly typed value result --
// boolean, number, string, null. We should be able to chain
// getState(...) and setState(...) methods.
var attr =
/** @type {string|number|boolean} */ (element.getAttribute(
goog.a11y.aria.getAriaAttributeName_(stateName)));
var isNullOrUndefined = attr == null || attr == undefined;
return isNullOrUndefined ? '' : String(attr);
};
/**
* Returns the activedescendant element for the input element by
* using the activedescendant ARIA property of the given element.
* @param {!Element} element DOM node to get activedescendant
* element for.
* @return {?Element} DOM node of the activedescendant, if found.
*/
goog.a11y.aria.getActiveDescendant = function(element) {
var id = goog.a11y.aria.getState(
element, goog.a11y.aria.State.ACTIVEDESCENDANT);
return goog.dom.getOwnerDocument(element).getElementById(id);
};
/**
* Sets the activedescendant ARIA property value for an element.
* If the activeElement is not null, it should have an id set.
* @param {!Element} element DOM node to set activedescendant ARIA property to.
* @param {?Element} activeElement DOM node being set as activedescendant.
*/
goog.a11y.aria.setActiveDescendant = function(element, activeElement) {
var id = '';
if (activeElement) {
id = activeElement.id;
goog.asserts.assert(id, 'The active element should have an id.');
}
goog.a11y.aria.setState(element, goog.a11y.aria.State.ACTIVEDESCENDANT, id);
};
/**
* Gets the label of the given element.
* @param {!Element} element DOM node to get label from.
* @return {string} label The label.
*/
goog.a11y.aria.getLabel = function(element) {
return goog.a11y.aria.getState(element, goog.a11y.aria.State.LABEL);
};
/**
* Sets the label of the given element.
* @param {!Element} element DOM node to set label to.
* @param {string} label The label to set.
*/
goog.a11y.aria.setLabel = function(element, label) {
goog.a11y.aria.setState(element, goog.a11y.aria.State.LABEL, label);
};
/**
* Asserts that the element has a role set if it's not an HTML element whose
* semantics is well supported by most screen readers.
* Only to be used internally by the ARIA library in goog.a11y.aria.*.
* @param {!Element} element The element to assert an ARIA role set.
* @param {!goog.array.ArrayLike.<string>} allowedRoles The child roles of
* the roles.
*/
goog.a11y.aria.assertRoleIsSetInternalUtil = function(element, allowedRoles) {
if (goog.array.contains(goog.a11y.aria.TAGS_WITH_ASSUMED_ROLES_,
element.tagName)) {
return;
}
var elementRole = /** @type {string}*/ (goog.a11y.aria.getRole(element));
goog.asserts.assert(elementRole != null,
'The element ARIA role cannot be null.');
goog.asserts.assert(goog.array.contains(allowedRoles, elementRole),
'Non existing or incorrect role set for element.' +
'The role set is "' + elementRole +
'". The role should be any of "' + allowedRoles +
'". Check the ARIA specification for more details ' +
'http://www.w3.org/TR/wai-aria/roles.');
};
/**
* Gets the boolean value of an ARIA state/property.
* Only to be used internally by the ARIA library in goog.a11y.aria.*.
* @param {!Element} element The element to get the ARIA state for.
* @param {!goog.a11y.aria.State|string} stateName the ARIA state name.
* @return {?boolean} Boolean value for the ARIA state value or null if
* the state value is not 'true' or 'false'.
*/
goog.a11y.aria.getBooleanStateInternalUtil = function(element, stateName) {
var stringValue = goog.a11y.aria.getState(element, stateName);
if (stringValue == 'true') {
return true;
}
if (stringValue == 'false') {
return false;
}
return null;
};
/**
* Gets the number value of an ARIA state/property.
* Only to be used internally by the ARIA library in goog.a11y.aria.*.
* @param {!Element} element The element to get the ARIA state for.
* @param {!goog.a11y.aria.State|string} stateName the ARIA state name.
* @return {?number} Number value for the ARIA state value or null if
* the state value is not a number.
*/
goog.a11y.aria.getNumberStateInternalUtil = function(element, stateName) {
var stringValue = goog.a11y.aria.getState(element, stateName);
if (goog.string.isNumeric(stringValue)) {
return goog.string.toNumber(stringValue);
}
return null;
};
/**
* Gets array of strings value of the specified state or
* property for the element.
* Only to be used internally by the ARIA library in goog.a11y.aria.*.
* @param {!Element} element DOM node to get state from.
* @param {!goog.a11y.aria.State} stateName State name.
* @return {!goog.array.ArrayLike.<string>} string Array
* value of the state attribute.
*/
goog.a11y.aria.getStringArrayStateInternalUtil = function(element, stateName) {
var attrValue = element.getAttribute(
goog.a11y.aria.getAriaAttributeName_(stateName));
return goog.a11y.aria.splitStringOnWhitespace_(attrValue);
};
/**
* Gets the string value of an ARIA state/property.
* Only to be used internally by the ARIA library in goog.a11y.aria.*.
* @param {!Element} element The element to get the ARIA state for.
* @param {!goog.a11y.aria.State|string} stateName the ARIA state name.
* @return {?string} String value for the ARIA state value or null if
* the state value is empty string.
*/
goog.a11y.aria.getStringStateInternalUtil = function(element, stateName) {
var stringValue = goog.a11y.aria.getState(element, stateName);
return stringValue || null;
};
/**
* Splits the input stringValue on whitespace.
* @param {string} stringValue The value of the string to split.
* @return {!goog.array.ArrayLike.<string>} string Array
* value as result of the split.
* @private
*/
goog.a11y.aria.splitStringOnWhitespace_ = function(stringValue) {
return stringValue ? stringValue.split(/\s+/) : [];
};
/**
* Adds the 'aria-' prefix to ariaName.
* @param {string} ariaName ARIA state/property name.
* @private
* @return {string} The ARIA attribute name with added 'aria-' prefix.
* @throws {Error} If no such attribute exists.
*/
goog.a11y.aria.getAriaAttributeName_ = function(ariaName) {
if (goog.asserts.ENABLE_ASSERTS) {
goog.asserts.assert(ariaName, 'ARIA attribute cannot be empty.');
goog.asserts.assert(goog.object.containsValue(
goog.a11y.aria.State, ariaName),
'No such ARIA attribute ' + ariaName);
}
return goog.a11y.aria.ARIA_PREFIX_ + ariaName;
};

View File

@@ -0,0 +1,389 @@
// Copyright 2013 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 The file contains generated enumerations for ARIA states
* and properties as defined by W3C ARIA standard:
* http://www.w3.org/TR/wai-aria/.
*
* This is auto-generated code. Do not manually edit! For more details
* about how to edit it via the generator check go/closure-ariagen.
*/
goog.provide('goog.a11y.aria.AutoCompleteValues');
goog.provide('goog.a11y.aria.CheckedValues');
goog.provide('goog.a11y.aria.DropEffectValues');
goog.provide('goog.a11y.aria.ExpandedValues');
goog.provide('goog.a11y.aria.GrabbedValues');
goog.provide('goog.a11y.aria.InvalidValues');
goog.provide('goog.a11y.aria.LivePriority');
goog.provide('goog.a11y.aria.OrientationValues');
goog.provide('goog.a11y.aria.PressedValues');
goog.provide('goog.a11y.aria.RelevantValues');
goog.provide('goog.a11y.aria.SelectedValues');
goog.provide('goog.a11y.aria.SortValues');
goog.provide('goog.a11y.aria.State');
/**
* ARIA states and properties.
* @enum {string}
*/
goog.a11y.aria.State = {
// ARIA property for setting the currently active descendant of an element,
// for example the selected item in a list box. Value: ID of an element.
ACTIVEDESCENDANT: 'activedescendant',
// ARIA property that, if true, indicates that all of a changed region should
// be presented, instead of only parts. Value: one of {true, false}.
ATOMIC: 'atomic',
// ARIA property to specify that input completion is provided. Value:
// one of {'inline', 'list', 'both', 'none'}.
AUTOCOMPLETE: 'autocomplete',
// ARIA state to indicate that an element and its subtree are being updated.
// Value: one of {true, false}.
BUSY: 'busy',
// ARIA state for a checked item. Value: one of {'true', 'false', 'mixed',
// undefined}.
CHECKED: 'checked',
// ARIA property that identifies the element or elements whose contents or
// presence are controlled by this element.
// Value: space-separated IDs of other elements.
CONTROLS: 'controls',
// ARIA property that identifies the element or elements that describe
// this element. Value: space-separated IDs of other elements.
DESCRIBEDBY: 'describedby',
// ARIA state for a disabled item. Value: one of {true, false}.
DISABLED: 'disabled',
// ARIA property that indicates what functions can be performed when a
// dragged object is released on the drop target. Value: one of
// {'copy', 'move', 'link', 'execute', 'popup', 'none'}.
DROPEFFECT: 'dropeffect',
// ARIA state for setting whether the element like a tree node is expanded.
// Value: one of {true, false, undefined}.
EXPANDED: 'expanded',
// ARIA property that identifies the next element (or elements) in the
// recommended reading order of content. Value: space-separated ids of
// elements to flow to.
FLOWTO: 'flowto',
// ARIA state that indicates an element's "grabbed" state in drag-and-drop.
// Value: one of {true, false, undefined}.
GRABBED: 'grabbed',
// ARIA property indicating whether the element has a popup.
// Value: one of {true, false}.
HASPOPUP: 'haspopup',
// ARIA state indicating that the element is not visible or perceivable
// to any user. Value: one of {true, false}.
HIDDEN: 'hidden',
// ARIA state indicating that the entered value does not conform. Value:
// one of {false, true, 'grammar', 'spelling'}
INVALID: 'invalid',
// ARIA property that provides a label to override any other text, value, or
// contents used to describe this element. Value: string.
LABEL: 'label',
// ARIA property for setting the element which labels another element.
// Value: space-separated IDs of elements.
LABELLEDBY: 'labelledby',
// ARIA property for setting the level of an element in the hierarchy.
// Value: integer.
LEVEL: 'level',
// ARIA property indicating that an element will be updated, and
// describes the types of updates the user agents, assistive technologies,
// and user can expect from the live region. Value: one of {'off', 'polite',
// 'assertive'}.
LIVE: 'live',
// ARIA property indicating whether a text box can accept multiline input.
// Value: one of {true, false}.
MULTILINE: 'multiline',
// ARIA property indicating if the user may select more than one item.
// Value: one of {true, false}.
MULTISELECTABLE: 'multiselectable',
// ARIA property indicating if the element is horizontal or vertical.
// Value: one of {'vertical', 'horizontal'}.
ORIENTATION: 'orientation',
// ARIA property creating a visual, functional, or contextual parent/child
// relationship when the DOM hierarchy can't be used to represent it.
// Value: Space-separated IDs of elements.
OWNS: 'owns',
// ARIA property that defines an element's number of position in a list.
// Value: integer.
POSINSET: 'posinset',
// ARIA state for a pressed item.
// Value: one of {true, false, undefined, 'mixed'}.
PRESSED: 'pressed',
// ARIA property indicating that an element is not editable.
// Value: one of {true, false}.
READONLY: 'readonly',
// ARIA property indicating that change notifications within this subtree
// of a live region should be announced. Value: one of {'additions',
// 'removals', 'text', 'all', 'additions text'}.
RELEVANT: 'relevant',
// ARIA property indicating that user input is required on this element
// before a form may be submitted. Value: one of {true, false}.
REQUIRED: 'required',
// ARIA state for setting the currently selected item in the list.
// Value: one of {true, false, undefined}.
SELECTED: 'selected',
// ARIA property defining the number of items in a list. Value: integer.
SETSIZE: 'setsize',
// ARIA property indicating if items are sorted. Value: one of {'ascending',
// 'descending', 'none', 'other'}.
SORT: 'sort',
// ARIA property for slider maximum value. Value: number.
VALUEMAX: 'valuemax',
// ARIA property for slider minimum value. Value: number.
VALUEMIN: 'valuemin',
// ARIA property for slider active value. Value: number.
VALUENOW: 'valuenow',
// ARIA property for slider active value represented as text.
// Value: string.
VALUETEXT: 'valuetext'
};
/**
* ARIA state values for AutoCompleteValues.
* @enum {string}
*/
goog.a11y.aria.AutoCompleteValues = {
// The system provides text after the caret as a suggestion
// for how to complete the field.
INLINE: 'inline',
// A list of choices appears from which the user can choose,
// but the edit box retains focus.
LIST: 'list',
// A list of choices appears and the currently selected suggestion
// also appears inline.
BOTH: 'both',
// No input completion suggestions are provided.
NONE: 'none'
};
/**
* ARIA state values for DropEffectValues.
* @enum {string}
*/
goog.a11y.aria.DropEffectValues = {
// A duplicate of the source object will be dropped into the target.
COPY: 'copy',
// The source object will be removed from its current location
// and dropped into the target.
MOVE: 'move',
// A reference or shortcut to the dragged object
// will be created in the target object.
LINK: 'link',
// A function supported by the drop target is
// executed, using the drag source as an input.
EXECUTE: 'execute',
// There is a popup menu or dialog that allows the user to choose
// one of the drag operations (copy, move, link, execute) and any other
// drag functionality, such as cancel.
POPUP: 'popup',
// No operation can be performed; effectively
// cancels the drag operation if an attempt is made to drop on this object.
NONE: 'none'
};
/**
* ARIA state values for LivePriority.
* @enum {string}
*/
goog.a11y.aria.LivePriority = {
// Updates to the region will not be presented to the user
// unless the assitive technology is currently focused on that region.
OFF: 'off',
// (Background change) Assistive technologies SHOULD announce
// updates at the next graceful opportunity, such as at the end of
// speaking the current sentence or when the user pauses typing.
POLITE: 'polite',
// This information has the highest priority and assistive
// technologies SHOULD notify the user immediately.
// Because an interruption may disorient users or cause them to not complete
// their current task, authors SHOULD NOT use the assertive value unless the
// interruption is imperative.
ASSERTIVE: 'assertive'
};
/**
* ARIA state values for OrientationValues.
* @enum {string}
*/
goog.a11y.aria.OrientationValues = {
// The element is oriented vertically.
VERTICAL: 'vertical',
// The element is oriented horizontally.
HORIZONTAL: 'horizontal'
};
/**
* ARIA state values for RelevantValues.
* @enum {string}
*/
goog.a11y.aria.RelevantValues = {
// Element nodes are added to the DOM within the live region.
ADDITIONS: 'additions',
// Text or element nodes within the live region are removed from the DOM.
REMOVALS: 'removals',
// Text is added to any DOM descendant nodes of the live region.
TEXT: 'text',
// Equivalent to the combination of all values, "additions removals text".
ALL: 'all'
};
/**
* ARIA state values for SortValues.
* @enum {string}
*/
goog.a11y.aria.SortValues = {
// Items are sorted in ascending order by this column.
ASCENDING: 'ascending',
// Items are sorted in descending order by this column.
DESCENDING: 'descending',
// There is no defined sort applied to the column.
NONE: 'none',
// A sort algorithm other than ascending or descending has been applied.
OTHER: 'other'
};
/**
* ARIA state values for CheckedValues.
* @enum {string}
*/
goog.a11y.aria.CheckedValues = {
// The selectable element is checked.
TRUE: 'true',
// The selectable element is not checked.
FALSE: 'false',
// Indicates a mixed mode value for a tri-state
// checkbox or menuitemcheckbox.
MIXED: 'mixed',
// The element does not support being checked.
UNDEFINED: 'undefined'
};
/**
* ARIA state values for ExpandedValues.
* @enum {string}
*/
goog.a11y.aria.ExpandedValues = {
// The element, or another grouping element it controls, is expanded.
TRUE: 'true',
// The element, or another grouping element it controls, is collapsed.
FALSE: 'false',
// The element, or another grouping element
// it controls, is neither expandable nor collapsible; all its
// child elements are shown or there are no child elements.
UNDEFINED: 'undefined'
};
/**
* ARIA state values for GrabbedValues.
* @enum {string}
*/
goog.a11y.aria.GrabbedValues = {
// Indicates that the element has been "grabbed" for dragging.
TRUE: 'true',
// Indicates that the element supports being dragged.
FALSE: 'false',
// Indicates that the element does not support being dragged.
UNDEFINED: 'undefined'
};
/**
* ARIA state values for InvalidValues.
* @enum {string}
*/
goog.a11y.aria.InvalidValues = {
// There are no detected errors in the value.
FALSE: 'false',
// The value entered by the user has failed validation.
TRUE: 'true',
// A grammatical error was detected.
GRAMMAR: 'grammar',
// A spelling error was detected.
SPELLING: 'spelling'
};
/**
* ARIA state values for PressedValues.
* @enum {string}
*/
goog.a11y.aria.PressedValues = {
// The element is pressed.
TRUE: 'true',
// The element supports being pressed but is not currently pressed.
FALSE: 'false',
// Indicates a mixed mode value for a tri-state toggle button.
MIXED: 'mixed',
// The element does not support being pressed.
UNDEFINED: 'undefined'
};
/**
* ARIA state values for SelectedValues.
* @enum {string}
*/
goog.a11y.aria.SelectedValues = {
// The selectable element is selected.
TRUE: 'true',
// The selectable element is not selected.
FALSE: 'false',
// The element is not selectable.
UNDEFINED: 'undefined'
};

View File

@@ -0,0 +1,68 @@
// Copyright 2013 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 The file contains data tables generated from the ARIA
* standard schema http://www.w3.org/TR/wai-aria/.
*
* This is auto-generated code. Do not manually edit!
*/
goog.provide('goog.a11y.aria.datatables');
goog.require('goog.a11y.aria.State');
goog.require('goog.object');
/**
* A map that contains mapping between an ARIA state and the default value
* for it. Note that not all ARIA states have default values.
*
* @type {Object.<!(goog.a11y.aria.State|string), (string|boolean|number)>}
*/
goog.a11y.aria.DefaultStateValueMap_;
/**
* A method that creates a map that contains mapping between an ARIA state and
* the default value for it. Note that not all ARIA states have default values.
*
* @return {Object.<!(goog.a11y.aria.State|string), (string|boolean|number)>}
* The names for each of the notification methods.
*/
goog.a11y.aria.datatables.getDefaultValuesMap = function() {
if (!goog.a11y.aria.DefaultStateValueMap_) {
goog.a11y.aria.DefaultStateValueMap_ = goog.object.create(
goog.a11y.aria.State.ATOMIC, false,
goog.a11y.aria.State.AUTOCOMPLETE, 'none',
goog.a11y.aria.State.DROPEFFECT, 'none',
goog.a11y.aria.State.HASPOPUP, false,
goog.a11y.aria.State.LIVE, 'off',
goog.a11y.aria.State.MULTILINE, false,
goog.a11y.aria.State.MULTISELECTABLE, false,
goog.a11y.aria.State.ORIENTATION, 'vertical',
goog.a11y.aria.State.READONLY, false,
goog.a11y.aria.State.RELEVANT, 'additions text',
goog.a11y.aria.State.REQUIRED, false,
goog.a11y.aria.State.SORT, 'none',
goog.a11y.aria.State.BUSY, false,
goog.a11y.aria.State.DISABLED, false,
goog.a11y.aria.State.HIDDEN, false,
goog.a11y.aria.State.INVALID, 'false');
}
return goog.a11y.aria.DefaultStateValueMap_;
};

View File

@@ -0,0 +1,216 @@
// Copyright 2013 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 The file contains generated enumerations for ARIA roles
* as defined by W3C ARIA standard: http://www.w3.org/TR/wai-aria/.
*
* This is auto-generated code. Do not manually edit! For more details
* about how to edit it via the generator check go/closure-ariagen.
*/
goog.provide('goog.a11y.aria.Role');
/**
* ARIA role values.
* @enum {string}
*/
goog.a11y.aria.Role = {
// ARIA role for an alert element that doesn't need to be explicitly closed.
ALERT: 'alert',
// ARIA role for an alert dialog element that takes focus and must be closed.
ALERTDIALOG: 'alertdialog',
// ARIA role for an application that implements its own keyboard navigation.
APPLICATION: 'application',
// ARIA role for an article.
ARTICLE: 'article',
// ARIA role for a banner containing mostly site content, not page content.
BANNER: 'banner',
// ARIA role for a button element.
BUTTON: 'button',
// ARIA role for a checkbox button element; use with the CHECKED state.
CHECKBOX: 'checkbox',
// ARIA role for a column header of a table or grid.
COLUMNHEADER: 'columnheader',
// ARIA role for a combo box element.
COMBOBOX: 'combobox',
// ARIA role for a supporting section of the document.
COMPLEMENTARY: 'complementary',
// ARIA role for a large perceivable region that contains information
// about the parent document.
CONTENTINFO: 'contentinfo',
// ARIA role for a definition of a term or concept.
DEFINITION: 'definition',
// ARIA role for a dialog, some descendant must take initial focus.
DIALOG: 'dialog',
// ARIA role for a directory, like a table of contents.
DIRECTORY: 'directory',
// ARIA role for a part of a page that's a document, not a web application.
DOCUMENT: 'document',
// ARIA role for a landmark region logically considered one form.
FORM: 'form',
// ARIA role for an interactive control of tabular data.
GRID: 'grid',
// ARIA role for a cell in a grid.
GRIDCELL: 'gridcell',
// ARIA role for a group of related elements like tree item siblings.
GROUP: 'group',
// ARIA role for a heading element.
HEADING: 'heading',
// ARIA role for a container of elements that together comprise one image.
IMG: 'img',
// ARIA role for a link.
LINK: 'link',
// ARIA role for a list of non-interactive list items.
LIST: 'list',
// ARIA role for a listbox.
LISTBOX: 'listbox',
// ARIA role for a list item.
LISTITEM: 'listitem',
// ARIA role for a live region where new information is added.
LOG: 'log',
// ARIA landmark role for the main content in a document. Use only once.
MAIN: 'main',
// ARIA role for a live region of non-essential information that changes.
MARQUEE: 'marquee',
// ARIA role for a mathematical expression.
MATH: 'math',
// ARIA role for a popup menu.
MENU: 'menu',
// ARIA role for a menubar element containing menu elements.
MENUBAR: 'menubar',
// ARIA role for menu item elements.
MENU_ITEM: 'menuitem',
// ARIA role for a checkbox box element inside a menu.
MENU_ITEM_CHECKBOX: 'menuitemcheckbox',
// ARIA role for a radio button element inside a menu.
MENU_ITEM_RADIO: 'menuitemradio',
// ARIA landmark role for a collection of navigation links.
NAVIGATION: 'navigation',
// ARIA role for a section ancillary to the main content.
NOTE: 'note',
// ARIA role for option items that are children of combobox, listbox, menu,
// radiogroup, or tree elements.
OPTION: 'option',
// ARIA role for ignorable cosmetic elements with no semantic significance.
PRESENTATION: 'presentation',
// ARIA role for a progress bar element.
PROGRESSBAR: 'progressbar',
// ARIA role for a radio button element.
RADIO: 'radio',
// ARIA role for a group of connected radio button elements.
RADIOGROUP: 'radiogroup',
// ARIA role for an important region of the page.
REGION: 'region',
// ARIA role for a row of cells in a grid.
ROW: 'row',
// ARIA role for a group of one or more rows in a grid.
ROWGROUP: 'rowgroup',
// ARIA role for a row header of a table or grid.
ROWHEADER: 'rowheader',
// ARIA role for a scrollbar element.
SCROLLBAR: 'scrollbar',
// ARIA landmark role for a part of the page providing search functionality.
SEARCH: 'search',
// ARIA role for a menu separator.
SEPARATOR: 'separator',
// ARIA role for a slider.
SLIDER: 'slider',
// ARIA role for a spin button.
SPINBUTTON: 'spinbutton',
// ARIA role for a live region with advisory info less severe than an alert.
STATUS: 'status',
// ARIA role for a tab button.
TAB: 'tab',
// ARIA role for a tab bar (i.e. a list of tab buttons).
TAB_LIST: 'tablist',
// ARIA role for a tab page (i.e. the element holding tab contents).
TAB_PANEL: 'tabpanel',
// ARIA role for a textbox element.
TEXTBOX: 'textbox',
// ARIA role for an element displaying elapsed time or time remaining.
TIMER: 'timer',
// ARIA role for a toolbar element.
TOOLBAR: 'toolbar',
// ARIA role for a tooltip element.
TOOLTIP: 'tooltip',
// ARIA role for a tree.
TREE: 'tree',
// ARIA role for a grid whose rows can be expanded and collapsed like a tree.
TREEGRID: 'treegrid',
// ARIA role for a tree item that sometimes may be expanded or collapsed.
TREEITEM: 'treeitem'
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,293 @@
// Copyright 2008 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 Utilities to check the preconditions, postconditions and
* invariants runtime.
*
* Methods in this package should be given special treatment by the compiler
* for type-inference. For example, <code>goog.asserts.assert(foo)</code>
* will restrict <code>foo</code> to a truthy value.
*
* The compiler has an option to disable asserts. So code like:
* <code>
* var x = goog.asserts.assert(foo()); goog.asserts.assert(bar());
* </code>
* will be transformed into:
* <code>
* var x = foo();
* </code>
* The compiler will leave in foo() (because its return value is used),
* but it will remove bar() because it assumes it does not have side-effects.
*
*/
goog.provide('goog.asserts');
goog.provide('goog.asserts.AssertionError');
goog.require('goog.debug.Error');
goog.require('goog.string');
/**
* @define {boolean} Whether to strip out asserts or to leave them in.
*/
goog.define('goog.asserts.ENABLE_ASSERTS', goog.DEBUG);
/**
* Error object for failed assertions.
* @param {string} messagePattern The pattern that was used to form message.
* @param {!Array.<*>} messageArgs The items to substitute into the pattern.
* @constructor
* @extends {goog.debug.Error}
*/
goog.asserts.AssertionError = function(messagePattern, messageArgs) {
messageArgs.unshift(messagePattern);
goog.debug.Error.call(this, goog.string.subs.apply(null, messageArgs));
// Remove the messagePattern afterwards to avoid permenantly modifying the
// passed in array.
messageArgs.shift();
/**
* The message pattern used to format the error message. Error handlers can
* use this to uniquely identify the assertion.
* @type {string}
*/
this.messagePattern = messagePattern;
};
goog.inherits(goog.asserts.AssertionError, goog.debug.Error);
/** @override */
goog.asserts.AssertionError.prototype.name = 'AssertionError';
/**
* Throws an exception with the given message and "Assertion failed" prefixed
* onto it.
* @param {string} defaultMessage The message to use if givenMessage is empty.
* @param {Array.<*>} defaultArgs The substitution arguments for defaultMessage.
* @param {string|undefined} givenMessage Message supplied by the caller.
* @param {Array.<*>} givenArgs The substitution arguments for givenMessage.
* @throws {goog.asserts.AssertionError} When the value is not a number.
* @private
*/
goog.asserts.doAssertFailure_ =
function(defaultMessage, defaultArgs, givenMessage, givenArgs) {
var message = 'Assertion failed';
if (givenMessage) {
message += ': ' + givenMessage;
var args = givenArgs;
} else if (defaultMessage) {
message += ': ' + defaultMessage;
args = defaultArgs;
}
// The '' + works around an Opera 10 bug in the unit tests. Without it,
// a stack trace is added to var message above. With this, a stack trace is
// not added until this line (it causes the extra garbage to be added after
// the assertion message instead of in the middle of it).
throw new goog.asserts.AssertionError('' + message, args || []);
};
/**
* Checks if the condition evaluates to true if goog.asserts.ENABLE_ASSERTS is
* true.
* @param {*} condition The condition to check.
* @param {string=} opt_message Error message in case of failure.
* @param {...*} var_args The items to substitute into the failure message.
* @return {*} The value of the condition.
* @throws {goog.asserts.AssertionError} When the condition evaluates to false.
*/
goog.asserts.assert = function(condition, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !condition) {
goog.asserts.doAssertFailure_('', null, opt_message,
Array.prototype.slice.call(arguments, 2));
}
return condition;
};
/**
* Fails if goog.asserts.ENABLE_ASSERTS is true. This function is useful in case
* when we want to add a check in the unreachable area like switch-case
* statement:
*
* <pre>
* switch(type) {
* case FOO: doSomething(); break;
* case BAR: doSomethingElse(); break;
* default: goog.assert.fail('Unrecognized type: ' + type);
* // We have only 2 types - "default:" section is unreachable code.
* }
* </pre>
*
* @param {string=} opt_message Error message in case of failure.
* @param {...*} var_args The items to substitute into the failure message.
* @throws {goog.asserts.AssertionError} Failure.
*/
goog.asserts.fail = function(opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS) {
throw new goog.asserts.AssertionError(
'Failure' + (opt_message ? ': ' + opt_message : ''),
Array.prototype.slice.call(arguments, 1));
}
};
/**
* Checks if the value is a number if goog.asserts.ENABLE_ASSERTS is true.
* @param {*} value The value to check.
* @param {string=} opt_message Error message in case of failure.
* @param {...*} var_args The items to substitute into the failure message.
* @return {number} The value, guaranteed to be a number when asserts enabled.
* @throws {goog.asserts.AssertionError} When the value is not a number.
*/
goog.asserts.assertNumber = function(value, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !goog.isNumber(value)) {
goog.asserts.doAssertFailure_('Expected number but got %s: %s.',
[goog.typeOf(value), value], opt_message,
Array.prototype.slice.call(arguments, 2));
}
return /** @type {number} */ (value);
};
/**
* Checks if the value is a string if goog.asserts.ENABLE_ASSERTS is true.
* @param {*} value The value to check.
* @param {string=} opt_message Error message in case of failure.
* @param {...*} var_args The items to substitute into the failure message.
* @return {string} The value, guaranteed to be a string when asserts enabled.
* @throws {goog.asserts.AssertionError} When the value is not a string.
*/
goog.asserts.assertString = function(value, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !goog.isString(value)) {
goog.asserts.doAssertFailure_('Expected string but got %s: %s.',
[goog.typeOf(value), value], opt_message,
Array.prototype.slice.call(arguments, 2));
}
return /** @type {string} */ (value);
};
/**
* Checks if the value is a function if goog.asserts.ENABLE_ASSERTS is true.
* @param {*} value The value to check.
* @param {string=} opt_message Error message in case of failure.
* @param {...*} var_args The items to substitute into the failure message.
* @return {!Function} The value, guaranteed to be a function when asserts
* enabled.
* @throws {goog.asserts.AssertionError} When the value is not a function.
*/
goog.asserts.assertFunction = function(value, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !goog.isFunction(value)) {
goog.asserts.doAssertFailure_('Expected function but got %s: %s.',
[goog.typeOf(value), value], opt_message,
Array.prototype.slice.call(arguments, 2));
}
return /** @type {!Function} */ (value);
};
/**
* Checks if the value is an Object if goog.asserts.ENABLE_ASSERTS is true.
* @param {*} value The value to check.
* @param {string=} opt_message Error message in case of failure.
* @param {...*} var_args The items to substitute into the failure message.
* @return {!Object} The value, guaranteed to be a non-null object.
* @throws {goog.asserts.AssertionError} When the value is not an object.
*/
goog.asserts.assertObject = function(value, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !goog.isObject(value)) {
goog.asserts.doAssertFailure_('Expected object but got %s: %s.',
[goog.typeOf(value), value],
opt_message, Array.prototype.slice.call(arguments, 2));
}
return /** @type {!Object} */ (value);
};
/**
* Checks if the value is an Array if goog.asserts.ENABLE_ASSERTS is true.
* @param {*} value The value to check.
* @param {string=} opt_message Error message in case of failure.
* @param {...*} var_args The items to substitute into the failure message.
* @return {!Array} The value, guaranteed to be a non-null array.
* @throws {goog.asserts.AssertionError} When the value is not an array.
*/
goog.asserts.assertArray = function(value, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !goog.isArray(value)) {
goog.asserts.doAssertFailure_('Expected array but got %s: %s.',
[goog.typeOf(value), value], opt_message,
Array.prototype.slice.call(arguments, 2));
}
return /** @type {!Array} */ (value);
};
/**
* Checks if the value is a boolean if goog.asserts.ENABLE_ASSERTS is true.
* @param {*} value The value to check.
* @param {string=} opt_message Error message in case of failure.
* @param {...*} var_args The items to substitute into the failure message.
* @return {boolean} The value, guaranteed to be a boolean when asserts are
* enabled.
* @throws {goog.asserts.AssertionError} When the value is not a boolean.
*/
goog.asserts.assertBoolean = function(value, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !goog.isBoolean(value)) {
goog.asserts.doAssertFailure_('Expected boolean but got %s: %s.',
[goog.typeOf(value), value], opt_message,
Array.prototype.slice.call(arguments, 2));
}
return /** @type {boolean} */ (value);
};
/**
* Checks if the value is an instance of the user-defined type if
* goog.asserts.ENABLE_ASSERTS is true.
*
* The compiler may tighten the type returned by this function.
*
* @param {*} value The value to check.
* @param {function(new: T, ...)} type A user-defined constructor.
* @param {string=} opt_message Error message in case of failure.
* @param {...*} var_args The items to substitute into the failure message.
* @throws {goog.asserts.AssertionError} When the value is not an instance of
* type.
* @return {!T}
* @template T
*/
goog.asserts.assertInstanceof = function(value, type, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !(value instanceof type)) {
goog.asserts.doAssertFailure_('instanceof check failed.', null,
opt_message, Array.prototype.slice.call(arguments, 3));
}
return value;
};
/**
* Checks that no enumerable keys are present in Object.prototype. Such keys
* would break most code that use {@code for (var ... in ...)} loops.
*/
goog.asserts.assertObjectPrototypeIsIntact = function() {
for (var key in Object.prototype) {
goog.asserts.fail(key + ' should not be enumerable in Object.prototype.');
}
};

View File

@@ -0,0 +1,270 @@
// Copyright 2012 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 delayed callback that pegs to the next animation frame
* instead of a user-configurable timeout.
*
*/
goog.provide('goog.async.AnimationDelay');
goog.require('goog.Disposable');
goog.require('goog.events');
goog.require('goog.functions');
// TODO(nicksantos): Should we factor out the common code between this and
// goog.async.Delay? I'm not sure if there's enough code for this to really
// make sense. Subclassing seems like the wrong approach for a variety of
// reasons. Maybe there should be a common interface?
/**
* A delayed callback that pegs to the next animation frame
* instead of a user configurable timeout. By design, this should have
* the same interface as goog.async.Delay.
*
* Uses requestAnimationFrame and friends when available, but falls
* back to a timeout of goog.async.AnimationDelay.TIMEOUT.
*
* For more on requestAnimationFrame and how you can use it to create smoother
* animations, see:
* @see http://paulirish.com/2011/requestanimationframe-for-smart-animating/
*
* @param {function(number)} listener Function to call when the delay completes.
* Will be passed the timestamp when it's called, in unix ms.
* @param {Window=} opt_window The window object to execute the delay in.
* Defaults to the global object.
* @param {Object=} opt_handler The object scope to invoke the function in.
* @constructor
* @extends {goog.Disposable}
*/
goog.async.AnimationDelay = function(listener, opt_window, opt_handler) {
goog.base(this);
/**
* The function that will be invoked after a delay.
* @type {function(number)}
* @private
*/
this.listener_ = listener;
/**
* The object context to invoke the callback in.
* @type {Object|undefined}
* @private
*/
this.handler_ = opt_handler;
/**
* @type {Window}
* @private
*/
this.win_ = opt_window || window;
/**
* Cached callback function invoked when the delay finishes.
* @type {function()}
* @private
*/
this.callback_ = goog.bind(this.doAction_, this);
};
goog.inherits(goog.async.AnimationDelay, goog.Disposable);
/**
* Identifier of the active delay timeout, or event listener,
* or null when inactive.
* @type {goog.events.Key|number|null}
* @private
*/
goog.async.AnimationDelay.prototype.id_ = null;
/**
* If we're using dom listeners.
* @type {?boolean}
* @private
*/
goog.async.AnimationDelay.prototype.usingListeners_ = false;
/**
* Default wait timeout for animations (in milliseconds). Only used for timed
* animation, which uses a timer (setTimeout) to schedule animation.
*
* @type {number}
* @const
*/
goog.async.AnimationDelay.TIMEOUT = 20;
/**
* Name of event received from the requestAnimationFrame in Firefox.
*
* @type {string}
* @const
* @private
*/
goog.async.AnimationDelay.MOZ_BEFORE_PAINT_EVENT_ = 'MozBeforePaint';
/**
* Starts the delay timer. The provided listener function will be called
* before the next animation frame.
*/
goog.async.AnimationDelay.prototype.start = function() {
this.stop();
this.usingListeners_ = false;
var raf = this.getRaf_();
var cancelRaf = this.getCancelRaf_();
if (raf && !cancelRaf && this.win_.mozRequestAnimationFrame) {
// Because Firefox (Gecko) runs animation in separate threads, it also saves
// time by running the requestAnimationFrame callbacks in that same thread.
// Sadly this breaks the assumption of implicit thread-safety in JS, and can
// thus create thread-based inconsistencies on counters etc.
//
// Calling cycleAnimations_ using the MozBeforePaint event instead of as
// callback fixes this.
//
// Trigger this condition only if the mozRequestAnimationFrame is available,
// but not the W3C requestAnimationFrame function (as in draft) or the
// equivalent cancel functions.
this.id_ = goog.events.listen(
this.win_,
goog.async.AnimationDelay.MOZ_BEFORE_PAINT_EVENT_,
this.callback_);
this.win_.mozRequestAnimationFrame(null);
this.usingListeners_ = true;
} else if (raf && cancelRaf) {
this.id_ = raf.call(this.win_, this.callback_);
} else {
this.id_ = this.win_.setTimeout(
// Prior to Firefox 13, Gecko passed a non-standard parameter
// to the callback that we want to ignore.
goog.functions.lock(this.callback_),
goog.async.AnimationDelay.TIMEOUT);
}
};
/**
* Stops the delay timer if it is active. No action is taken if the timer is not
* in use.
*/
goog.async.AnimationDelay.prototype.stop = function() {
if (this.isActive()) {
var raf = this.getRaf_();
var cancelRaf = this.getCancelRaf_();
if (raf && !cancelRaf && this.win_.mozRequestAnimationFrame) {
goog.events.unlistenByKey(this.id_);
} else if (raf && cancelRaf) {
cancelRaf.call(this.win_, /** @type {number} */ (this.id_));
} else {
this.win_.clearTimeout(/** @type {number} */ (this.id_));
}
}
this.id_ = null;
};
/**
* Fires delay's action even if timer has already gone off or has not been
* started yet; guarantees action firing. Stops the delay timer.
*/
goog.async.AnimationDelay.prototype.fire = function() {
this.stop();
this.doAction_();
};
/**
* Fires delay's action only if timer is currently active. Stops the delay
* timer.
*/
goog.async.AnimationDelay.prototype.fireIfActive = function() {
if (this.isActive()) {
this.fire();
}
};
/**
* @return {boolean} True if the delay is currently active, false otherwise.
*/
goog.async.AnimationDelay.prototype.isActive = function() {
return this.id_ != null;
};
/**
* Invokes the callback function after the delay successfully completes.
* @private
*/
goog.async.AnimationDelay.prototype.doAction_ = function() {
if (this.usingListeners_ && this.id_) {
goog.events.unlistenByKey(this.id_);
}
this.id_ = null;
// We are not using the timestamp returned by requestAnimationFrame
// because it may be either a Date.now-style time or a
// high-resolution time (depending on browser implementation). Using
// goog.now() will ensure that the timestamp used is consistent and
// compatible with goog.fx.Animation.
this.listener_.call(this.handler_, goog.now());
};
/** @override */
goog.async.AnimationDelay.prototype.disposeInternal = function() {
this.stop();
goog.base(this, 'disposeInternal');
};
/**
* @return {?function(function(number)): number} The requestAnimationFrame
* function, or null if not available on this browser.
* @private
*/
goog.async.AnimationDelay.prototype.getRaf_ = function() {
var win = this.win_;
return win.requestAnimationFrame ||
win.webkitRequestAnimationFrame ||
win.mozRequestAnimationFrame ||
win.oRequestAnimationFrame ||
win.msRequestAnimationFrame ||
null;
};
/**
* @return {?function(number): number} The cancelAnimationFrame function,
* or null if not available on this browser.
* @private
*/
goog.async.AnimationDelay.prototype.getCancelRaf_ = function() {
var win = this.win_;
return win.cancelRequestAnimationFrame ||
win.webkitCancelRequestAnimationFrame ||
win.mozCancelRequestAnimationFrame ||
win.oCancelRequestAnimationFrame ||
win.msCancelRequestAnimationFrame ||
null;
};

View File

@@ -0,0 +1,235 @@
// Copyright 2008 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 Defines a class useful for handling functions that must be
* invoked later when some condition holds. Examples include deferred function
* calls that return a boolean flag whether it succedeed or not.
*
* Example:
*
* function deferred() {
* var succeeded = false;
* // ... custom code
* return succeeded;
* }
*
* var deferredCall = new goog.async.ConditionalDelay(deferred);
* deferredCall.onSuccess = function() {
* alert('Success: The deferred function has been successfully executed.');
* }
* deferredCall.onFailure = function() {
* alert('Failure: Time limit exceeded.');
* }
*
* // Call the deferred() every 100 msec until it returns true,
* // or 5 seconds pass.
* deferredCall.start(100, 5000);
*
* // Stop the deferred function call (does nothing if it's not active).
* deferredCall.stop();
*
*/
goog.provide('goog.async.ConditionalDelay');
goog.require('goog.Disposable');
goog.require('goog.async.Delay');
/**
* A ConditionalDelay object invokes the associated function after a specified
* interval delay and checks its return value. If the function returns
* {@code true} the conditional delay is cancelled and {@see #onSuccess}
* is called. Otherwise this object keeps to invoke the deferred function until
* either it returns {@code true} or the timeout is exceeded. In the latter case
* the {@see #onFailure} method will be called.
*
* The interval duration and timeout can be specified each time the delay is
* started. Calling start on an active delay will reset the timer.
*
* @param {function():boolean} listener Function to call when the delay
* completes. Should return a value that type-converts to {@code true} if
* the call succeeded and this delay should be stopped.
* @param {Object=} opt_handler The object scope to invoke the function in.
* @constructor
* @extends {goog.Disposable}
*/
goog.async.ConditionalDelay = function(listener, opt_handler) {
goog.Disposable.call(this);
/**
* The function that will be invoked after a delay.
* @type {function():boolean}
* @private
*/
this.listener_ = listener;
/**
* The object context to invoke the callback in.
* @type {Object|undefined}
* @private
*/
this.handler_ = opt_handler;
/**
* The underlying goog.async.Delay delegate object.
* @type {goog.async.Delay}
* @private
*/
this.delay_ = new goog.async.Delay(
goog.bind(this.onTick_, this), 0 /*interval*/, this /*scope*/);
};
goog.inherits(goog.async.ConditionalDelay, goog.Disposable);
/**
* The delay interval in milliseconds to between the calls to the callback.
* Note, that the callback may be invoked earlier than this interval if the
* timeout is exceeded.
* @type {number}
* @private
*/
goog.async.ConditionalDelay.prototype.interval_ = 0;
/**
* The timeout timestamp until which the delay is to be executed.
* A negative value means no timeout.
* @type {number}
* @private
*/
goog.async.ConditionalDelay.prototype.runUntil_ = 0;
/**
* True if the listener has been executed, and it returned {@code true}.
* @type {boolean}
* @private
*/
goog.async.ConditionalDelay.prototype.isDone_ = false;
/** @override */
goog.async.ConditionalDelay.prototype.disposeInternal = function() {
this.delay_.dispose();
delete this.listener_;
delete this.handler_;
goog.async.ConditionalDelay.superClass_.disposeInternal.call(this);
};
/**
* Starts the delay timer. The provided listener function will be called
* repeatedly after the specified interval until the function returns
* {@code true} or the timeout is exceeded. Calling start on an active timer
* will stop the timer first.
* @param {number=} opt_interval The time interval between the function
* invocations (in milliseconds). Default is 0.
* @param {number=} opt_timeout The timeout interval (in milliseconds). Takes
* precedence over the {@code opt_interval}, i.e. if the timeout is less
* than the invocation interval, the function will be called when the
* timeout is exceeded. A negative value means no timeout. Default is 0.
*/
goog.async.ConditionalDelay.prototype.start = function(opt_interval,
opt_timeout) {
this.stop();
this.isDone_ = false;
var timeout = opt_timeout || 0;
this.interval_ = Math.max(opt_interval || 0, 0);
this.runUntil_ = timeout < 0 ? -1 : (goog.now() + timeout);
this.delay_.start(
timeout < 0 ? this.interval_ : Math.min(this.interval_, timeout));
};
/**
* Stops the delay timer if it is active. No action is taken if the timer is not
* in use.
*/
goog.async.ConditionalDelay.prototype.stop = function() {
this.delay_.stop();
};
/**
* @return {boolean} True if the delay is currently active, false otherwise.
*/
goog.async.ConditionalDelay.prototype.isActive = function() {
return this.delay_.isActive();
};
/**
* @return {boolean} True if the listener has been executed and returned
* {@code true} since the last call to {@see #start}.
*/
goog.async.ConditionalDelay.prototype.isDone = function() {
return this.isDone_;
};
/**
* Called when the listener has been successfully executed and returned
* {@code true}. The {@see #isDone} method should return {@code true} by now.
* Designed for inheritance, should be overridden by subclasses or on the
* instances if they care.
*/
goog.async.ConditionalDelay.prototype.onSuccess = function() {
// Do nothing by default.
};
/**
* Called when this delayed call is cancelled because the timeout has been
* exceeded, and the listener has never returned {@code true}.
* Designed for inheritance, should be overridden by subclasses or on the
* instances if they care.
*/
goog.async.ConditionalDelay.prototype.onFailure = function() {
// Do nothing by default.
};
/**
* A callback function for the underlying {@code goog.async.Delay} object. When
* executed the listener function is called, and if it returns {@code true}
* the delay is stopped and the {@see #onSuccess} method is invoked.
* If the timeout is exceeded the delay is stopped and the
* {@see #onFailure} method is called.
* @private
*/
goog.async.ConditionalDelay.prototype.onTick_ = function() {
var successful = this.listener_.call(this.handler_);
if (successful) {
this.isDone_ = true;
this.onSuccess();
} else {
// Try to reschedule the task.
if (this.runUntil_ < 0) {
// No timeout.
this.delay_.start(this.interval_);
} else {
var timeLeft = this.runUntil_ - goog.now();
if (timeLeft <= 0) {
this.onFailure();
} else {
this.delay_.start(Math.min(this.interval_, timeLeft));
}
}
}
};

View File

@@ -0,0 +1,177 @@
// Copyright 2007 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 Defines a class useful for handling functions that must be
* invoked after a delay, especially when that delay is frequently restarted.
* Examples include delaying before displaying a tooltip, menu hysteresis,
* idle timers, etc.
* @author brenneman@google.com (Shawn Brenneman)
* @see ../demos/timers.html
*/
goog.provide('goog.Delay');
goog.provide('goog.async.Delay');
goog.require('goog.Disposable');
goog.require('goog.Timer');
/**
* A Delay object invokes the associated function after a specified delay. The
* interval duration can be specified once in the constructor, or can be defined
* each time the delay is started. Calling start on an active delay will reset
* the timer.
*
* @param {Function} listener Function to call when the delay completes.
* @param {number=} opt_interval The default length of the invocation delay (in
* milliseconds).
* @param {Object=} opt_handler The object scope to invoke the function in.
* @constructor
* @extends {goog.Disposable}
*/
goog.async.Delay = function(listener, opt_interval, opt_handler) {
goog.Disposable.call(this);
/**
* The function that will be invoked after a delay.
* @type {Function}
* @private
*/
this.listener_ = listener;
/**
* The default amount of time to delay before invoking the callback.
* @type {number}
* @private
*/
this.interval_ = opt_interval || 0;
/**
* The object context to invoke the callback in.
* @type {Object|undefined}
* @private
*/
this.handler_ = opt_handler;
/**
* Cached callback function invoked when the delay finishes.
* @type {Function}
* @private
*/
this.callback_ = goog.bind(this.doAction_, this);
};
goog.inherits(goog.async.Delay, goog.Disposable);
/**
* A deprecated alias.
* @deprecated Use goog.async.Delay instead.
* @constructor
*/
goog.Delay = goog.async.Delay;
/**
* Identifier of the active delay timeout, or 0 when inactive.
* @type {number}
* @private
*/
goog.async.Delay.prototype.id_ = 0;
/**
* Disposes of the object, cancelling the timeout if it is still outstanding and
* removing all object references.
* @override
* @protected
*/
goog.async.Delay.prototype.disposeInternal = function() {
goog.async.Delay.superClass_.disposeInternal.call(this);
this.stop();
delete this.listener_;
delete this.handler_;
};
/**
* Starts the delay timer. The provided listener function will be called after
* the specified interval. Calling start on an active timer will reset the
* delay interval.
* @param {number=} opt_interval If specified, overrides the object's default
* interval with this one (in milliseconds).
*/
goog.async.Delay.prototype.start = function(opt_interval) {
this.stop();
this.id_ = goog.Timer.callOnce(
this.callback_,
goog.isDef(opt_interval) ? opt_interval : this.interval_);
};
/**
* Stops the delay timer if it is active. No action is taken if the timer is not
* in use.
*/
goog.async.Delay.prototype.stop = function() {
if (this.isActive()) {
goog.Timer.clear(this.id_);
}
this.id_ = 0;
};
/**
* Fires delay's action even if timer has already gone off or has not been
* started yet; guarantees action firing. Stops the delay timer.
*/
goog.async.Delay.prototype.fire = function() {
this.stop();
this.doAction_();
};
/**
* Fires delay's action only if timer is currently active. Stops the delay
* timer.
*/
goog.async.Delay.prototype.fireIfActive = function() {
if (this.isActive()) {
this.fire();
}
};
/**
* @return {boolean} True if the delay is currently active, false otherwise.
*/
goog.async.Delay.prototype.isActive = function() {
return this.id_ != 0;
};
/**
* Invokes the callback function after the delay successfully completes.
* @private
*/
goog.async.Delay.prototype.doAction_ = function() {
this.id_ = 0;
if (this.listener_) {
this.listener_.call(this.handler_);
}
};

View File

@@ -0,0 +1,174 @@
// Copyright 2013 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 Provides a function to schedule running a function as soon
* as possible after the current JS execution stops and yields to the event
* loop.
*
*/
goog.provide('goog.async.nextTick');
goog.require('goog.debug.entryPointRegistry');
goog.require('goog.functions');
/**
* Fires the provided callbacks as soon as possible after the current JS
* execution context. setTimeout(…, 0) always takes at least 5ms for legacy
* reasons.
* @param {function()} callback Callback function to fire as soon as possible.
* @param {Object=} opt_context Object in whose scope to call the listener.
*/
goog.async.nextTick = function(callback, opt_context) {
var cb = callback;
if (opt_context) {
cb = goog.bind(callback, opt_context);
}
cb = goog.async.nextTick.wrapCallback_(cb);
// Introduced and currently only supported by IE10.
if (goog.isFunction(goog.global.setImmediate)) {
goog.global.setImmediate(cb);
return;
}
// Look for and cache the custom fallback version of setImmediate.
if (!goog.async.nextTick.setImmediate_) {
goog.async.nextTick.setImmediate_ =
goog.async.nextTick.getSetImmediateEmulator_();
}
goog.async.nextTick.setImmediate_(cb);
};
/**
* Cache for the setImmediate implementation.
* @type {function(function())}
* @private
*/
goog.async.nextTick.setImmediate_;
/**
* Determines the best possible implementation to run a function as soon as
* the JS event loop is idle.
* @return {function(function())} The "setImmediate" implementation.
* @private
*/
goog.async.nextTick.getSetImmediateEmulator_ = function() {
// Create a private message channel and use it to postMessage empty messages
// to ourselves.
var Channel = goog.global['MessageChannel'];
// If MessageChannel is not available and we are in a browser, implement
// an iframe based polyfill in browsers that have postMessage and
// document.addEventListener. The latter excludes IE8 because it has a
// synchronous postMessage implementation.
if (typeof Channel === 'undefined' && typeof window !== 'undefined' &&
window.postMessage && window.addEventListener) {
/** @constructor */
Channel = function() {
// Make an empty, invisible iframe.
var iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = '';
document.body.appendChild(iframe);
var win = iframe.contentWindow;
var doc = win.document;
doc.open();
doc.write('');
doc.close();
var message = 'callImmediate' + Math.random();
var origin = win.location.protocol + '//' + win.location.host;
var onmessage = goog.bind(function(e) {
// Validate origin and message to make sure that this message was
// intended for us.
if (e.origin != origin && e.data != message) {
return;
}
this['port1'].onmessage();
}, this);
win.addEventListener('message', onmessage, false);
this['port1'] = {};
this['port2'] = {
postMessage: function() {
win.postMessage(message, origin);
}
};
};
}
if (typeof Channel !== 'undefined') {
var channel = new Channel();
// Use a fifo linked list to call callbacks in the right order.
var head = {};
var tail = head;
channel['port1'].onmessage = function() {
head = head.next;
var cb = head.cb;
head.cb = null;
cb();
};
return function(cb) {
tail.next = {
cb: cb
};
tail = tail.next;
channel['port2'].postMessage(0);
};
}
// Implementation for IE6-8: Script elements fire an asynchronous
// onreadystatechange event when inserted into the DOM.
if (typeof document !== 'undefined' && 'onreadystatechange' in
document.createElement('script')) {
return function(cb) {
var script = document.createElement('script');
script.onreadystatechange = function() {
// Clean up and call the callback.
script.onreadystatechange = null;
script.parentNode.removeChild(script);
script = null;
cb();
cb = null;
};
document.documentElement.appendChild(script);
};
}
// Fall back to setTimeout with 0. In browsers this creates a delay of 5ms
// or more.
return function(cb) {
goog.global.setTimeout(cb, 0);
};
};
/**
* Helper function that is overrided to protect callbacks with entry point
* monitor if the application monitors entry points.
* @param {function()} callback Callback function to fire as soon as possible.
* @return {function()} The wrapped callback.
* @private
*/
goog.async.nextTick.wrapCallback_ = goog.functions.identity;
// Register the callback function as an entry point, so that it can be
// monitored for exception handling, etc. This has to be done in this file
// since it requires special code to handle all browsers.
goog.debug.entryPointRegistry.register(
/**
* @param {function(!Function): !Function} transformer The transforming
* function.
*/
function(transformer) {
goog.async.nextTick.wrapCallback_ = transformer;
});

View File

@@ -0,0 +1,189 @@
// Copyright 2007 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 Definition of the goog.async.Throttle class.
*
* @see ../demos/timers.html
*/
goog.provide('goog.Throttle');
goog.provide('goog.async.Throttle');
goog.require('goog.Disposable');
goog.require('goog.Timer');
/**
* Throttle will perform an action that is passed in no more than once
* per interval (specified in milliseconds). If it gets multiple signals
* to perform the action while it is waiting, it will only perform the action
* once at the end of the interval.
* @param {Function} listener Function to callback when the action is triggered.
* @param {number} interval Interval over which to throttle. The handler can
* only be called once per interval.
* @param {Object=} opt_handler Object in whose scope to call the listener.
* @constructor
* @extends {goog.Disposable}
*/
goog.async.Throttle = function(listener, interval, opt_handler) {
goog.Disposable.call(this);
/**
* Function to callback
* @type {Function}
* @private
*/
this.listener_ = listener;
/**
* Interval for the throttle time
* @type {number}
* @private
*/
this.interval_ = interval;
/**
* "this" context for the listener
* @type {Object|undefined}
* @private
*/
this.handler_ = opt_handler;
/**
* Cached callback function invoked after the throttle timeout completes
* @type {Function}
* @private
*/
this.callback_ = goog.bind(this.onTimer_, this);
};
goog.inherits(goog.async.Throttle, goog.Disposable);
/**
* A deprecated alias.
* @deprecated Use goog.async.Throttle instead.
* @constructor
*/
goog.Throttle = goog.async.Throttle;
/**
* Indicates that the action is pending and needs to be fired.
* @type {boolean}
* @private
*/
goog.async.Throttle.prototype.shouldFire_ = false;
/**
* Indicates the count of nested pauses currently in effect on the throttle.
* When this count is not zero, fired actions will be postponed until the
* throttle is resumed enough times to drop the pause count to zero.
* @type {number}
* @private
*/
goog.async.Throttle.prototype.pauseCount_ = 0;
/**
* Timer for scheduling the next callback
* @type {?number}
* @private
*/
goog.async.Throttle.prototype.timer_ = null;
/**
* Notifies the throttle that the action has happened. It will throttle the call
* so that the callback is not called too often according to the interval
* parameter passed to the constructor.
*/
goog.async.Throttle.prototype.fire = function() {
if (!this.timer_ && !this.pauseCount_) {
this.doAction_();
} else {
this.shouldFire_ = true;
}
};
/**
* Cancels any pending action callback. The throttle can be restarted by
* calling {@link #fire}.
*/
goog.async.Throttle.prototype.stop = function() {
if (this.timer_) {
goog.Timer.clear(this.timer_);
this.timer_ = null;
this.shouldFire_ = false;
}
};
/**
* Pauses the throttle. All pending and future action callbacks will be
* delayed until the throttle is resumed. Pauses can be nested.
*/
goog.async.Throttle.prototype.pause = function() {
this.pauseCount_++;
};
/**
* Resumes the throttle. If doing so drops the pausing count to zero, pending
* action callbacks will be executed as soon as possible, but still no sooner
* than an interval's delay after the previous call. Future action callbacks
* will be executed as normal.
*/
goog.async.Throttle.prototype.resume = function() {
this.pauseCount_--;
if (!this.pauseCount_ && this.shouldFire_ && !this.timer_) {
this.shouldFire_ = false;
this.doAction_();
}
};
/** @override */
goog.async.Throttle.prototype.disposeInternal = function() {
goog.async.Throttle.superClass_.disposeInternal.call(this);
this.stop();
};
/**
* Handler for the timer to fire the throttle
* @private
*/
goog.async.Throttle.prototype.onTimer_ = function() {
this.timer_ = null;
if (this.shouldFire_ && !this.pauseCount_) {
this.shouldFire_ = false;
this.doAction_();
}
};
/**
* Calls the callback
* @private
*/
goog.async.Throttle.prototype.doAction_ = function() {
this.timer_ = goog.Timer.callOnce(this.callback_, this.interval_);
this.listener_.call(this.handler_);
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,91 @@
// Copyright 2013 The Closure Library Authors.
//
// 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 nodejs script for dynamically requiring Closure within
* nodejs.
*
* Example of usage:
* <code>
* require('./bootstrap/nodejs')
* goog.require('goog.ui.Component')
* </code>
*
* This loads goog.ui.Component in the global scope.
*
* If you want to load custom libraries, you can require the custom deps file
* directly. If your custom libraries introduce new globals, you may
* need to run goog.nodeGlobalRequire to get them to load correctly.
*
* <code>
* require('./path/to/my/deps.js')
* goog.bootstrap.nodeJs.nodeGlobalRequire('./path/to/my/base.js')
* goog.require('my.Class')
* </code>
*
* @author nick@medium.com (Nick Santos)
*
* @nocompile
*/
var fs = require('fs');
var path = require('path');
/**
* The goog namespace in the global scope.
*/
global.goog = {};
/**
* Imports a script using Node's require() API.
*
* @param {string} src The script source.
* @return {boolean} True if the script was imported, false otherwise.
*/
global.CLOSURE_IMPORT_SCRIPT = function(src) {
// Sources are always expressed relative to closure's base.js, but
// require() is always relative to the current source.
require('./../' + src);
return true;
};
// Declared here so it can be used to require base.js
function nodeGlobalRequire(file) {
process.binding('evals').NodeScript.runInThisContext.call(
global, fs.readFileSync(file), file);
}
// Load Closure's base.js into memory. It is assumed base.js is in the
// directory above this directory given this script's location in
// bootstrap/nodejs.js.
nodeGlobalRequire(path.resolve(__dirname, '..', 'base.js'));
/**
* Bootstraps a file into the global scope.
*
* This is strictly for cases where normal require() won't work,
* because the file declares global symbols with 'var' that need to
* be added to the global scope.
* @suppress {missingProvide}
*
* @param {string} file The path to the file.
*/
goog.nodeGlobalRequire = nodeGlobalRequire;

View File

@@ -0,0 +1,37 @@
// 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 bootstrap for dynamically requiring Closure within an HTML5
* Web Worker context. To use this, first set CLOSURE_BASE_PATH to the directory
* containing base.js (relative to the main script), then use importScripts to
* load this file and base.js (in that order). After this you can use
* goog.require for further imports.
*
* @nocompile
*/
/**
* Imports a script using the Web Worker importScript API.
*
* @param {string} src The script source.
* @return {boolean} True if the script was imported, false otherwise.
*/
this.CLOSURE_IMPORT_SCRIPT = (function(global) {
return function(src) {
global['importScripts'](src);
return true;
};
})(this);

View File

@@ -0,0 +1,467 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utilities related to alpha/transparent colors and alpha color
* conversion.
*/
goog.provide('goog.color.alpha');
goog.require('goog.color');
/**
* Parses an alpha color out of a string.
* @param {string} str Color in some format.
* @return {Object} Contains two properties: 'hex', which is a string containing
* a hex representation of the color, as well as 'type', which is a string
* containing the type of color format passed in ('hex', 'rgb', 'named').
*/
goog.color.alpha.parse = function(str) {
var result = {};
str = String(str);
var maybeHex = goog.color.prependHashIfNecessaryHelper(str);
if (goog.color.alpha.isValidAlphaHexColor_(maybeHex)) {
result.hex = goog.color.alpha.normalizeAlphaHex_(maybeHex);
result.type = 'hex';
return result;
} else {
var rgba = goog.color.alpha.isValidRgbaColor_(str);
if (rgba.length) {
result.hex = goog.color.alpha.rgbaArrayToHex(rgba);
result.type = 'rgba';
return result;
} else {
var hsla = goog.color.alpha.isValidHslaColor_(str);
if (hsla.length) {
result.hex = goog.color.alpha.hslaArrayToHex(hsla);
result.type = 'hsla';
return result;
}
}
}
throw Error(str + ' is not a valid color string');
};
/**
* Converts a hex representation of a color to RGBA.
* @param {string} hexColor Color to convert.
* @return {string} string of the form 'rgba(R,G,B,A)' which can be used in
* styles.
*/
goog.color.alpha.hexToRgbaStyle = function(hexColor) {
return goog.color.alpha.rgbaStyle_(goog.color.alpha.hexToRgba(hexColor));
};
/**
* Gets the hex color part of an alpha hex color. For example, from '#abcdef55'
* return '#abcdef'.
* @param {string} colorWithAlpha The alpha hex color to get the hex color from.
* @return {string} The hex color where the alpha part has been stripped off.
*/
goog.color.alpha.extractHexColor = function(colorWithAlpha) {
if (goog.color.alpha.isValidAlphaHexColor_(colorWithAlpha)) {
var fullColor = goog.color.prependHashIfNecessaryHelper(colorWithAlpha);
var normalizedColor = goog.color.alpha.normalizeAlphaHex_(fullColor);
return normalizedColor.substring(0, 7);
} else {
throw Error(colorWithAlpha + ' is not a valid 8-hex color string');
}
};
/**
* Gets the alpha color part of an alpha hex color. For example, from
* '#abcdef55' return '55'. The result is guaranteed to be two characters long.
* @param {string} colorWithAlpha The alpha hex color to get the hex color from.
* @return {string} The hex color where the alpha part has been stripped off.
*/
goog.color.alpha.extractAlpha = function(colorWithAlpha) {
if (goog.color.alpha.isValidAlphaHexColor_(colorWithAlpha)) {
var fullColor = goog.color.prependHashIfNecessaryHelper(colorWithAlpha);
var normalizedColor = goog.color.alpha.normalizeAlphaHex_(fullColor);
return normalizedColor.substring(7, 9);
} else {
throw Error(colorWithAlpha + ' is not a valid 8-hex color string');
}
};
/**
* Regular expression for extracting the digits in a hex color quadruplet.
* @type {RegExp}
* @private
*/
goog.color.alpha.hexQuadrupletRe_ = /#(.)(.)(.)(.)/;
/**
* Normalize a hex representation of an alpha color.
* @param {string} hexColor an alpha hex color string.
* @return {string} hex color in the format '#rrggbbaa' with all lowercase
* literals.
* @private
*/
goog.color.alpha.normalizeAlphaHex_ = function(hexColor) {
if (!goog.color.alpha.isValidAlphaHexColor_(hexColor)) {
throw Error("'" + hexColor + "' is not a valid alpha hex color");
}
if (hexColor.length == 5) { // of the form #RGBA
hexColor = hexColor.replace(goog.color.alpha.hexQuadrupletRe_,
'#$1$1$2$2$3$3$4$4');
}
return hexColor.toLowerCase();
};
/**
* Converts an 8-hex representation of a color to RGBA.
* @param {string} hexColor Color to convert.
* @return {Array} array containing [r, g, b] as ints in [0, 255].
*/
goog.color.alpha.hexToRgba = function(hexColor) {
// TODO(user): Enhance code sharing with goog.color, for example by
// adding a goog.color.genericHexToRgb method.
hexColor = goog.color.alpha.normalizeAlphaHex_(hexColor);
var r = parseInt(hexColor.substr(1, 2), 16);
var g = parseInt(hexColor.substr(3, 2), 16);
var b = parseInt(hexColor.substr(5, 2), 16);
var a = parseInt(hexColor.substr(7, 2), 16);
return [r, g, b, a / 255];
};
/**
* Converts a color from RGBA to hex representation.
* @param {number} r Amount of red, int between 0 and 255.
* @param {number} g Amount of green, int between 0 and 255.
* @param {number} b Amount of blue, int between 0 and 255.
* @param {number} a Amount of alpha, float between 0 and 1.
* @return {string} hex representation of the color.
*/
goog.color.alpha.rgbaToHex = function(r, g, b, a) {
var intAlpha = Math.floor(a * 255);
if (isNaN(intAlpha) || intAlpha < 0 || intAlpha > 255) {
// TODO(user): The CSS spec says the value should be clamped.
throw Error('"(' + r + ',' + g + ',' + b + ',' + a +
'") is not a valid RGBA color');
}
var hexA = goog.color.prependZeroIfNecessaryHelper(intAlpha.toString(16));
return goog.color.rgbToHex(r, g, b) + hexA;
};
/**
* Converts a color from HSLA to hex representation.
* @param {number} h Amount of hue, int between 0 and 360.
* @param {number} s Amount of saturation, int between 0 and 100.
* @param {number} l Amount of lightness, int between 0 and 100.
* @param {number} a Amount of alpha, float between 0 and 1.
* @return {string} hex representation of the color.
*/
goog.color.alpha.hslaToHex = function(h, s, l, a) {
var intAlpha = Math.floor(a * 255);
if (isNaN(intAlpha) || intAlpha < 0 || intAlpha > 255) {
// TODO(user): The CSS spec says the value should be clamped.
throw Error('"(' + h + ',' + s + ',' + l + ',' + a +
'") is not a valid HSLA color');
}
var hexA = goog.color.prependZeroIfNecessaryHelper(intAlpha.toString(16));
return goog.color.hslToHex(h, s / 100, l / 100) + hexA;
};
/**
* Converts a color from RGBA to hex representation.
* @param {Array.<number>} rgba Array of [r, g, b, a], with r, g, b in [0, 255]
* and a in [0, 1].
* @return {string} hex representation of the color.
*/
goog.color.alpha.rgbaArrayToHex = function(rgba) {
return goog.color.alpha.rgbaToHex(rgba[0], rgba[1], rgba[2], rgba[3]);
};
/**
* Converts a color from RGBA to an RGBA style string.
* @param {number} r Value of red, in [0, 255].
* @param {number} g Value of green, in [0, 255].
* @param {number} b Value of blue, in [0, 255].
* @param {number} a Value of alpha, in [0, 1].
* @return {string} An 'rgba(r,g,b,a)' string ready for use in a CSS rule.
*/
goog.color.alpha.rgbaToRgbaStyle = function(r, g, b, a) {
if (isNaN(r) || r < 0 || r > 255 ||
isNaN(g) || g < 0 || g > 255 ||
isNaN(b) || b < 0 || b > 255 ||
isNaN(a) || a < 0 || a > 1) {
throw Error('"(' + r + ',' + g + ',' + b + ',' + a +
')" is not a valid RGBA color');
}
return goog.color.alpha.rgbaStyle_([r, g, b, a]);
};
/**
* Converts a color from RGBA to an RGBA style string.
* @param {(Array.<number>|Float32Array)} rgba Array of [r, g, b, a],
* with r, g, b in [0, 255] and a in [0, 1].
* @return {string} An 'rgba(r,g,b,a)' string ready for use in a CSS rule.
*/
goog.color.alpha.rgbaArrayToRgbaStyle = function(rgba) {
return goog.color.alpha.rgbaToRgbaStyle(rgba[0], rgba[1], rgba[2], rgba[3]);
};
/**
* Converts a color from HSLA to hex representation.
* @param {Array.<number>} hsla Array of [h, s, l, a], where h is an integer in
* [0, 360], s and l are integers in [0, 100], and a is in [0, 1].
* @return {string} hex representation of the color, such as '#af457eff'.
*/
goog.color.alpha.hslaArrayToHex = function(hsla) {
return goog.color.alpha.hslaToHex(hsla[0], hsla[1], hsla[2], hsla[3]);
};
/**
* Converts a color from HSLA to an RGBA style string.
* @param {Array.<number>} hsla Array of [h, s, l, a], where h is and integer in
* [0, 360], s and l are integers in [0, 100], and a is in [0, 1].
* @return {string} An 'rgba(r,g,b,a)' string ready for use in a CSS rule.
*/
goog.color.alpha.hslaArrayToRgbaStyle = function(hsla) {
return goog.color.alpha.hslaToRgbaStyle(hsla[0], hsla[1], hsla[2], hsla[3]);
};
/**
* Converts a color from HSLA to an RGBA style string.
* @param {number} h Amount of hue, int between 0 and 360.
* @param {number} s Amount of saturation, int between 0 and 100.
* @param {number} l Amount of lightness, int between 0 and 100.
* @param {number} a Amount of alpha, float between 0 and 1.
* @return {string} An 'rgba(r,g,b,a)' string ready for use in a CSS rule.
* styles.
*/
goog.color.alpha.hslaToRgbaStyle = function(h, s, l, a) {
return goog.color.alpha.rgbaStyle_(goog.color.alpha.hslaToRgba(h, s, l, a));
};
/**
* Converts a color from HSLA color space to RGBA color space.
* @param {number} h Amount of hue, int between 0 and 360.
* @param {number} s Amount of saturation, int between 0 and 100.
* @param {number} l Amount of lightness, int between 0 and 100.
* @param {number} a Amount of alpha, float between 0 and 1.
* @return {Array.<number>} [r, g, b, a] values for the color, where r, g, b
* are integers in [0, 255] and a is a float in [0, 1].
*/
goog.color.alpha.hslaToRgba = function(h, s, l, a) {
return goog.color.hslToRgb(h, s / 100, l / 100).concat(a);
};
/**
* Converts a color from RGBA color space to HSLA color space.
* Modified from {@link http://en.wikipedia.org/wiki/HLS_color_space}.
* @param {number} r Value of red, in [0, 255].
* @param {number} g Value of green, in [0, 255].
* @param {number} b Value of blue, in [0, 255].
* @param {number} a Value of alpha, in [0, 255].
* @return {Array.<number>} [h, s, l, a] values for the color, with h an int in
* [0, 360] and s, l and a in [0, 1].
*/
goog.color.alpha.rgbaToHsla = function(r, g, b, a) {
return goog.color.rgbToHsl(r, g, b).concat(a);
};
/**
* Converts a color from RGBA color space to HSLA color space.
* @param {Array.<number>} rgba [r, g, b, a] values for the color, each in
* [0, 255].
* @return {Array.<number>} [h, s, l, a] values for the color, with h in
* [0, 360] and s, l and a in [0, 1].
*/
goog.color.alpha.rgbaArrayToHsla = function(rgba) {
return goog.color.alpha.rgbaToHsla(rgba[0], rgba[1], rgba[2], rgba[3]);
};
/**
* Helper for isValidAlphaHexColor_.
* @type {RegExp}
* @private
*/
goog.color.alpha.validAlphaHexColorRe_ = /^#(?:[0-9a-f]{4}){1,2}$/i;
/**
* Checks if a string is a valid alpha hex color. We expect strings of the
* format #RRGGBBAA (ex: #1b3d5f5b) or #RGBA (ex: #3CAF == #33CCAAFF).
* @param {string} str String to check.
* @return {boolean} Whether the string is a valid alpha hex color.
* @private
*/
// TODO(user): Support percentages when goog.color also supports them.
goog.color.alpha.isValidAlphaHexColor_ = function(str) {
return goog.color.alpha.validAlphaHexColorRe_.test(str);
};
/**
* Helper for isNormalizedAlphaHexColor_.
* @type {RegExp}
* @private
*/
goog.color.alpha.normalizedAlphaHexColorRe_ = /^#[0-9a-f]{8}$/;
/**
* Checks if a string is a normalized alpha hex color.
* We expect strings of the format #RRGGBBAA (ex: #1b3d5f5b)
* using only lowercase letters.
* @param {string} str String to check.
* @return {boolean} Whether the string is a normalized hex color.
* @private
*/
goog.color.alpha.isNormalizedAlphaHexColor_ = function(str) {
return goog.color.alpha.normalizedAlphaHexColorRe_.test(str);
};
/**
* Regular expression for matching and capturing RGBA style strings. Helper for
* isValidRgbaColor_.
* @type {RegExp}
* @private
*/
goog.color.alpha.rgbaColorRe_ =
/^(?:rgba)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|1|0\.\d{0,10})\)$/i;
/**
* Regular expression for matching and capturing HSLA style strings. Helper for
* isValidHslaColor_.
* @type {RegExp}
* @private
*/
goog.color.alpha.hslaColorRe_ =
/^(?:hsla)\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\%,\s?(0|[1-9]\d{0,2})\%,\s?(0|1|0\.\d{0,10})\)$/i;
/**
* Checks if a string is a valid rgba color. We expect strings of the format
* '(r, g, b, a)', or 'rgba(r, g, b, a)', where r, g, b are ints in [0, 255]
* and a is a float in [0, 1].
* @param {string} str String to check.
* @return {Array.<number>} the integers [r, g, b, a] for valid colors or the
* empty array for invalid colors.
* @private
*/
goog.color.alpha.isValidRgbaColor_ = function(str) {
// Each component is separate (rather than using a repeater) so we can
// capture the match. Also, we explicitly set each component to be either 0,
// or start with a non-zero, to prevent octal numbers from slipping through.
var regExpResultArray = str.match(goog.color.alpha.rgbaColorRe_);
if (regExpResultArray) {
var r = Number(regExpResultArray[1]);
var g = Number(regExpResultArray[2]);
var b = Number(regExpResultArray[3]);
var a = Number(regExpResultArray[4]);
if (r >= 0 && r <= 255 &&
g >= 0 && g <= 255 &&
b >= 0 && b <= 255 &&
a >= 0 && a <= 1) {
return [r, g, b, a];
}
}
return [];
};
/**
* Checks if a string is a valid hsla color. We expect strings of the format
* 'hsla(h, s, l, a)', where s in an int in [0, 360], s and l are percentages
* between 0 and 100 such as '50%' or '70%', and a is a float in [0, 1].
* @param {string} str String to check.
* @return {Array.<number>} the integers [h, s, l, a] for valid colors or the
* empty array for invalid colors.
* @private
*/
goog.color.alpha.isValidHslaColor_ = function(str) {
// Each component is separate (rather than using a repeater) so we can
// capture the match. Also, we explicitly set each component to be either 0,
// or start with a non-zero, to prevent octal numbers from slipping through.
var regExpResultArray = str.match(goog.color.alpha.hslaColorRe_);
if (regExpResultArray) {
var h = Number(regExpResultArray[1]);
var s = Number(regExpResultArray[2]);
var l = Number(regExpResultArray[3]);
var a = Number(regExpResultArray[4]);
if (h >= 0 && h <= 360 &&
s >= 0 && s <= 100 &&
l >= 0 && l <= 100 &&
a >= 0 && a <= 1) {
return [h, s, l, a];
}
}
return [];
};
/**
* Takes an array of [r, g, b, a] and converts it into a string appropriate for
* CSS styles.
* @param {Array.<number>} rgba [r, g, b, a] with r, g, b in [0, 255] and a
* in [0, 1].
* @return {string} string of the form 'rgba(r,g,b,a)'.
* @private
*/
goog.color.alpha.rgbaStyle_ = function(rgba) {
return 'rgba(' + rgba.join(',') + ')';
};
/**
* Converts from h,s,v,a values to a hex string
* @param {number} h Hue, in [0, 1].
* @param {number} s Saturation, in [0, 1].
* @param {number} v Value, in [0, 255].
* @param {number} a Alpha, in [0, 1].
* @return {string} hex representation of the color.
*/
goog.color.alpha.hsvaToHex = function(h, s, v, a) {
var alpha = Math.floor(a * 255);
return goog.color.hsvArrayToHex([h, s, v]) +
goog.color.prependZeroIfNecessaryHelper(alpha.toString(16));
};
/**
* Converts from an HSVA array to a hex string
* @param {Array} hsva Array of [h, s, v, a] in
* [[0, 1], [0, 1], [0, 255], [0, 1]].
* @return {string} hex representation of the color.
*/
goog.color.alpha.hsvaArrayToHex = function(hsva) {
return goog.color.alpha.hsvaToHex(hsva[0], hsva[1], hsva[2], hsva[3]);
};

View File

@@ -0,0 +1,773 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utilities related to color and color conversion.
*/
goog.provide('goog.color');
goog.require('goog.color.names');
goog.require('goog.math');
/**
* RGB color representation. An array containing three elements [r, g, b],
* each an integer in [0, 255], representing the red, green, and blue components
* of the color respectively.
* @typedef {Array.<number>}
*/
goog.color.Rgb;
/**
* HSV color representation. An array containing three elements [h, s, v]:
* h (hue) must be an integer in [0, 360], cyclic.
* s (saturation) must be a number in [0, 1].
* v (value/brightness) must be an integer in [0, 255].
* @typedef {Array.<number>}
*/
goog.color.Hsv;
/**
* HSL color representation. An array containing three elements [h, s, l]:
* h (hue) must be an integer in [0, 360], cyclic.
* s (saturation) must be a number in [0, 1].
* l (lightness) must be a number in [0, 1].
* @typedef {Array.<number>}
*/
goog.color.Hsl;
/**
* Parses a color out of a string.
* @param {string} str Color in some format.
* @return {Object} Contains two properties: 'hex', which is a string containing
* a hex representation of the color, as well as 'type', which is a string
* containing the type of color format passed in ('hex', 'rgb', 'named').
*/
goog.color.parse = function(str) {
var result = {};
str = String(str);
var maybeHex = goog.color.prependHashIfNecessaryHelper(str);
if (goog.color.isValidHexColor_(maybeHex)) {
result.hex = goog.color.normalizeHex(maybeHex);
result.type = 'hex';
return result;
} else {
var rgb = goog.color.isValidRgbColor_(str);
if (rgb.length) {
result.hex = goog.color.rgbArrayToHex(rgb);
result.type = 'rgb';
return result;
} else if (goog.color.names) {
var hex = goog.color.names[str.toLowerCase()];
if (hex) {
result.hex = hex;
result.type = 'named';
return result;
}
}
}
throw Error(str + ' is not a valid color string');
};
/**
* Determines if the given string can be parsed as a color.
* {@see goog.color.parse}.
* @param {string} str Potential color string.
* @return {boolean} True if str is in a format that can be parsed to a color.
*/
goog.color.isValidColor = function(str) {
var maybeHex = goog.color.prependHashIfNecessaryHelper(str);
return !!(goog.color.isValidHexColor_(maybeHex) ||
goog.color.isValidRgbColor_(str).length ||
goog.color.names && goog.color.names[str.toLowerCase()]);
};
/**
* Parses red, green, blue components out of a valid rgb color string.
* Throws Error if the color string is invalid.
* @param {string} str RGB representation of a color.
* {@see goog.color.isValidRgbColor_}.
* @return {!goog.color.Rgb} rgb representation of the color.
*/
goog.color.parseRgb = function(str) {
var rgb = goog.color.isValidRgbColor_(str);
if (!rgb.length) {
throw Error(str + ' is not a valid RGB color');
}
return rgb;
};
/**
* Converts a hex representation of a color to RGB.
* @param {string} hexColor Color to convert.
* @return {string} string of the form 'rgb(R,G,B)' which can be used in
* styles.
*/
goog.color.hexToRgbStyle = function(hexColor) {
return goog.color.rgbStyle_(goog.color.hexToRgb(hexColor));
};
/**
* Regular expression for extracting the digits in a hex color triplet.
* @type {RegExp}
* @private
*/
goog.color.hexTripletRe_ = /#(.)(.)(.)/;
/**
* Normalize an hex representation of a color
* @param {string} hexColor an hex color string.
* @return {string} hex color in the format '#rrggbb' with all lowercase
* literals.
*/
goog.color.normalizeHex = function(hexColor) {
if (!goog.color.isValidHexColor_(hexColor)) {
throw Error("'" + hexColor + "' is not a valid hex color");
}
if (hexColor.length == 4) { // of the form #RGB
hexColor = hexColor.replace(goog.color.hexTripletRe_, '#$1$1$2$2$3$3');
}
return hexColor.toLowerCase();
};
/**
* Converts a hex representation of a color to RGB.
* @param {string} hexColor Color to convert.
* @return {!goog.color.Rgb} rgb representation of the color.
*/
goog.color.hexToRgb = function(hexColor) {
hexColor = goog.color.normalizeHex(hexColor);
var r = parseInt(hexColor.substr(1, 2), 16);
var g = parseInt(hexColor.substr(3, 2), 16);
var b = parseInt(hexColor.substr(5, 2), 16);
return [r, g, b];
};
/**
* Converts a color from RGB to hex representation.
* @param {number} r Amount of red, int between 0 and 255.
* @param {number} g Amount of green, int between 0 and 255.
* @param {number} b Amount of blue, int between 0 and 255.
* @return {string} hex representation of the color.
*/
goog.color.rgbToHex = function(r, g, b) {
r = Number(r);
g = Number(g);
b = Number(b);
if (isNaN(r) || r < 0 || r > 255 ||
isNaN(g) || g < 0 || g > 255 ||
isNaN(b) || b < 0 || b > 255) {
throw Error('"(' + r + ',' + g + ',' + b + '") is not a valid RGB color');
}
var hexR = goog.color.prependZeroIfNecessaryHelper(r.toString(16));
var hexG = goog.color.prependZeroIfNecessaryHelper(g.toString(16));
var hexB = goog.color.prependZeroIfNecessaryHelper(b.toString(16));
return '#' + hexR + hexG + hexB;
};
/**
* Converts a color from RGB to hex representation.
* @param {goog.color.Rgb} rgb rgb representation of the color.
* @return {string} hex representation of the color.
*/
goog.color.rgbArrayToHex = function(rgb) {
return goog.color.rgbToHex(rgb[0], rgb[1], rgb[2]);
};
/**
* Converts a color from RGB color space to HSL color space.
* Modified from {@link http://en.wikipedia.org/wiki/HLS_color_space}.
* @param {number} r Value of red, in [0, 255].
* @param {number} g Value of green, in [0, 255].
* @param {number} b Value of blue, in [0, 255].
* @return {!goog.color.Hsl} hsl representation of the color.
*/
goog.color.rgbToHsl = function(r, g, b) {
// First must normalize r, g, b to be between 0 and 1.
var normR = r / 255;
var normG = g / 255;
var normB = b / 255;
var max = Math.max(normR, normG, normB);
var min = Math.min(normR, normG, normB);
var h = 0;
var s = 0;
// Luminosity is the average of the max and min rgb color intensities.
var l = 0.5 * (max + min);
// The hue and saturation are dependent on which color intensity is the max.
// If max and min are equal, the color is gray and h and s should be 0.
if (max != min) {
if (max == normR) {
h = 60 * (normG - normB) / (max - min);
} else if (max == normG) {
h = 60 * (normB - normR) / (max - min) + 120;
} else if (max == normB) {
h = 60 * (normR - normG) / (max - min) + 240;
}
if (0 < l && l <= 0.5) {
s = (max - min) / (2 * l);
} else {
s = (max - min) / (2 - 2 * l);
}
}
// Make sure the hue falls between 0 and 360.
return [Math.round(h + 360) % 360, s, l];
};
/**
* Converts a color from RGB color space to HSL color space.
* @param {goog.color.Rgb} rgb rgb representation of the color.
* @return {!goog.color.Hsl} hsl representation of the color.
*/
goog.color.rgbArrayToHsl = function(rgb) {
return goog.color.rgbToHsl(rgb[0], rgb[1], rgb[2]);
};
/**
* Helper for hslToRgb.
* @param {number} v1 Helper variable 1.
* @param {number} v2 Helper variable 2.
* @param {number} vH Helper variable 3.
* @return {number} Appropriate RGB value, given the above.
* @private
*/
goog.color.hueToRgb_ = function(v1, v2, vH) {
if (vH < 0) {
vH += 1;
} else if (vH > 1) {
vH -= 1;
}
if ((6 * vH) < 1) {
return (v1 + (v2 - v1) * 6 * vH);
} else if (2 * vH < 1) {
return v2;
} else if (3 * vH < 2) {
return (v1 + (v2 - v1) * ((2 / 3) - vH) * 6);
}
return v1;
};
/**
* Converts a color from HSL color space to RGB color space.
* Modified from {@link http://www.easyrgb.com/math.html}
* @param {number} h Hue, in [0, 360].
* @param {number} s Saturation, in [0, 1].
* @param {number} l Luminosity, in [0, 1].
* @return {!goog.color.Rgb} rgb representation of the color.
*/
goog.color.hslToRgb = function(h, s, l) {
var r = 0;
var g = 0;
var b = 0;
var normH = h / 360; // normalize h to fall in [0, 1]
if (s == 0) {
r = g = b = l * 255;
} else {
var temp1 = 0;
var temp2 = 0;
if (l < 0.5) {
temp2 = l * (1 + s);
} else {
temp2 = l + s - (s * l);
}
temp1 = 2 * l - temp2;
r = 255 * goog.color.hueToRgb_(temp1, temp2, normH + (1 / 3));
g = 255 * goog.color.hueToRgb_(temp1, temp2, normH);
b = 255 * goog.color.hueToRgb_(temp1, temp2, normH - (1 / 3));
}
return [Math.round(r), Math.round(g), Math.round(b)];
};
/**
* Converts a color from HSL color space to RGB color space.
* @param {goog.color.Hsl} hsl hsl representation of the color.
* @return {!goog.color.Rgb} rgb representation of the color.
*/
goog.color.hslArrayToRgb = function(hsl) {
return goog.color.hslToRgb(hsl[0], hsl[1], hsl[2]);
};
/**
* Helper for isValidHexColor_.
* @type {RegExp}
* @private
*/
goog.color.validHexColorRe_ = /^#(?:[0-9a-f]{3}){1,2}$/i;
/**
* Checks if a string is a valid hex color. We expect strings of the format
* #RRGGBB (ex: #1b3d5f) or #RGB (ex: #3CA == #33CCAA).
* @param {string} str String to check.
* @return {boolean} Whether the string is a valid hex color.
* @private
*/
goog.color.isValidHexColor_ = function(str) {
return goog.color.validHexColorRe_.test(str);
};
/**
* Helper for isNormalizedHexColor_.
* @type {RegExp}
* @private
*/
goog.color.normalizedHexColorRe_ = /^#[0-9a-f]{6}$/;
/**
* Checks if a string is a normalized hex color.
* We expect strings of the format #RRGGBB (ex: #1b3d5f)
* using only lowercase letters.
* @param {string} str String to check.
* @return {boolean} Whether the string is a normalized hex color.
* @private
*/
goog.color.isNormalizedHexColor_ = function(str) {
return goog.color.normalizedHexColorRe_.test(str);
};
/**
* Regular expression for matching and capturing RGB style strings. Helper for
* isValidRgbColor_.
* @type {RegExp}
* @private
*/
goog.color.rgbColorRe_ =
/^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i;
/**
* Checks if a string is a valid rgb color. We expect strings of the format
* '(r, g, b)', or 'rgb(r, g, b)', where each color component is an int in
* [0, 255].
* @param {string} str String to check.
* @return {!goog.color.Rgb} the rgb representation of the color if it is
* a valid color, or the empty array otherwise.
* @private
*/
goog.color.isValidRgbColor_ = function(str) {
// Each component is separate (rather than using a repeater) so we can
// capture the match. Also, we explicitly set each component to be either 0,
// or start with a non-zero, to prevent octal numbers from slipping through.
var regExpResultArray = str.match(goog.color.rgbColorRe_);
if (regExpResultArray) {
var r = Number(regExpResultArray[1]);
var g = Number(regExpResultArray[2]);
var b = Number(regExpResultArray[3]);
if (r >= 0 && r <= 255 &&
g >= 0 && g <= 255 &&
b >= 0 && b <= 255) {
return [r, g, b];
}
}
return [];
};
/**
* Takes a hex value and prepends a zero if it's a single digit.
* Small helper method for use by goog.color and friends.
* @param {string} hex Hex value to prepend if single digit.
* @return {string} hex value prepended with zero if it was single digit,
* otherwise the same value that was passed in.
*/
goog.color.prependZeroIfNecessaryHelper = function(hex) {
return hex.length == 1 ? '0' + hex : hex;
};
/**
* Takes a string a prepends a '#' sign if one doesn't exist.
* Small helper method for use by goog.color and friends.
* @param {string} str String to check.
* @return {string} The value passed in, prepended with a '#' if it didn't
* already have one.
*/
goog.color.prependHashIfNecessaryHelper = function(str) {
return str.charAt(0) == '#' ? str : '#' + str;
};
/**
* Takes an array of [r, g, b] and converts it into a string appropriate for
* CSS styles.
* @param {goog.color.Rgb} rgb rgb representation of the color.
* @return {string} string of the form 'rgb(r,g,b)'.
* @private
*/
goog.color.rgbStyle_ = function(rgb) {
return 'rgb(' + rgb.join(',') + ')';
};
/**
* Converts an HSV triplet to an RGB array. V is brightness because b is
* reserved for blue in RGB.
* @param {number} h Hue value in [0, 360].
* @param {number} s Saturation value in [0, 1].
* @param {number} brightness brightness in [0, 255].
* @return {!goog.color.Rgb} rgb representation of the color.
*/
goog.color.hsvToRgb = function(h, s, brightness) {
var red = 0;
var green = 0;
var blue = 0;
if (s == 0) {
red = brightness;
green = brightness;
blue = brightness;
} else {
var sextant = Math.floor(h / 60);
var remainder = (h / 60) - sextant;
var val1 = brightness * (1 - s);
var val2 = brightness * (1 - (s * remainder));
var val3 = brightness * (1 - (s * (1 - remainder)));
switch (sextant) {
case 1:
red = val2;
green = brightness;
blue = val1;
break;
case 2:
red = val1;
green = brightness;
blue = val3;
break;
case 3:
red = val1;
green = val2;
blue = brightness;
break;
case 4:
red = val3;
green = val1;
blue = brightness;
break;
case 5:
red = brightness;
green = val1;
blue = val2;
break;
case 6:
case 0:
red = brightness;
green = val3;
blue = val1;
break;
}
}
return [Math.floor(red), Math.floor(green), Math.floor(blue)];
};
/**
* Converts from RGB values to an array of HSV values.
* @param {number} red Red value in [0, 255].
* @param {number} green Green value in [0, 255].
* @param {number} blue Blue value in [0, 255].
* @return {!goog.color.Hsv} hsv representation of the color.
*/
goog.color.rgbToHsv = function(red, green, blue) {
var max = Math.max(Math.max(red, green), blue);
var min = Math.min(Math.min(red, green), blue);
var hue;
var saturation;
var value = max;
if (min == max) {
hue = 0;
saturation = 0;
} else {
var delta = (max - min);
saturation = delta / max;
if (red == max) {
hue = (green - blue) / delta;
} else if (green == max) {
hue = 2 + ((blue - red) / delta);
} else {
hue = 4 + ((red - green) / delta);
}
hue *= 60;
if (hue < 0) {
hue += 360;
}
if (hue > 360) {
hue -= 360;
}
}
return [hue, saturation, value];
};
/**
* Converts from an array of RGB values to an array of HSV values.
* @param {goog.color.Rgb} rgb rgb representation of the color.
* @return {!goog.color.Hsv} hsv representation of the color.
*/
goog.color.rgbArrayToHsv = function(rgb) {
return goog.color.rgbToHsv(rgb[0], rgb[1], rgb[2]);
};
/**
* Converts an HSV triplet to an RGB array.
* @param {goog.color.Hsv} hsv hsv representation of the color.
* @return {!goog.color.Rgb} rgb representation of the color.
*/
goog.color.hsvArrayToRgb = function(hsv) {
return goog.color.hsvToRgb(hsv[0], hsv[1], hsv[2]);
};
/**
* Converts a hex representation of a color to HSL.
* @param {string} hex Color to convert.
* @return {!goog.color.Hsv} hsv representation of the color.
*/
goog.color.hexToHsl = function(hex) {
var rgb = goog.color.hexToRgb(hex);
return goog.color.rgbToHsl(rgb[0], rgb[1], rgb[2]);
};
/**
* Converts from h,s,l values to a hex string
* @param {number} h Hue, in [0, 360].
* @param {number} s Saturation, in [0, 1].
* @param {number} l Luminosity, in [0, 1].
* @return {string} hex representation of the color.
*/
goog.color.hslToHex = function(h, s, l) {
return goog.color.rgbArrayToHex(goog.color.hslToRgb(h, s, l));
};
/**
* Converts from an hsl array to a hex string
* @param {goog.color.Hsl} hsl hsl representation of the color.
* @return {string} hex representation of the color.
*/
goog.color.hslArrayToHex = function(hsl) {
return goog.color.rgbArrayToHex(goog.color.hslToRgb(hsl[0], hsl[1], hsl[2]));
};
/**
* Converts a hex representation of a color to HSV
* @param {string} hex Color to convert.
* @return {!goog.color.Hsv} hsv representation of the color.
*/
goog.color.hexToHsv = function(hex) {
return goog.color.rgbArrayToHsv(goog.color.hexToRgb(hex));
};
/**
* Converts from h,s,v values to a hex string
* @param {number} h Hue, in [0, 360].
* @param {number} s Saturation, in [0, 1].
* @param {number} v Value, in [0, 255].
* @return {string} hex representation of the color.
*/
goog.color.hsvToHex = function(h, s, v) {
return goog.color.rgbArrayToHex(goog.color.hsvToRgb(h, s, v));
};
/**
* Converts from an HSV array to a hex string
* @param {goog.color.Hsv} hsv hsv representation of the color.
* @return {string} hex representation of the color.
*/
goog.color.hsvArrayToHex = function(hsv) {
return goog.color.hsvToHex(hsv[0], hsv[1], hsv[2]);
};
/**
* Calculates the Euclidean distance between two color vectors on an HSL sphere.
* A demo of the sphere can be found at:
* http://en.wikipedia.org/wiki/HSL_color_space
* In short, a vector for color (H, S, L) in this system can be expressed as
* (S*L'*cos(2*PI*H), S*L'*sin(2*PI*H), L), where L' = abs(L - 0.5), and we
* simply calculate the 1-2 distance using these coordinates
* @param {goog.color.Hsl} hsl1 First color in hsl representation.
* @param {goog.color.Hsl} hsl2 Second color in hsl representation.
* @return {number} Distance between the two colors, in the range [0, 1].
*/
goog.color.hslDistance = function(hsl1, hsl2) {
var sl1, sl2;
if (hsl1[2] <= 0.5) {
sl1 = hsl1[1] * hsl1[2];
} else {
sl1 = hsl1[1] * (1.0 - hsl1[2]);
}
if (hsl2[2] <= 0.5) {
sl2 = hsl2[1] * hsl2[2];
} else {
sl2 = hsl2[1] * (1.0 - hsl2[2]);
}
var h1 = hsl1[0] / 360.0;
var h2 = hsl2[0] / 360.0;
var dh = (h1 - h2) * 2.0 * Math.PI;
return (hsl1[2] - hsl2[2]) * (hsl1[2] - hsl2[2]) +
sl1 * sl1 + sl2 * sl2 - 2 * sl1 * sl2 * Math.cos(dh);
};
/**
* Blend two colors together, using the specified factor to indicate the weight
* given to the first color
* @param {goog.color.Rgb} rgb1 First color represented in rgb.
* @param {goog.color.Rgb} rgb2 Second color represented in rgb.
* @param {number} factor The weight to be given to rgb1 over rgb2. Values
* should be in the range [0, 1]. If less than 0, factor will be set to 0.
* If greater than 1, factor will be set to 1.
* @return {!goog.color.Rgb} Combined color represented in rgb.
*/
goog.color.blend = function(rgb1, rgb2, factor) {
factor = goog.math.clamp(factor, 0, 1);
return [
Math.round(factor * rgb1[0] + (1.0 - factor) * rgb2[0]),
Math.round(factor * rgb1[1] + (1.0 - factor) * rgb2[1]),
Math.round(factor * rgb1[2] + (1.0 - factor) * rgb2[2])
];
};
/**
* Adds black to the specified color, darkening it
* @param {goog.color.Rgb} rgb rgb representation of the color.
* @param {number} factor Number in the range [0, 1]. 0 will do nothing, while
* 1 will return black. If less than 0, factor will be set to 0. If greater
* than 1, factor will be set to 1.
* @return {!goog.color.Rgb} Combined rgb color.
*/
goog.color.darken = function(rgb, factor) {
var black = [0, 0, 0];
return goog.color.blend(black, rgb, factor);
};
/**
* Adds white to the specified color, lightening it
* @param {goog.color.Rgb} rgb rgb representation of the color.
* @param {number} factor Number in the range [0, 1]. 0 will do nothing, while
* 1 will return white. If less than 0, factor will be set to 0. If greater
* than 1, factor will be set to 1.
* @return {!goog.color.Rgb} Combined rgb color.
*/
goog.color.lighten = function(rgb, factor) {
var white = [255, 255, 255];
return goog.color.blend(white, rgb, factor);
};
/**
* Find the "best" (highest-contrast) of the suggested colors for the prime
* color. Uses W3C formula for judging readability and visual accessibility:
* http://www.w3.org/TR/AERT#color-contrast
* @param {goog.color.Rgb} prime Color represented as a rgb array.
* @param {Array.<goog.color.Rgb>} suggestions Array of colors,
* each representing a rgb array.
* @return {!goog.color.Rgb} Highest-contrast color represented by an array..
*/
goog.color.highContrast = function(prime, suggestions) {
var suggestionsWithDiff = [];
for (var i = 0; i < suggestions.length; i++) {
suggestionsWithDiff.push({
color: suggestions[i],
diff: goog.color.yiqBrightnessDiff_(suggestions[i], prime) +
goog.color.colorDiff_(suggestions[i], prime)
});
}
suggestionsWithDiff.sort(function(a, b) {
return b.diff - a.diff;
});
return suggestionsWithDiff[0].color;
};
/**
* Calculate brightness of a color according to YIQ formula (brightness is Y).
* More info on YIQ here: http://en.wikipedia.org/wiki/YIQ. Helper method for
* goog.color.highContrast()
* @param {goog.color.Rgb} rgb Color represented by a rgb array.
* @return {number} brightness (Y).
* @private
*/
goog.color.yiqBrightness_ = function(rgb) {
return Math.round((rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000);
};
/**
* Calculate difference in brightness of two colors. Helper method for
* goog.color.highContrast()
* @param {goog.color.Rgb} rgb1 Color represented by a rgb array.
* @param {goog.color.Rgb} rgb2 Color represented by a rgb array.
* @return {number} Brightness difference.
* @private
*/
goog.color.yiqBrightnessDiff_ = function(rgb1, rgb2) {
return Math.abs(goog.color.yiqBrightness_(rgb1) -
goog.color.yiqBrightness_(rgb2));
};
/**
* Calculate color difference between two colors. Helper method for
* goog.color.highContrast()
* @param {goog.color.Rgb} rgb1 Color represented by a rgb array.
* @param {goog.color.Rgb} rgb2 Color represented by a rgb array.
* @return {number} Color difference.
* @private
*/
goog.color.colorDiff_ = function(rgb1, rgb2) {
return Math.abs(rgb1[0] - rgb2[0]) + Math.abs(rgb1[1] - rgb2[1]) +
Math.abs(rgb1[2] - rgb2[2]);
};

View File

@@ -0,0 +1,176 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Names of standard colors with their associated hex values.
*/
goog.provide('goog.color.names');
/**
* A map that contains a lot of colors that are recognised by various browsers.
* This list is way larger than the minimal one dictated by W3C.
* The keys of this map are the lowercase "readable" names of the colors, while
* the values are the "hex" values.
*/
goog.color.names = {
'aliceblue': '#f0f8ff',
'antiquewhite': '#faebd7',
'aqua': '#00ffff',
'aquamarine': '#7fffd4',
'azure': '#f0ffff',
'beige': '#f5f5dc',
'bisque': '#ffe4c4',
'black': '#000000',
'blanchedalmond': '#ffebcd',
'blue': '#0000ff',
'blueviolet': '#8a2be2',
'brown': '#a52a2a',
'burlywood': '#deb887',
'cadetblue': '#5f9ea0',
'chartreuse': '#7fff00',
'chocolate': '#d2691e',
'coral': '#ff7f50',
'cornflowerblue': '#6495ed',
'cornsilk': '#fff8dc',
'crimson': '#dc143c',
'cyan': '#00ffff',
'darkblue': '#00008b',
'darkcyan': '#008b8b',
'darkgoldenrod': '#b8860b',
'darkgray': '#a9a9a9',
'darkgreen': '#006400',
'darkgrey': '#a9a9a9',
'darkkhaki': '#bdb76b',
'darkmagenta': '#8b008b',
'darkolivegreen': '#556b2f',
'darkorange': '#ff8c00',
'darkorchid': '#9932cc',
'darkred': '#8b0000',
'darksalmon': '#e9967a',
'darkseagreen': '#8fbc8f',
'darkslateblue': '#483d8b',
'darkslategray': '#2f4f4f',
'darkslategrey': '#2f4f4f',
'darkturquoise': '#00ced1',
'darkviolet': '#9400d3',
'deeppink': '#ff1493',
'deepskyblue': '#00bfff',
'dimgray': '#696969',
'dimgrey': '#696969',
'dodgerblue': '#1e90ff',
'firebrick': '#b22222',
'floralwhite': '#fffaf0',
'forestgreen': '#228b22',
'fuchsia': '#ff00ff',
'gainsboro': '#dcdcdc',
'ghostwhite': '#f8f8ff',
'gold': '#ffd700',
'goldenrod': '#daa520',
'gray': '#808080',
'green': '#008000',
'greenyellow': '#adff2f',
'grey': '#808080',
'honeydew': '#f0fff0',
'hotpink': '#ff69b4',
'indianred': '#cd5c5c',
'indigo': '#4b0082',
'ivory': '#fffff0',
'khaki': '#f0e68c',
'lavender': '#e6e6fa',
'lavenderblush': '#fff0f5',
'lawngreen': '#7cfc00',
'lemonchiffon': '#fffacd',
'lightblue': '#add8e6',
'lightcoral': '#f08080',
'lightcyan': '#e0ffff',
'lightgoldenrodyellow': '#fafad2',
'lightgray': '#d3d3d3',
'lightgreen': '#90ee90',
'lightgrey': '#d3d3d3',
'lightpink': '#ffb6c1',
'lightsalmon': '#ffa07a',
'lightseagreen': '#20b2aa',
'lightskyblue': '#87cefa',
'lightslategray': '#778899',
'lightslategrey': '#778899',
'lightsteelblue': '#b0c4de',
'lightyellow': '#ffffe0',
'lime': '#00ff00',
'limegreen': '#32cd32',
'linen': '#faf0e6',
'magenta': '#ff00ff',
'maroon': '#800000',
'mediumaquamarine': '#66cdaa',
'mediumblue': '#0000cd',
'mediumorchid': '#ba55d3',
'mediumpurple': '#9370db',
'mediumseagreen': '#3cb371',
'mediumslateblue': '#7b68ee',
'mediumspringgreen': '#00fa9a',
'mediumturquoise': '#48d1cc',
'mediumvioletred': '#c71585',
'midnightblue': '#191970',
'mintcream': '#f5fffa',
'mistyrose': '#ffe4e1',
'moccasin': '#ffe4b5',
'navajowhite': '#ffdead',
'navy': '#000080',
'oldlace': '#fdf5e6',
'olive': '#808000',
'olivedrab': '#6b8e23',
'orange': '#ffa500',
'orangered': '#ff4500',
'orchid': '#da70d6',
'palegoldenrod': '#eee8aa',
'palegreen': '#98fb98',
'paleturquoise': '#afeeee',
'palevioletred': '#db7093',
'papayawhip': '#ffefd5',
'peachpuff': '#ffdab9',
'peru': '#cd853f',
'pink': '#ffc0cb',
'plum': '#dda0dd',
'powderblue': '#b0e0e6',
'purple': '#800080',
'red': '#ff0000',
'rosybrown': '#bc8f8f',
'royalblue': '#4169e1',
'saddlebrown': '#8b4513',
'salmon': '#fa8072',
'sandybrown': '#f4a460',
'seagreen': '#2e8b57',
'seashell': '#fff5ee',
'sienna': '#a0522d',
'silver': '#c0c0c0',
'skyblue': '#87ceeb',
'slateblue': '#6a5acd',
'slategray': '#708090',
'slategrey': '#708090',
'snow': '#fffafa',
'springgreen': '#00ff7f',
'steelblue': '#4682b4',
'tan': '#d2b48c',
'teal': '#008080',
'thistle': '#d8bfd8',
'tomato': '#ff6347',
'turquoise': '#40e0d0',
'violet': '#ee82ee',
'wheat': '#f5deb3',
'white': '#ffffff',
'whitesmoke': '#f5f5f5',
'yellow': '#ffff00',
'yellowgreen': '#9acd32'
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,162 @@
// Copyright 2005 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 ARC4 streamcipher implementation. A description of the
* algorithm can be found at:
* http://www.mozilla.org/projects/security/pki/nss/draft-kaukonen-cipher-arcfour-03.txt.
*
* Usage:
* <code>
* var arc4 = new goog.crypt.Arc4();
* arc4.setKey(key);
* arc4.discard(1536);
* arc4.crypt(bytes);
* </code>
*
* Note: For converting between strings and byte arrays, goog.crypt.base64 may
* be useful.
*
*/
goog.provide('goog.crypt.Arc4');
goog.require('goog.asserts');
/**
* ARC4 streamcipher implementation.
* @constructor
*/
goog.crypt.Arc4 = function() {
/**
* A permutation of all 256 possible bytes.
* @type {Array.<number>}
* @private
*/
this.state_ = [];
/**
* 8 bit index pointer into this.state_.
* @type {number}
* @private
*/
this.index1_ = 0;
/**
* 8 bit index pointer into this.state_.
* @type {number}
* @private
*/
this.index2_ = 0;
};
/**
* Initialize the cipher for use with new key.
* @param {Array.<number>} key A byte array containing the key.
* @param {number=} opt_length Indicates # of bytes to take from the key.
*/
goog.crypt.Arc4.prototype.setKey = function(key, opt_length) {
goog.asserts.assertArray(key, 'Key parameter must be a byte array');
if (!opt_length) {
opt_length = key.length;
}
var state = this.state_;
for (var i = 0; i < 256; ++i) {
state[i] = i;
}
var j = 0;
for (var i = 0; i < 256; ++i) {
j = (j + state[i] + key[i % opt_length]) & 255;
var tmp = state[i];
state[i] = state[j];
state[j] = tmp;
}
this.index1_ = 0;
this.index2_ = 0;
};
/**
* Discards n bytes of the keystream.
* These days 1536 is considered a decent amount to drop to get the key state
* warmed-up enough for secure usage. This is not done in the constructor to
* preserve efficiency for use cases that do not need this.
* NOTE: Discard is identical to crypt without actually xoring any data. It's
* unfortunate to have this code duplicated, but this was done for performance
* reasons. Alternatives which were attempted:
* 1. Create a temp array of the correct length and pass it to crypt. This
* works but needlessly allocates an array. But more importantly this
* requires choosing an array type (Array or Uint8Array) in discard, and
* choosing a different type than will be passed to crypt by the client
* code hurts the javascript engines ability to optimize crypt (7x hit in
* v8).
* 2. Make data option in crypt so discard can pass null, this has a huge
* perf hit for crypt.
* @param {number} length Number of bytes to disregard from the stream.
*/
goog.crypt.Arc4.prototype.discard = function(length) {
var i = this.index1_;
var j = this.index2_;
var state = this.state_;
for (var n = 0; n < length; ++n) {
i = (i + 1) & 255;
j = (j + state[i]) & 255;
var tmp = state[i];
state[i] = state[j];
state[j] = tmp;
}
this.index1_ = i;
this.index2_ = j;
};
/**
* En- or decrypt (same operation for streamciphers like ARC4)
* @param {Array.<number>|Uint8Array} data The data to be xor-ed in place.
* @param {number=} opt_length The number of bytes to crypt.
*/
goog.crypt.Arc4.prototype.crypt = function(data, opt_length) {
if (!opt_length) {
opt_length = data.length;
}
var i = this.index1_;
var j = this.index2_;
var state = this.state_;
for (var n = 0; n < opt_length; ++n) {
i = (i + 1) & 255;
j = (j + state[i]) & 255;
var tmp = state[i];
state[i] = state[j];
state[j] = tmp;
data[n] ^= state[(state[i] + state[j]) & 255];
}
this.index1_ = i;
this.index2_ = j;
};

View File

@@ -0,0 +1,272 @@
// Copyright 2007 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 Base64 en/decoding. Not much to say here except that we
* work with decoded values in arrays of bytes. By "byte" I mean a number
* in [0, 255].
*
* @author doughtie@google.com (Gavin Doughtie)
* @author fschneider@google.com (Fritz Schneider)
*/
goog.provide('goog.crypt.base64');
goog.require('goog.crypt');
goog.require('goog.userAgent');
// Static lookup maps, lazily populated by init_()
/**
* Maps bytes to characters.
* @type {Object}
* @private
*/
goog.crypt.base64.byteToCharMap_ = null;
/**
* Maps characters to bytes.
* @type {Object}
* @private
*/
goog.crypt.base64.charToByteMap_ = null;
/**
* Maps bytes to websafe characters.
* @type {Object}
* @private
*/
goog.crypt.base64.byteToCharMapWebSafe_ = null;
/**
* Maps websafe characters to bytes.
* @type {Object}
* @private
*/
goog.crypt.base64.charToByteMapWebSafe_ = null;
/**
* Our default alphabet, shared between
* ENCODED_VALS and ENCODED_VALS_WEBSAFE
* @type {string}
*/
goog.crypt.base64.ENCODED_VALS_BASE =
'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
'abcdefghijklmnopqrstuvwxyz' +
'0123456789';
/**
* Our default alphabet. Value 64 (=) is special; it means "nothing."
* @type {string}
*/
goog.crypt.base64.ENCODED_VALS =
goog.crypt.base64.ENCODED_VALS_BASE + '+/=';
/**
* Our websafe alphabet.
* @type {string}
*/
goog.crypt.base64.ENCODED_VALS_WEBSAFE =
goog.crypt.base64.ENCODED_VALS_BASE + '-_.';
/**
* Whether this browser supports the atob and btoa functions. This extension
* started at Mozilla but is now implemented by many browsers. We use the
* ASSUME_* variables to avoid pulling in the full useragent detection library
* but still allowing the standard per-browser compilations.
*
* @type {boolean}
*/
goog.crypt.base64.HAS_NATIVE_SUPPORT = goog.userAgent.GECKO ||
goog.userAgent.WEBKIT ||
goog.userAgent.OPERA ||
typeof(goog.global.atob) == 'function';
/**
* Base64-encode an array of bytes.
*
* @param {Array.<number>|Uint8Array} input An array of bytes (numbers with
* value in [0, 255]) to encode.
* @param {boolean=} opt_webSafe Boolean indicating we should use the
* alternative alphabet.
* @return {string} The base64 encoded string.
*/
goog.crypt.base64.encodeByteArray = function(input, opt_webSafe) {
if (!goog.isArrayLike(input)) {
throw Error('encodeByteArray takes an array as a parameter');
}
goog.crypt.base64.init_();
var byteToCharMap = opt_webSafe ?
goog.crypt.base64.byteToCharMapWebSafe_ :
goog.crypt.base64.byteToCharMap_;
var output = [];
for (var i = 0; i < input.length; i += 3) {
var byte1 = input[i];
var haveByte2 = i + 1 < input.length;
var byte2 = haveByte2 ? input[i + 1] : 0;
var haveByte3 = i + 2 < input.length;
var byte3 = haveByte3 ? input[i + 2] : 0;
var outByte1 = byte1 >> 2;
var outByte2 = ((byte1 & 0x03) << 4) | (byte2 >> 4);
var outByte3 = ((byte2 & 0x0F) << 2) | (byte3 >> 6);
var outByte4 = byte3 & 0x3F;
if (!haveByte3) {
outByte4 = 64;
if (!haveByte2) {
outByte3 = 64;
}
}
output.push(byteToCharMap[outByte1],
byteToCharMap[outByte2],
byteToCharMap[outByte3],
byteToCharMap[outByte4]);
}
return output.join('');
};
/**
* Base64-encode a string.
*
* @param {string} input A string to encode.
* @param {boolean=} opt_webSafe If true, we should use the
* alternative alphabet.
* @return {string} The base64 encoded string.
*/
goog.crypt.base64.encodeString = function(input, opt_webSafe) {
// Shortcut for Mozilla browsers that implement
// a native base64 encoder in the form of "btoa/atob"
if (goog.crypt.base64.HAS_NATIVE_SUPPORT && !opt_webSafe) {
return goog.global.btoa(input);
}
return goog.crypt.base64.encodeByteArray(
goog.crypt.stringToByteArray(input), opt_webSafe);
};
/**
* Base64-decode a string.
*
* @param {string} input to decode.
* @param {boolean=} opt_webSafe True if we should use the
* alternative alphabet.
* @return {string} string representing the decoded value.
*/
goog.crypt.base64.decodeString = function(input, opt_webSafe) {
// Shortcut for Mozilla browsers that implement
// a native base64 encoder in the form of "btoa/atob"
if (goog.crypt.base64.HAS_NATIVE_SUPPORT && !opt_webSafe) {
return goog.global.atob(input);
}
return goog.crypt.byteArrayToString(
goog.crypt.base64.decodeStringToByteArray(input, opt_webSafe));
};
/**
* Base64-decode a string.
*
* @param {string} input to decode (length not required to be a multiple of 4).
* @param {boolean=} opt_webSafe True if we should use the
* alternative alphabet.
* @return {Array} bytes representing the decoded value.
*/
goog.crypt.base64.decodeStringToByteArray = function(input, opt_webSafe) {
goog.crypt.base64.init_();
var charToByteMap = opt_webSafe ?
goog.crypt.base64.charToByteMapWebSafe_ :
goog.crypt.base64.charToByteMap_;
var output = [];
for (var i = 0; i < input.length; ) {
var byte1 = charToByteMap[input.charAt(i++)];
var haveByte2 = i < input.length;
var byte2 = haveByte2 ? charToByteMap[input.charAt(i)] : 0;
++i;
var haveByte3 = i < input.length;
var byte3 = haveByte3 ? charToByteMap[input.charAt(i)] : 0;
++i;
var haveByte4 = i < input.length;
var byte4 = haveByte4 ? charToByteMap[input.charAt(i)] : 0;
++i;
if (byte1 == null || byte2 == null ||
byte3 == null || byte4 == null) {
throw Error();
}
var outByte1 = (byte1 << 2) | (byte2 >> 4);
output.push(outByte1);
if (byte3 != 64) {
var outByte2 = ((byte2 << 4) & 0xF0) | (byte3 >> 2);
output.push(outByte2);
if (byte4 != 64) {
var outByte3 = ((byte3 << 6) & 0xC0) | byte4;
output.push(outByte3);
}
}
}
return output;
};
/**
* Lazy static initialization function. Called before
* accessing any of the static map variables.
* @private
*/
goog.crypt.base64.init_ = function() {
if (!goog.crypt.base64.byteToCharMap_) {
goog.crypt.base64.byteToCharMap_ = {};
goog.crypt.base64.charToByteMap_ = {};
goog.crypt.base64.byteToCharMapWebSafe_ = {};
goog.crypt.base64.charToByteMapWebSafe_ = {};
// We want quick mappings back and forth, so we precompute two maps.
for (var i = 0; i < goog.crypt.base64.ENCODED_VALS.length; i++) {
goog.crypt.base64.byteToCharMap_[i] =
goog.crypt.base64.ENCODED_VALS.charAt(i);
goog.crypt.base64.charToByteMap_[goog.crypt.base64.byteToCharMap_[i]] = i;
goog.crypt.base64.byteToCharMapWebSafe_[i] =
goog.crypt.base64.ENCODED_VALS_WEBSAFE.charAt(i);
goog.crypt.base64.charToByteMapWebSafe_[
goog.crypt.base64.byteToCharMapWebSafe_[i]] = i;
}
}
};

View File

@@ -0,0 +1,242 @@
// Copyright 2007 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 Numeric base conversion library. Works for arbitrary bases and
* arbitrary length numbers.
*
* For base-64 conversion use base64.js because it is optimized for the specific
* conversion to base-64 while this module is generic. Base-64 is defined here
* mostly for demonstration purpose.
*
* TODO: Make base64 and baseN classes that have common interface. (Perhaps...)
*
*/
goog.provide('goog.crypt.baseN');
/**
* Base-2, i.e. '01'.
* @type {string}
*/
goog.crypt.baseN.BASE_BINARY = '01';
/**
* Base-8, i.e. '01234567'.
* @type {string}
*/
goog.crypt.baseN.BASE_OCTAL = '01234567';
/**
* Base-10, i.e. '0123456789'.
* @type {string}
*/
goog.crypt.baseN.BASE_DECIMAL = '0123456789';
/**
* Base-16 using lower case, i.e. '0123456789abcdef'.
* @type {string}
*/
goog.crypt.baseN.BASE_LOWERCASE_HEXADECIMAL = '0123456789abcdef';
/**
* Base-16 using upper case, i.e. '0123456789ABCDEF'.
* @type {string}
*/
goog.crypt.baseN.BASE_UPPERCASE_HEXADECIMAL = '0123456789ABCDEF';
/**
* The more-known version of the BASE-64 encoding. Uses + and / characters.
* @type {string}
*/
goog.crypt.baseN.BASE_64 =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
/**
* URL-safe version of the BASE-64 encoding.
* @type {string}
*/
goog.crypt.baseN.BASE_64_URL_SAFE =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
/**
* Converts a number from one numeric base to another.
*
* The bases are represented as strings, which list allowed digits. Each digit
* should be unique. The bases can either be user defined, or any of
* goog.crypt.baseN.BASE_xxx.
*
* The number is in human-readable format, most significant digit first, and is
* a non-negative integer. Base designators such as $, 0x, d, b or h (at end)
* will be interpreted as digits, so avoid them. Leading zeros will be trimmed.
*
* Note: for huge bases the result may be inaccurate because of overflowing
* 64-bit doubles used by JavaScript for integer calculus. This may happen
* if the product of the number of digits in the input and output bases comes
* close to 10^16, which is VERY unlikely (100M digits in each base), but
* may be possible in the future unicode world. (Unicode 3.2 has less than 100K
* characters. However, it reserves some more, close to 1M.)
*
* @param {string} number The number to convert.
* @param {string} inputBase The numeric base the number is in (all digits).
* @param {string} outputBase Requested numeric base.
* @return {string} The converted number.
*/
goog.crypt.baseN.recodeString = function(number, inputBase, outputBase) {
if (outputBase == '') {
throw Error('Empty output base');
}
// Check if number is 0 (special case when we don't want to return '').
var isZero = true;
for (var i = 0, n = number.length; i < n; i++) {
if (number.charAt(i) != inputBase.charAt(0)) {
isZero = false;
break;
}
}
if (isZero) {
return outputBase.charAt(0);
}
var numberDigits = goog.crypt.baseN.stringToArray_(number, inputBase);
var inputBaseSize = inputBase.length;
var outputBaseSize = outputBase.length;
// result = 0.
var result = [];
// For all digits of number, starting with the most significant ...
for (var i = numberDigits.length - 1; i >= 0; i--) {
// result *= number.base.
var carry = 0;
for (var j = 0, n = result.length; j < n; j++) {
var digit = result[j];
// This may overflow for huge bases. See function comment.
digit = digit * inputBaseSize + carry;
if (digit >= outputBaseSize) {
var remainder = digit % outputBaseSize;
carry = (digit - remainder) / outputBaseSize;
digit = remainder;
} else {
carry = 0;
}
result[j] = digit;
}
while (carry) {
var remainder = carry % outputBaseSize;
result.push(remainder);
carry = (carry - remainder) / outputBaseSize;
}
// result += number[i].
carry = numberDigits[i];
var j = 0;
while (carry) {
if (j >= result.length) {
// Extend result with a leading zero which will be overwritten below.
result.push(0);
}
var digit = result[j];
digit += carry;
if (digit >= outputBaseSize) {
var remainder = digit % outputBaseSize;
carry = (digit - remainder) / outputBaseSize;
digit = remainder;
} else {
carry = 0;
}
result[j] = digit;
j++;
}
}
return goog.crypt.baseN.arrayToString_(result, outputBase);
};
/**
* Converts a string representation of a number to an array of digit values.
*
* More precisely, the digit values are indices into the number base, which
* is represented as a string, which can either be user defined or one of the
* BASE_xxx constants.
*
* Throws an Error if the number contains a digit not found in the base.
*
* @param {string} number The string to convert, most significant digit first.
* @param {string} base Digits in the base.
* @return {Array.<number>} Array of digit values, least significant digit
* first.
* @private
*/
goog.crypt.baseN.stringToArray_ = function(number, base) {
var index = {};
for (var i = 0, n = base.length; i < n; i++) {
index[base.charAt(i)] = i;
}
var result = [];
for (var i = number.length - 1; i >= 0; i--) {
var character = number.charAt(i);
var digit = index[character];
if (typeof digit == 'undefined') {
throw Error('Number ' + number +
' contains a character not found in base ' +
base + ', which is ' + character);
}
result.push(digit);
}
return result;
};
/**
* Converts an array representation of a number to a string.
*
* More precisely, the elements of the input array are indices into the base,
* which is represented as a string, which can either be user defined or one of
* the BASE_xxx constants.
*
* Throws an Error if the number contains a digit which is outside the range
* 0 ... base.length - 1.
*
* @param {Array.<number>} number Array of digit values, least significant
* first.
* @param {string} base Digits in the base.
* @return {string} Number as a string, most significant digit first.
* @private
*/
goog.crypt.baseN.arrayToString_ = function(number, base) {
var n = number.length;
var chars = [];
var baseSize = base.length;
for (var i = n - 1; i >= 0; i--) {
var digit = number[i];
if (digit >= baseSize || digit < 0) {
throw Error('Number ' + number + ' contains an invalid digit: ' + digit);
}
chars.push(base.charAt(digit));
}
return chars.join('');
};

View File

@@ -0,0 +1,284 @@
// Copyright 2011 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 Asynchronous hash computer for the Blob interface.
*
* The Blob interface, part of the HTML5 File API, is supported on Chrome 7+,
* Firefox 4.0 and Opera 11. No Blob interface implementation is expected on
* Internet Explorer 10. Chrome 11, Firefox 5.0 and the subsequent release of
* Opera are supposed to use vendor prefixes due to evolving API, see
* http://dev.w3.org/2006/webapi/FileAPI/ for details.
*
* This implementation currently uses upcoming Chrome and Firefox prefixes,
* plus the original Blob.slice specification, as implemented on Chrome 10
* and Firefox 4.0.
*
*/
goog.provide('goog.crypt.BlobHasher');
goog.provide('goog.crypt.BlobHasher.EventType');
goog.require('goog.asserts');
goog.require('goog.crypt');
goog.require('goog.crypt.Hash');
goog.require('goog.events.EventTarget');
goog.require('goog.fs');
goog.require('goog.log');
/**
* Construct the hash computer.
*
* @param {!goog.crypt.Hash} hashFn The hash function to use.
* @param {number=} opt_blockSize Processing block size.
* @constructor
* @extends {goog.events.EventTarget}
*/
goog.crypt.BlobHasher = function(hashFn, opt_blockSize) {
goog.base(this);
/**
* The actual hash function.
* @type {!goog.crypt.Hash}
* @private
*/
this.hashFn_ = hashFn;
/**
* The blob being processed or null if no blob is being processed.
* @type {Blob}
* @private
*/
this.blob_ = null;
/**
* Computed hash value.
* @type {Array.<number>}
* @private
*/
this.hashVal_ = null;
/**
* Number of bytes already processed.
* @type {number}
* @private
*/
this.bytesProcessed_ = 0;
/**
* The number of bytes to hash or Infinity for no limit.
* @type {number}
* @private
*/
this.hashingLimit_ = Infinity;
/**
* Processing block size.
* @type {number}
* @private
*/
this.blockSize_ = opt_blockSize || 5000000;
/**
* File reader object. Will be null if no chunk is currently being read.
* @type {FileReader}
* @private
*/
this.fileReader_ = null;
/**
* The logger used by this object.
* @type {goog.log.Logger}
* @private
*/
this.logger_ = goog.log.getLogger('goog.crypt.BlobHasher');
};
goog.inherits(goog.crypt.BlobHasher, goog.events.EventTarget);
/**
* Event names for hash computation events
* @enum {string}
*/
goog.crypt.BlobHasher.EventType = {
STARTED: 'started',
PROGRESS: 'progress',
THROTTLED: 'throttled',
COMPLETE: 'complete',
ABORT: 'abort',
ERROR: 'error'
};
/**
* Start the hash computation.
* @param {!Blob} blob The blob of data to compute the hash for.
*/
goog.crypt.BlobHasher.prototype.hash = function(blob) {
this.abort();
this.hashFn_.reset();
this.blob_ = blob;
this.hashVal_ = null;
this.bytesProcessed_ = 0;
this.dispatchEvent(goog.crypt.BlobHasher.EventType.STARTED);
this.processNextBlock_();
};
/**
* Sets the maximum number of bytes to hash or Infinity for no limit. Can be
* called before hash() to throttle the hash computation. The hash computation
* can then be continued by repeatedly calling setHashingLimit() with greater
* byte offsets. This is useful if you don't need the hash until some time in
* the future, for example when uploading a file and you don't need the hash
* until the transfer is complete.
* @param {number} byteOffset The byte offset to compute the hash up to.
* Should be a non-negative integer or Infinity for no limit. Negative
* values are not allowed.
*/
goog.crypt.BlobHasher.prototype.setHashingLimit = function(byteOffset) {
goog.asserts.assert(byteOffset >= 0, 'Hashing limit must be non-negative.');
this.hashingLimit_ = byteOffset;
// Resume processing if a blob is currently being hashed, but no block read
// is currently in progress.
if (this.blob_ && !this.fileReader_) {
this.processNextBlock_();
}
};
/**
* Abort hash computation.
*/
goog.crypt.BlobHasher.prototype.abort = function() {
if (this.fileReader_) {
this.fileReader_.abort();
this.fileReader_ = null;
}
if (this.blob_) {
this.blob_ = null;
this.dispatchEvent(goog.crypt.BlobHasher.EventType.ABORT);
}
};
/**
* @return {number} Number of bytes processed so far.
*/
goog.crypt.BlobHasher.prototype.getBytesProcessed = function() {
return this.bytesProcessed_;
};
/**
* @return {Array.<number>} The computed hash value or null if not ready.
*/
goog.crypt.BlobHasher.prototype.getHash = function() {
return this.hashVal_;
};
/**
* Helper function setting up the processing for the next block, or finalizing
* the computation if all blocks were processed.
* @private
*/
goog.crypt.BlobHasher.prototype.processNextBlock_ = function() {
goog.asserts.assert(this.blob_, 'A hash computation must be in progress.');
if (this.bytesProcessed_ < this.blob_.size) {
if (this.hashingLimit_ <= this.bytesProcessed_) {
// Throttle limit reached. Wait until we are allowed to hash more bytes.
this.dispatchEvent(goog.crypt.BlobHasher.EventType.THROTTLED);
return;
}
// We have to reset the FileReader every time, otherwise it fails on
// Chrome, including the latest Chrome 12 beta.
// http://code.google.com/p/chromium/issues/detail?id=82346
this.fileReader_ = new FileReader();
this.fileReader_.onload = goog.bind(this.onLoad_, this);
this.fileReader_.onerror = goog.bind(this.onError_, this);
var endOffset = Math.min(this.hashingLimit_, this.blob_.size);
var size = Math.min(endOffset - this.bytesProcessed_, this.blockSize_);
var chunk = goog.fs.sliceBlob(this.blob_, this.bytesProcessed_,
this.bytesProcessed_ + size);
if (!chunk || chunk.size != size) {
goog.log.error(this.logger_, 'Failed slicing the blob');
this.onError_();
return;
}
if (this.fileReader_.readAsArrayBuffer) {
this.fileReader_.readAsArrayBuffer(chunk);
} else if (this.fileReader_.readAsBinaryString) {
this.fileReader_.readAsBinaryString(chunk);
} else {
goog.log.error(this.logger_, 'Failed calling the chunk reader');
this.onError_();
}
} else {
this.hashVal_ = this.hashFn_.digest();
this.blob_ = null;
this.dispatchEvent(goog.crypt.BlobHasher.EventType.COMPLETE);
}
};
/**
* Handle processing block loaded.
* @private
*/
goog.crypt.BlobHasher.prototype.onLoad_ = function() {
goog.log.info(this.logger_, 'Successfully loaded a chunk');
var array = null;
if (this.fileReader_.result instanceof Array ||
goog.isString(this.fileReader_.result)) {
array = this.fileReader_.result;
} else if (goog.global['ArrayBuffer'] && goog.global['Uint8Array'] &&
this.fileReader_.result instanceof ArrayBuffer) {
array = new Uint8Array(this.fileReader_.result);
}
if (!array) {
goog.log.error(this.logger_, 'Failed reading the chunk');
this.onError_();
return;
}
this.hashFn_.update(array);
this.bytesProcessed_ += array.length;
this.fileReader_ = null;
this.dispatchEvent(goog.crypt.BlobHasher.EventType.PROGRESS);
this.processNextBlock_();
};
/**
* Handles error.
* @private
*/
goog.crypt.BlobHasher.prototype.onError_ = function() {
this.fileReader_ = null;
this.blob_ = null;
this.dispatchEvent(goog.crypt.BlobHasher.EventType.ERROR);
};

View File

@@ -0,0 +1,52 @@
// Copyright 2012 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 Interface definition of a block cipher. A block cipher is a
* pair of algorithms that implement encryption and decryption of input bytes.
*
* @see http://en.wikipedia.org/wiki/Block_cipher
*
* @author nnaze@google.com (Nathan Naze)
*/
goog.provide('goog.crypt.BlockCipher');
/**
* Interface definition for a block cipher.
* @interface
*/
goog.crypt.BlockCipher = function() {};
/**
* Encrypt a plaintext block. The implementation may expect (and assert)
* a particular block length.
* @param {!Array.<number>} input Plaintext array of input bytes.
* @return {!Array.<number>} Encrypted ciphertext array of bytes. Should be the
* same length as input.
*/
goog.crypt.BlockCipher.prototype.encrypt;
/**
* Decrypt a plaintext block. The implementation may expect (and assert)
* a particular block length.
* @param {!Array.<number>} input Ciphertext. Array of input bytes.
* @return {!Array.<number>} Decrypted plaintext array of bytes. Should be the
* same length as input.
*/
goog.crypt.BlockCipher.prototype.decrypt;

View File

@@ -0,0 +1,150 @@
// Copyright 2012 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 Implementation of CBC mode for block ciphers. See
* http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation
* #Cipher-block_chaining_.28CBC.29. for description.
*
* @author nnaze@google.com (Nathan Naze)
*/
goog.provide('goog.crypt.Cbc');
goog.require('goog.array');
goog.require('goog.crypt');
/**
* Implements the CBC mode for block ciphers. See
* http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation
* #Cipher-block_chaining_.28CBC.29
*
* @param {!goog.crypt.BlockCipher} cipher The block cipher to use.
* @param {number=} opt_blockSize The block size of the cipher in bytes.
* Defaults to 16 bytes.
* @constructor
*/
goog.crypt.Cbc = function(cipher, opt_blockSize) {
/**
* Block cipher.
* @type {!goog.crypt.BlockCipher}
* @private
*/
this.cipher_ = cipher;
/**
* Block size in bytes.
* @type {number}
* @private
*/
this.blockSize_ = opt_blockSize || 16;
};
/**
* Encrypt a message.
*
* @param {!Array.<number>} plainText Message to encrypt. An array of bytes.
* The length should be a multiple of the block size.
* @param {!Array.<number>} initialVector Initial vector for the CBC mode.
* An array of bytes with the same length as the block size.
* @return {!Array.<number>} Encrypted message.
*/
goog.crypt.Cbc.prototype.encrypt = function(plainText, initialVector) {
goog.asserts.assert(
plainText.length % this.blockSize_ == 0,
'Data\'s length must be multiple of block size.');
goog.asserts.assert(
initialVector.length == this.blockSize_,
'Initial vector must be size of one block.');
// Implementation of
// http://en.wikipedia.org/wiki/File:Cbc_encryption.png
var cipherText = [];
var vector = initialVector;
// Generate each block of the encrypted cypher text.
for (var blockStartIndex = 0;
blockStartIndex < plainText.length;
blockStartIndex += this.blockSize_) {
// Takes one block from the input message.
var plainTextBlock = goog.array.slice(
plainText,
blockStartIndex,
blockStartIndex + this.blockSize_);
var input = goog.crypt.xorByteArray(plainTextBlock, vector);
var resultBlock = this.cipher_.encrypt(input);
goog.array.extend(cipherText, resultBlock);
vector = resultBlock;
}
return cipherText;
};
/**
* Decrypt a message.
*
* @param {!Array.<number>} cipherText Message to decrypt. An array of bytes.
* The length should be a multiple of the block size.
* @param {!Array.<number>} initialVector Initial vector for the CBC mode.
* An array of bytes with the same length as the block size.
* @return {!Array.<number>} Decrypted message.
*/
goog.crypt.Cbc.prototype.decrypt = function(cipherText, initialVector) {
goog.asserts.assert(
cipherText.length % this.blockSize_ == 0,
'Data\'s length must be multiple of block size.');
goog.asserts.assert(
initialVector.length == this.blockSize_,
'Initial vector must be size of one block.');
// Implementation of
// http://en.wikipedia.org/wiki/File:Cbc_decryption.png
var plainText = [];
var blockStartIndex = 0;
var vector = initialVector;
// Generate each block of the decrypted plain text.
while (blockStartIndex < cipherText.length) {
// Takes one block.
var cipherTextBlock = goog.array.slice(
cipherText,
blockStartIndex,
blockStartIndex + this.blockSize_);
var resultBlock = this.cipher_.decrypt(cipherTextBlock);
var plainTextBlock = goog.crypt.xorByteArray(vector, resultBlock);
goog.array.extend(plainText, plainTextBlock);
vector = cipherTextBlock;
blockStartIndex += this.blockSize_;
}
return plainText;
};

View File

@@ -0,0 +1,100 @@
// Copyright 2012 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 Unit tests for CBC mode for block ciphers.
*
* @author nnaze@google.com (Nathan Naze)
*/
/** @suppress {extraProvide} */
goog.provide('goog.crypt.CbcTest');
goog.require('goog.crypt');
goog.require('goog.crypt.Aes');
goog.require('goog.crypt.Cbc');
goog.require('goog.testing.jsunit');
goog.setTestOnly('goog.crypt.CbcTest');
function stringToBytes(s) {
var bytes = new Array(s.length);
for (var i = 0; i < s.length; ++i)
bytes[i] = s.charCodeAt(i) & 255;
return bytes;
}
function runCbcAesTest(keyBytes, initialVectorBytes, plainTextBytes,
cipherTextBytes) {
var aes = new goog.crypt.Aes(keyBytes);
var cbc = new goog.crypt.Cbc(aes);
var encryptedBytes = cbc.encrypt(plainTextBytes, initialVectorBytes);
assertEquals('Encrypted bytes should match cipher text.',
goog.crypt.byteArrayToHex(cipherTextBytes),
goog.crypt.byteArrayToHex(encryptedBytes));
var decryptedBytes = cbc.decrypt(cipherTextBytes, initialVectorBytes);
assertEquals('Decrypted bytes should match plain text.',
goog.crypt.byteArrayToHex(plainTextBytes),
goog.crypt.byteArrayToHex(decryptedBytes));
}
function testAesCbcCipherAlgorithm() {
// Test values from http://www.ietf.org/rfc/rfc3602.txt
// Case #1
runCbcAesTest(
goog.crypt.hexToByteArray('06a9214036b8a15b512e03d534120006'),
goog.crypt.hexToByteArray('3dafba429d9eb430b422da802c9fac41'),
stringToBytes('Single block msg'),
goog.crypt.hexToByteArray('e353779c1079aeb82708942dbe77181a'));
// Case #2
runCbcAesTest(
goog.crypt.hexToByteArray('c286696d887c9aa0611bbb3e2025a45a'),
goog.crypt.hexToByteArray('562e17996d093d28ddb3ba695a2e6f58'),
goog.crypt.hexToByteArray(
'000102030405060708090a0b0c0d0e0f' +
'101112131415161718191a1b1c1d1e1f'),
goog.crypt.hexToByteArray(
'd296cd94c2cccf8a3a863028b5e1dc0a' +
'7586602d253cfff91b8266bea6d61ab1'));
// Case #3
runCbcAesTest(
goog.crypt.hexToByteArray('6c3ea0477630ce21a2ce334aa746c2cd'),
goog.crypt.hexToByteArray('c782dc4c098c66cbd9cd27d825682c81'),
stringToBytes('This is a 48-byte message (exactly 3 AES blocks)'),
goog.crypt.hexToByteArray(
'd0a02b3836451753d493665d33f0e886' +
'2dea54cdb293abc7506939276772f8d5' +
'021c19216bad525c8579695d83ba2684'));
// Case #4
runCbcAesTest(
goog.crypt.hexToByteArray('56e47a38c5598974bc46903dba290349'),
goog.crypt.hexToByteArray('8ce82eefbea0da3c44699ed7db51b7d9'),
goog.crypt.hexToByteArray(
'a0a1a2a3a4a5a6a7a8a9aaabacadaeaf' +
'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' +
'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf' +
'd0d1d2d3d4d5d6d7d8d9dadbdcdddedf'),
goog.crypt.hexToByteArray(
'c30e32ffedc0774e6aff6af0869f71aa' +
'0f3af07a9a31a9c684db207eb0ef8e4e' +
'35907aa632c3ffdf868bb7b29d3d46ad' +
'83ce9f9a102ee99d49a53e87f4c3da55'));
}

View File

@@ -0,0 +1,155 @@
// Copyright 2008 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 Namespace with crypto related helper functions.
*/
goog.provide('goog.crypt');
goog.require('goog.array');
/**
* Turns a string into an array of bytes; a "byte" being a JS number in the
* range 0-255.
* @param {string} str String value to arrify.
* @return {!Array.<number>} Array of numbers corresponding to the
* UCS character codes of each character in str.
*/
goog.crypt.stringToByteArray = function(str) {
var output = [], p = 0;
for (var i = 0; i < str.length; i++) {
var c = str.charCodeAt(i);
while (c > 0xff) {
output[p++] = c & 0xff;
c >>= 8;
}
output[p++] = c;
}
return output;
};
/**
* Turns an array of numbers into the string given by the concatenation of the
* characters to which the numbers correspond.
* @param {Array} array Array of numbers representing characters.
* @return {string} Stringification of the array.
*/
goog.crypt.byteArrayToString = function(array) {
return String.fromCharCode.apply(null, array);
};
/**
* Turns an array of numbers into the hex string given by the concatenation of
* the hex values to which the numbers correspond.
* @param {Array} array Array of numbers representing characters.
* @return {string} Hex string.
*/
goog.crypt.byteArrayToHex = function(array) {
return goog.array.map(array, function(numByte) {
var hexByte = numByte.toString(16);
return hexByte.length > 1 ? hexByte : '0' + hexByte;
}).join('');
};
/**
* Converts a hex string into an integer array.
* @param {string} hexString Hex string of 16-bit integers (two characters
* per integer).
* @return {!Array.<number>} Array of {0,255} integers for the given string.
*/
goog.crypt.hexToByteArray = function(hexString) {
goog.asserts.assert(hexString.length % 2 == 0,
'Key string length must be multiple of 2');
var arr = [];
for (var i = 0; i < hexString.length; i += 2) {
arr.push(parseInt(hexString.substring(i, i + 2), 16));
}
return arr;
};
/**
* Converts a JS string to a UTF-8 "byte" array.
* @param {string} str 16-bit unicode string.
* @return {Array.<number>} UTF-8 byte array.
*/
goog.crypt.stringToUtf8ByteArray = function(str) {
// TODO(user): Use native implementations if/when available
str = str.replace(/\r\n/g, '\n');
var out = [], p = 0;
for (var i = 0; i < str.length; i++) {
var c = str.charCodeAt(i);
if (c < 128) {
out[p++] = c;
} else if (c < 2048) {
out[p++] = (c >> 6) | 192;
out[p++] = (c & 63) | 128;
} else {
out[p++] = (c >> 12) | 224;
out[p++] = ((c >> 6) & 63) | 128;
out[p++] = (c & 63) | 128;
}
}
return out;
};
/**
* Converts a UTF-8 byte array to JavaScript's 16-bit Unicode.
* @param {Array.<number>} bytes UTF-8 byte array.
* @return {string} 16-bit Unicode string.
*/
goog.crypt.utf8ByteArrayToString = function(bytes) {
// TODO(user): Use native implementations if/when available
var out = [], pos = 0, c = 0;
while (pos < bytes.length) {
var c1 = bytes[pos++];
if (c1 < 128) {
out[c++] = String.fromCharCode(c1);
} else if (c1 > 191 && c1 < 224) {
var c2 = bytes[pos++];
out[c++] = String.fromCharCode((c1 & 31) << 6 | c2 & 63);
} else {
var c2 = bytes[pos++];
var c3 = bytes[pos++];
out[c++] = String.fromCharCode(
(c1 & 15) << 12 | (c2 & 63) << 6 | c3 & 63);
}
}
return out.join('');
};
/**
* XOR two byte arrays.
* @param {!Array.<number>} bytes1 Byte array 1.
* @param {!Array.<number>} bytes2 Byte array 2.
* @return {!Array.<number>} Resulting XOR of the two byte arrays.
*/
goog.crypt.xorByteArray = function(bytes1, bytes2) {
goog.asserts.assert(
bytes1.length == bytes2.length,
'XOR array lengths must match');
var result = [];
for (var i = 0; i < bytes1.length; i++) {
result.push(bytes1[i] ^ bytes2[i]);
}
return result;
};

View File

@@ -0,0 +1,62 @@
// Copyright 2011 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 Abstract cryptographic hash interface.
*
* See goog.crypt.Sha1 and goog.crypt.Md5 for sample implementations.
*
*/
goog.provide('goog.crypt.Hash');
/**
* Create a cryptographic hash instance.
*
* @constructor
*/
goog.crypt.Hash = function() {};
/**
* Resets the internal accumulator.
*/
goog.crypt.Hash.prototype.reset = goog.abstractMethod;
/**
* Adds a byte array (array with values in [0-255] range) or a string (might
* only contain 8-bit, i.e., Latin1 characters) to the internal accumulator.
*
* Many hash functions operate on blocks of data and implement optimizations
* when a full chunk of data is readily available. Hence it is often preferable
* to provide large chunks of data (a kilobyte or more) than to repeatedly
* call the update method with few tens of bytes. If this is not possible, or
* not feasible, it might be good to provide data in multiplies of hash block
* size (often 64 bytes). Please see the implementation and performance tests
* of your favourite hash.
*
* @param {Array.<number>|Uint8Array|string} bytes Data used for the update.
* @param {number=} opt_length Number of bytes to use.
*/
goog.crypt.Hash.prototype.update = goog.abstractMethod;
/**
* @return {!Array.<number>} The finalized hash computed
* from the internal accumulator.
*/
goog.crypt.Hash.prototype.digest = goog.abstractMethod;

View File

@@ -0,0 +1,184 @@
// Copyright 2008 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 Implementation of 32-bit hashing functions.
*
* This is a direct port from the Google Java Hash class
*
*/
goog.provide('goog.crypt.hash32');
goog.require('goog.crypt');
/**
* Default seed used during hashing, digits of pie.
* See SEED32 in http://go/base.hash.java
* @type {number}
*/
goog.crypt.hash32.SEED32 = 314159265;
/**
* Arbitrary constant used during hashing.
* See CONSTANT32 in http://go/base.hash.java
* @type {number}
*/
goog.crypt.hash32.CONSTANT32 = -1640531527;
/**
* Hashes a string to a 32-bit value.
* @param {string} str String to hash.
* @return {number} 32-bit hash.
*/
goog.crypt.hash32.encodeString = function(str) {
return goog.crypt.hash32.encodeByteArray(goog.crypt.stringToByteArray(str));
};
/**
* Hashes a string to a 32-bit value, converting the string to UTF-8 before
* doing the encoding.
* @param {string} str String to hash.
* @return {number} 32-bit hash.
*/
goog.crypt.hash32.encodeStringUtf8 = function(str) {
return goog.crypt.hash32.encodeByteArray(
goog.crypt.stringToUtf8ByteArray(str));
};
/**
* Hashes an integer to a 32-bit value.
* @param {number} value Number to hash.
* @return {number} 32-bit hash.
*/
goog.crypt.hash32.encodeInteger = function(value) {
// TODO(user): Does this make sense in JavaScript with doubles? Should we
// force the value to be in the correct range?
return goog.crypt.hash32.mix32_({
a: value,
b: goog.crypt.hash32.CONSTANT32,
c: goog.crypt.hash32.SEED32
});
};
/**
* Hashes a "byte" array to a 32-bit value using the supplied seed.
* @param {Array.<number>} bytes Array of bytes.
* @param {number=} opt_offset The starting position to use for hash
* computation.
* @param {number=} opt_length Number of bytes that are used for hashing.
* @param {number=} opt_seed The seed.
* @return {number} 32-bit hash.
*/
goog.crypt.hash32.encodeByteArray = function(
bytes, opt_offset, opt_length, opt_seed) {
var offset = opt_offset || 0;
var length = opt_length || bytes.length;
var seed = opt_seed || goog.crypt.hash32.SEED32;
var mix = {
a: goog.crypt.hash32.CONSTANT32,
b: goog.crypt.hash32.CONSTANT32,
c: seed
};
var keylen;
for (keylen = length; keylen >= 12; keylen -= 12, offset += 12) {
mix.a += goog.crypt.hash32.wordAt_(bytes, offset);
mix.b += goog.crypt.hash32.wordAt_(bytes, offset + 4);
mix.c += goog.crypt.hash32.wordAt_(bytes, offset + 8);
goog.crypt.hash32.mix32_(mix);
}
// Hash any remaining bytes
mix.c += length;
switch (keylen) { // deal with rest. Some cases fall through
case 11: mix.c += (bytes[offset + 10]) << 24;
case 10: mix.c += (bytes[offset + 9] & 0xff) << 16;
case 9 : mix.c += (bytes[offset + 8] & 0xff) << 8;
// the first byte of c is reserved for the length
case 8 :
mix.b += goog.crypt.hash32.wordAt_(bytes, offset + 4);
mix.a += goog.crypt.hash32.wordAt_(bytes, offset);
break;
case 7 : mix.b += (bytes[offset + 6] & 0xff) << 16;
case 6 : mix.b += (bytes[offset + 5] & 0xff) << 8;
case 5 : mix.b += (bytes[offset + 4] & 0xff);
case 4 :
mix.a += goog.crypt.hash32.wordAt_(bytes, offset);
break;
case 3 : mix.a += (bytes[offset + 2] & 0xff) << 16;
case 2 : mix.a += (bytes[offset + 1] & 0xff) << 8;
case 1 : mix.a += (bytes[offset + 0] & 0xff);
// case 0 : nothing left to add
}
return goog.crypt.hash32.mix32_(mix);
};
/**
* Performs an inplace mix of an object with the integer properties (a, b, c)
* and returns the final value of c.
* @param {Object} mix Object with properties, a, b, and c.
* @return {number} The end c-value for the mixing.
* @private
*/
goog.crypt.hash32.mix32_ = function(mix) {
var a = mix.a, b = mix.b, c = mix.c;
a -= b; a -= c; a ^= c >>> 13;
b -= c; b -= a; b ^= a << 8;
c -= a; c -= b; c ^= b >>> 13;
a -= b; a -= c; a ^= c >>> 12;
b -= c; b -= a; b ^= a << 16;
c -= a; c -= b; c ^= b >>> 5;
a -= b; a -= c; a ^= c >>> 3;
b -= c; b -= a; b ^= a << 10;
c -= a; c -= b; c ^= b >>> 15;
mix.a = a; mix.b = b; mix.c = c;
return c;
};
/**
* Returns the word at a given offset. Treating an array of bytes a word at a
* time is far more efficient than byte-by-byte.
* @param {Array.<number>} bytes Array of bytes.
* @param {number} offset Offset in the byte array.
* @return {number} Integer value for the word.
* @private
*/
goog.crypt.hash32.wordAt_ = function(bytes, offset) {
var a = goog.crypt.hash32.toSigned_(bytes[offset + 0]);
var b = goog.crypt.hash32.toSigned_(bytes[offset + 1]);
var c = goog.crypt.hash32.toSigned_(bytes[offset + 2]);
var d = goog.crypt.hash32.toSigned_(bytes[offset + 3]);
return a + (b << 8) + (c << 16) + (d << 24);
};
/**
* Converts an unsigned "byte" to signed, that is, convert a value in the range
* (0, 2^8-1) to (-2^7, 2^7-1) in order to be compatible with Java's byte type.
* @param {number} n Unsigned "byte" value.
* @return {number} Signed "byte" value.
* @private
*/
goog.crypt.hash32.toSigned_ = function(n) {
return n > 127 ? n - 256 : n;
};

View File

@@ -0,0 +1,243 @@
// Copyright 2011 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 Unit tests for the abstract cryptographic hash interface.
*
*/
goog.provide('goog.crypt.hashTester');
goog.require('goog.array');
goog.require('goog.crypt');
goog.require('goog.testing.PerformanceTable');
goog.require('goog.testing.PseudoRandom');
goog.require('goog.testing.asserts');
goog.setTestOnly('hashTester');
/**
* Runs basic tests.
*
* @param {!goog.crypt.Hash} hash A hash instance.
*/
goog.crypt.hashTester.runBasicTests = function(hash) {
// Compute first hash.
hash.update([97, 158]);
var golden1 = hash.digest();
// Compute second hash.
hash.reset();
hash.update('aB');
var golden2 = hash.digest();
assertTrue('Two different inputs resulted in a hash collision',
!!goog.testing.asserts.findDifferences(golden1, golden2));
// Empty hash.
hash.reset();
var empty = hash.digest();
assertTrue('Empty hash collided with a non-trivial one',
!!goog.testing.asserts.findDifferences(golden1, empty) &&
!!goog.testing.asserts.findDifferences(golden2, empty));
// Zero-length array update.
hash.reset();
hash.update([]);
assertArrayEquals('Updating with an empty array did not give an empty hash',
empty, hash.digest());
// Zero-length string update.
hash.reset();
hash.update('');
assertArrayEquals('Updating with an empty string did not give an empty hash',
empty, hash.digest());
// Recompute the first hash.
hash.reset();
hash.update([97, 158]);
assertArrayEquals('The reset did not produce the initial state',
golden1, hash.digest());
// Check for a trivial collision.
hash.reset();
hash.update([158, 97]);
assertTrue('Swapping bytes resulted in a hash collision',
!!goog.testing.asserts.findDifferences(golden1, hash.digest()));
// Compare array and string input.
hash.reset();
hash.update([97, 66]);
assertArrayEquals('String and array inputs should give the same result',
golden2, hash.digest());
// Compute in parts.
hash.reset();
hash.update('a');
hash.update([158]);
assertArrayEquals('Partial updates resulted in a different hash',
golden1, hash.digest());
// Test update with specified length.
hash.reset();
hash.update('aB', 0);
hash.update([97, 158, 32], 2);
assertArrayEquals('Updating with an explicit buffer length did not work',
golden1, hash.digest());
};
/**
* Runs block tests.
*
* @param {!goog.crypt.Hash} hash A hash instance.
* @param {number} blockBytes Size of the hash block.
*/
goog.crypt.hashTester.runBlockTests = function(hash, blockBytes) {
// Compute a message which is 1 byte shorter than hash block size.
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var message = '';
for (var i = 0; i < blockBytes - 1; i++) {
message += chars.charAt(i % chars.length);
}
// Compute golden hash for 1 block + 2 bytes.
hash.update(message + '123');
var golden1 = hash.digest();
// Compute golden hash for 2 blocks + 1 byte.
hash.reset();
hash.update(message + message + '123');
var golden2 = hash.digest();
// Almost fill a block, then overflow.
hash.reset();
hash.update(message);
hash.update('123');
assertArrayEquals(golden1, hash.digest());
// Fill a block.
hash.reset();
hash.update(message + '1');
hash.update('23');
assertArrayEquals(golden1, hash.digest());
// Overflow a block.
hash.reset();
hash.update(message + '12');
hash.update('3');
assertArrayEquals(golden1, hash.digest());
// Test single overflow with an array.
hash.reset();
hash.update(goog.crypt.stringToByteArray(message + '123'));
assertArrayEquals(golden1, hash.digest());
// Almost fill a block, then overflow this and the next block.
hash.reset();
hash.update(message);
hash.update(message + '123');
assertArrayEquals(golden2, hash.digest());
// Fill two blocks.
hash.reset();
hash.update(message + message + '12');
hash.update('3');
assertArrayEquals(golden2, hash.digest());
// Test double overflow with an array.
hash.reset();
hash.update(goog.crypt.stringToByteArray(message));
hash.update(goog.crypt.stringToByteArray(message + '123'));
assertArrayEquals(golden2, hash.digest());
};
/**
* Runs performance tests.
*
* @param {function():!goog.crypt.Hash} hashFactory A hash factory.
* @param {string} hashName Name of the hashing function.
*/
goog.crypt.hashTester.runPerfTests = function(hashFactory, hashName) {
var body = goog.dom.getDocument().body;
var perfTable = goog.dom.createElement('div');
goog.dom.appendChild(body, perfTable);
var table = new goog.testing.PerformanceTable(perfTable);
function runPerfTest(byteLength, updateCount) {
var label = (hashName + ': ' + updateCount + ' update(s) of ' + byteLength +
' bytes');
function run(data, dataType) {
table.run(function() {
var hash = hashFactory();
for (var i = 0; i < updateCount; i++) {
hash.update(data, byteLength);
}
var digest = hash.digest();
}, label + ' (' + dataType + ')');
}
var byteArray = goog.crypt.hashTester.createRandomByteArray_(byteLength);
var byteString = goog.crypt.hashTester.createByteString_(byteArray);
run(byteArray, 'byte array');
run(byteString, 'byte string');
}
var MESSAGE_LENGTH_LONG = 10000000; // 10 Mbytes
var MESSAGE_LENGTH_SHORT = 10; // 10 bytes
var MESSAGE_COUNT_SHORT = MESSAGE_LENGTH_LONG / MESSAGE_LENGTH_SHORT;
runPerfTest(MESSAGE_LENGTH_LONG, 1);
runPerfTest(MESSAGE_LENGTH_SHORT, MESSAGE_COUNT_SHORT);
};
/**
* Creates and returns a random byte array.
*
* @param {number} length Length of the byte array.
* @return {!Array.<number>} An array of bytes.
* @private
*/
goog.crypt.hashTester.createRandomByteArray_ = function(length) {
var random = new goog.testing.PseudoRandom(0);
var bytes = [];
for (var i = 0; i < length; ++i) {
// Generates an integer from 0 to 255.
var b = Math.floor(random.random() * 0x100);
bytes.push(b);
}
return bytes;
};
/**
* Creates a string from an array of bytes.
*
* @param {!Array.<number>} bytes An array of bytes.
* @return {string} The string encoded by the bytes.
* @private
*/
goog.crypt.hashTester.createByteString_ = function(bytes) {
var str = '';
goog.array.forEach(bytes, function(b) {
str += String.fromCharCode(b);
});
return str;
};

View File

@@ -0,0 +1,162 @@
// Copyright 2011 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 Implementation of HMAC in JavaScript.
*
* Usage:
* var hmac = new goog.crypt.Hmac(new goog.crypt.sha1(), key, 64);
* var digest = hmac.getHmac(bytes);
*
*/
goog.provide('goog.crypt.Hmac');
goog.require('goog.asserts');
goog.require('goog.crypt.Hash');
/**
* @constructor
* @param {!goog.crypt.Hash} hasher An object to serve as a hash function.
* @param {Array.<number>} key The secret key to use to calculate the hmac.
* Should be an array of not more than {@code blockSize} integers in
{0, 255}.
* @param {number=} opt_blockSize Optional. The block size {@code hasher} uses.
* If not specified, 16.
* @extends {goog.crypt.Hash}
*/
goog.crypt.Hmac = function(hasher, key, opt_blockSize) {
goog.base(this);
/**
* The underlying hasher to calculate hash.
*
* @type {!goog.crypt.Hash}
* @private
*/
this.hasher_ = hasher;
/**
* The block size.
*
* @type {number}
* @private
*/
this.blockSize_ = opt_blockSize || 16;
/**
* The outer padding array of hmac
*
* @type {!Array.<number>}
* @private
*/
this.keyO_ = new Array(this.blockSize_);
/**
* The inner padding array of hmac
*
* @type {!Array.<number>}
* @private
*/
this.keyI_ = new Array(this.blockSize_);
this.initialize_(key);
};
goog.inherits(goog.crypt.Hmac, goog.crypt.Hash);
/**
* Outer padding byte of HMAC algorith, per http://en.wikipedia.org/wiki/HMAC
*
* @type {number}
* @private
*/
goog.crypt.Hmac.OPAD_ = 0x5c;
/**
* Inner padding byte of HMAC algorith, per http://en.wikipedia.org/wiki/HMAC
*
* @type {number}
* @private
*/
goog.crypt.Hmac.IPAD_ = 0x36;
/**
* Initializes Hmac by precalculating the inner and outer paddings.
*
* @param {Array.<number>} key The secret key to use to calculate the hmac.
* Should be an array of not more than {@code blockSize} integers in
{0, 255}.
* @private
*/
goog.crypt.Hmac.prototype.initialize_ = function(key) {
if (key.length > this.blockSize_) {
this.hasher_.update(key);
key = this.hasher_.digest();
}
// Precalculate padded and xor'd keys.
var keyByte;
for (var i = 0; i < this.blockSize_; i++) {
if (i < key.length) {
keyByte = key[i];
} else {
keyByte = 0;
}
this.keyO_[i] = keyByte ^ goog.crypt.Hmac.OPAD_;
this.keyI_[i] = keyByte ^ goog.crypt.Hmac.IPAD_;
}
// Be ready for an immediate update.
this.hasher_.update(this.keyI_);
};
/** @override */
goog.crypt.Hmac.prototype.reset = function() {
this.hasher_.reset();
this.hasher_.update(this.keyI_);
};
/** @override */
goog.crypt.Hmac.prototype.update = function(bytes, opt_length) {
this.hasher_.update(bytes, opt_length);
};
/** @override */
goog.crypt.Hmac.prototype.digest = function() {
var temp = this.hasher_.digest();
this.hasher_.reset();
this.hasher_.update(this.keyO_);
this.hasher_.update(temp);
return this.hasher_.digest();
};
/**
* Calculates an HMAC for a given message.
*
* @param {Array.<number>} message An array of integers in {0, 255}.
* @return {!Array.<number>} the digest of the given message.
*/
goog.crypt.Hmac.prototype.getHmac = function(message) {
this.reset();
this.update(message);
return this.digest();
};

View File

@@ -0,0 +1,429 @@
// Copyright 2011 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 MD5 cryptographic hash.
* Implementation of http://tools.ietf.org/html/rfc1321 with common
* optimizations and tweaks (see http://en.wikipedia.org/wiki/MD5).
*
* Usage:
* var md5 = new goog.crypt.Md5();
* md5.update(bytes);
* var hash = md5.digest();
*
* Performance:
* Chrome 23 ~680 Mbit/s
* Chrome 13 (in a VM) ~250 Mbit/s
* Firefox 6.0 (in a VM) ~100 Mbit/s
* IE9 (in a VM) ~27 Mbit/s
* Firefox 3.6 ~15 Mbit/s
* IE8 (in a VM) ~13 Mbit/s
*
*/
goog.provide('goog.crypt.Md5');
goog.require('goog.crypt.Hash');
/**
* MD5 cryptographic hash constructor.
* @constructor
* @extends {goog.crypt.Hash}
*/
goog.crypt.Md5 = function() {
goog.base(this);
/**
* Holds the current values of accumulated A-D variables (MD buffer).
* @type {Array.<number>}
* @private
*/
this.chain_ = new Array(4);
/**
* A buffer holding the data until the whole block can be processed.
* @type {Array.<number>}
* @private
*/
this.block_ = new Array(64);
/**
* The length of yet-unprocessed data as collected in the block.
* @type {number}
* @private
*/
this.blockLength_ = 0;
/**
* The total length of the message so far.
* @type {number}
* @private
*/
this.totalLength_ = 0;
this.reset();
};
goog.inherits(goog.crypt.Md5, goog.crypt.Hash);
/**
* Integer rotation constants used by the abbreviated implementation.
* They are hardcoded in the unrolled implementation, so it is left
* here commented out.
* @type {Array.<number>}
* @private
*
goog.crypt.Md5.S_ = [
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
];
*/
/**
* Sine function constants used by the abbreviated implementation.
* They are hardcoded in the unrolled implementation, so it is left
* here commented out.
* @type {Array.<number>}
* @private
*
goog.crypt.Md5.T_ = [
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
];
*/
/** @override */
goog.crypt.Md5.prototype.reset = function() {
this.chain_[0] = 0x67452301;
this.chain_[1] = 0xefcdab89;
this.chain_[2] = 0x98badcfe;
this.chain_[3] = 0x10325476;
this.blockLength_ = 0;
this.totalLength_ = 0;
};
/**
* Internal compress helper function. It takes a block of data (64 bytes)
* and updates the accumulator.
* @param {Array.<number>|Uint8Array|string} buf The block to compress.
* @param {number=} opt_offset Offset of the block in the buffer.
* @private
*/
goog.crypt.Md5.prototype.compress_ = function(buf, opt_offset) {
if (!opt_offset) {
opt_offset = 0;
}
// We allocate the array every time, but it's cheap in practice.
var X = new Array(16);
// Get 16 little endian words. It is not worth unrolling this for Chrome 11.
if (goog.isString(buf)) {
for (var i = 0; i < 16; ++i) {
X[i] = (buf.charCodeAt(opt_offset++)) |
(buf.charCodeAt(opt_offset++) << 8) |
(buf.charCodeAt(opt_offset++) << 16) |
(buf.charCodeAt(opt_offset++) << 24);
}
} else {
for (var i = 0; i < 16; ++i) {
X[i] = (buf[opt_offset++]) |
(buf[opt_offset++] << 8) |
(buf[opt_offset++] << 16) |
(buf[opt_offset++] << 24);
}
}
var A = this.chain_[0];
var B = this.chain_[1];
var C = this.chain_[2];
var D = this.chain_[3];
var sum = 0;
/*
* This is an abbreviated implementation, it is left here commented out for
* reference purposes. See below for an unrolled version in use.
*
var f, n, tmp;
for (var i = 0; i < 64; ++i) {
if (i < 16) {
f = (D ^ (B & (C ^ D)));
n = i;
} else if (i < 32) {
f = (C ^ (D & (B ^ C)));
n = (5 * i + 1) % 16;
} else if (i < 48) {
f = (B ^ C ^ D);
n = (3 * i + 5) % 16;
} else {
f = (C ^ (B | (~D)));
n = (7 * i) % 16;
}
tmp = D;
D = C;
C = B;
sum = (A + f + goog.crypt.Md5.T_[i] + X[n]) & 0xffffffff;
B += ((sum << goog.crypt.Md5.S_[i]) & 0xffffffff) |
(sum >>> (32 - goog.crypt.Md5.S_[i]));
A = tmp;
}
*/
/*
* This is an unrolled MD5 implementation, which gives ~30% speedup compared
* to the abbreviated implementation above, as measured on Chrome 11. It is
* important to keep 32-bit croppings to minimum and inline the integer
* rotation.
*/
sum = (A + (D ^ (B & (C ^ D))) + X[0] + 0xd76aa478) & 0xffffffff;
A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25));
sum = (D + (C ^ (A & (B ^ C))) + X[1] + 0xe8c7b756) & 0xffffffff;
D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20));
sum = (C + (B ^ (D & (A ^ B))) + X[2] + 0x242070db) & 0xffffffff;
C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15));
sum = (B + (A ^ (C & (D ^ A))) + X[3] + 0xc1bdceee) & 0xffffffff;
B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10));
sum = (A + (D ^ (B & (C ^ D))) + X[4] + 0xf57c0faf) & 0xffffffff;
A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25));
sum = (D + (C ^ (A & (B ^ C))) + X[5] + 0x4787c62a) & 0xffffffff;
D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20));
sum = (C + (B ^ (D & (A ^ B))) + X[6] + 0xa8304613) & 0xffffffff;
C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15));
sum = (B + (A ^ (C & (D ^ A))) + X[7] + 0xfd469501) & 0xffffffff;
B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10));
sum = (A + (D ^ (B & (C ^ D))) + X[8] + 0x698098d8) & 0xffffffff;
A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25));
sum = (D + (C ^ (A & (B ^ C))) + X[9] + 0x8b44f7af) & 0xffffffff;
D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20));
sum = (C + (B ^ (D & (A ^ B))) + X[10] + 0xffff5bb1) & 0xffffffff;
C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15));
sum = (B + (A ^ (C & (D ^ A))) + X[11] + 0x895cd7be) & 0xffffffff;
B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10));
sum = (A + (D ^ (B & (C ^ D))) + X[12] + 0x6b901122) & 0xffffffff;
A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25));
sum = (D + (C ^ (A & (B ^ C))) + X[13] + 0xfd987193) & 0xffffffff;
D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20));
sum = (C + (B ^ (D & (A ^ B))) + X[14] + 0xa679438e) & 0xffffffff;
C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15));
sum = (B + (A ^ (C & (D ^ A))) + X[15] + 0x49b40821) & 0xffffffff;
B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10));
sum = (A + (C ^ (D & (B ^ C))) + X[1] + 0xf61e2562) & 0xffffffff;
A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27));
sum = (D + (B ^ (C & (A ^ B))) + X[6] + 0xc040b340) & 0xffffffff;
D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23));
sum = (C + (A ^ (B & (D ^ A))) + X[11] + 0x265e5a51) & 0xffffffff;
C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18));
sum = (B + (D ^ (A & (C ^ D))) + X[0] + 0xe9b6c7aa) & 0xffffffff;
B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12));
sum = (A + (C ^ (D & (B ^ C))) + X[5] + 0xd62f105d) & 0xffffffff;
A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27));
sum = (D + (B ^ (C & (A ^ B))) + X[10] + 0x02441453) & 0xffffffff;
D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23));
sum = (C + (A ^ (B & (D ^ A))) + X[15] + 0xd8a1e681) & 0xffffffff;
C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18));
sum = (B + (D ^ (A & (C ^ D))) + X[4] + 0xe7d3fbc8) & 0xffffffff;
B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12));
sum = (A + (C ^ (D & (B ^ C))) + X[9] + 0x21e1cde6) & 0xffffffff;
A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27));
sum = (D + (B ^ (C & (A ^ B))) + X[14] + 0xc33707d6) & 0xffffffff;
D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23));
sum = (C + (A ^ (B & (D ^ A))) + X[3] + 0xf4d50d87) & 0xffffffff;
C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18));
sum = (B + (D ^ (A & (C ^ D))) + X[8] + 0x455a14ed) & 0xffffffff;
B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12));
sum = (A + (C ^ (D & (B ^ C))) + X[13] + 0xa9e3e905) & 0xffffffff;
A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27));
sum = (D + (B ^ (C & (A ^ B))) + X[2] + 0xfcefa3f8) & 0xffffffff;
D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23));
sum = (C + (A ^ (B & (D ^ A))) + X[7] + 0x676f02d9) & 0xffffffff;
C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18));
sum = (B + (D ^ (A & (C ^ D))) + X[12] + 0x8d2a4c8a) & 0xffffffff;
B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12));
sum = (A + (B ^ C ^ D) + X[5] + 0xfffa3942) & 0xffffffff;
A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28));
sum = (D + (A ^ B ^ C) + X[8] + 0x8771f681) & 0xffffffff;
D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21));
sum = (C + (D ^ A ^ B) + X[11] + 0x6d9d6122) & 0xffffffff;
C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16));
sum = (B + (C ^ D ^ A) + X[14] + 0xfde5380c) & 0xffffffff;
B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9));
sum = (A + (B ^ C ^ D) + X[1] + 0xa4beea44) & 0xffffffff;
A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28));
sum = (D + (A ^ B ^ C) + X[4] + 0x4bdecfa9) & 0xffffffff;
D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21));
sum = (C + (D ^ A ^ B) + X[7] + 0xf6bb4b60) & 0xffffffff;
C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16));
sum = (B + (C ^ D ^ A) + X[10] + 0xbebfbc70) & 0xffffffff;
B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9));
sum = (A + (B ^ C ^ D) + X[13] + 0x289b7ec6) & 0xffffffff;
A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28));
sum = (D + (A ^ B ^ C) + X[0] + 0xeaa127fa) & 0xffffffff;
D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21));
sum = (C + (D ^ A ^ B) + X[3] + 0xd4ef3085) & 0xffffffff;
C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16));
sum = (B + (C ^ D ^ A) + X[6] + 0x04881d05) & 0xffffffff;
B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9));
sum = (A + (B ^ C ^ D) + X[9] + 0xd9d4d039) & 0xffffffff;
A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28));
sum = (D + (A ^ B ^ C) + X[12] + 0xe6db99e5) & 0xffffffff;
D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21));
sum = (C + (D ^ A ^ B) + X[15] + 0x1fa27cf8) & 0xffffffff;
C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16));
sum = (B + (C ^ D ^ A) + X[2] + 0xc4ac5665) & 0xffffffff;
B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9));
sum = (A + (C ^ (B | (~D))) + X[0] + 0xf4292244) & 0xffffffff;
A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26));
sum = (D + (B ^ (A | (~C))) + X[7] + 0x432aff97) & 0xffffffff;
D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22));
sum = (C + (A ^ (D | (~B))) + X[14] + 0xab9423a7) & 0xffffffff;
C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17));
sum = (B + (D ^ (C | (~A))) + X[5] + 0xfc93a039) & 0xffffffff;
B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11));
sum = (A + (C ^ (B | (~D))) + X[12] + 0x655b59c3) & 0xffffffff;
A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26));
sum = (D + (B ^ (A | (~C))) + X[3] + 0x8f0ccc92) & 0xffffffff;
D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22));
sum = (C + (A ^ (D | (~B))) + X[10] + 0xffeff47d) & 0xffffffff;
C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17));
sum = (B + (D ^ (C | (~A))) + X[1] + 0x85845dd1) & 0xffffffff;
B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11));
sum = (A + (C ^ (B | (~D))) + X[8] + 0x6fa87e4f) & 0xffffffff;
A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26));
sum = (D + (B ^ (A | (~C))) + X[15] + 0xfe2ce6e0) & 0xffffffff;
D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22));
sum = (C + (A ^ (D | (~B))) + X[6] + 0xa3014314) & 0xffffffff;
C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17));
sum = (B + (D ^ (C | (~A))) + X[13] + 0x4e0811a1) & 0xffffffff;
B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11));
sum = (A + (C ^ (B | (~D))) + X[4] + 0xf7537e82) & 0xffffffff;
A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26));
sum = (D + (B ^ (A | (~C))) + X[11] + 0xbd3af235) & 0xffffffff;
D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22));
sum = (C + (A ^ (D | (~B))) + X[2] + 0x2ad7d2bb) & 0xffffffff;
C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17));
sum = (B + (D ^ (C | (~A))) + X[9] + 0xeb86d391) & 0xffffffff;
B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11));
this.chain_[0] = (this.chain_[0] + A) & 0xffffffff;
this.chain_[1] = (this.chain_[1] + B) & 0xffffffff;
this.chain_[2] = (this.chain_[2] + C) & 0xffffffff;
this.chain_[3] = (this.chain_[3] + D) & 0xffffffff;
};
/** @override */
goog.crypt.Md5.prototype.update = function(bytes, opt_length) {
if (!goog.isDef(opt_length)) {
opt_length = bytes.length;
}
var lengthMinusBlock = opt_length - 64;
// Copy some object properties to local variables in order to save on access
// time from inside the loop (~10% speedup was observed on Chrome 11).
var block = this.block_;
var blockLength = this.blockLength_;
var i = 0;
// The outer while loop should execute at most twice.
while (i < opt_length) {
// When we have no data in the block to top up, we can directly process the
// input buffer (assuming it contains sufficient data). This gives ~30%
// speedup on Chrome 14 and ~70% speedup on Firefox 6.0, but requires that
// the data is provided in large chunks (or in multiples of 64 bytes).
if (blockLength == 0) {
while (i <= lengthMinusBlock) {
this.compress_(bytes, i);
i += 64;
}
}
if (goog.isString(bytes)) {
while (i < opt_length) {
block[blockLength++] = bytes.charCodeAt(i++);
if (blockLength == 64) {
this.compress_(block);
blockLength = 0;
// Jump to the outer loop so we use the full-block optimization.
break;
}
}
} else {
while (i < opt_length) {
block[blockLength++] = bytes[i++];
if (blockLength == 64) {
this.compress_(block);
blockLength = 0;
// Jump to the outer loop so we use the full-block optimization.
break;
}
}
}
}
this.blockLength_ = blockLength;
this.totalLength_ += opt_length;
};
/** @override */
goog.crypt.Md5.prototype.digest = function() {
// This must accommodate at least 1 padding byte (0x80), 8 bytes of
// total bitlength, and must end at a 64-byte boundary.
var pad = new Array((this.blockLength_ < 56 ? 64 : 128) - this.blockLength_);
// Add padding: 0x80 0x00*
pad[0] = 0x80;
for (var i = 1; i < pad.length - 8; ++i) {
pad[i] = 0;
}
// Add the total number of bits, little endian 64-bit integer.
var totalBits = this.totalLength_ * 8;
for (var i = pad.length - 8; i < pad.length; ++i) {
pad[i] = totalBits & 0xff;
totalBits /= 0x100; // Don't use bit-shifting here!
}
this.update(pad);
var digest = new Array(16);
var n = 0;
for (var i = 0; i < 4; ++i) {
for (var j = 0; j < 32; j += 8) {
digest[n++] = (this.chain_[i] >>> j) & 0xff;
}
}
return digest;
};

View File

@@ -0,0 +1,127 @@
// Copyright 2012 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 Implementation of PBKDF2 in JavaScript.
* @see http://en.wikipedia.org/wiki/PBKDF2
*
* Currently we only support HMAC-SHA1 as the underlying hash function. To add a
* new hash function, add a static method similar to deriveKeyFromPasswordSha1()
* and implement the specific computeBlockCallback() using the hash function.
*
* Usage:
* var key = pbkdf2.deriveKeySha1(
* stringToByteArray('password'), stringToByteArray('salt'), 1000, 128);
*
*/
goog.provide('goog.crypt.pbkdf2');
goog.require('goog.asserts');
goog.require('goog.crypt');
goog.require('goog.crypt.Hmac');
goog.require('goog.crypt.Sha1');
/**
* Derives key from password using PBKDF2-SHA1
* @param {!Array.<number>} password Byte array representation of the password
* from which the key is derived.
* @param {!Array.<number>} initialSalt Byte array representation of the salt.
* @param {number} iterations Number of interations when computing the key.
* @param {number} keyLength Length of the output key in bits.
* Must be multiple of 8.
* @return {!Array.<number>} Byte array representation of the output key.
*/
goog.crypt.pbkdf2.deriveKeySha1 = function(
password, initialSalt, iterations, keyLength) {
// Length of the HMAC-SHA1 output in bits.
var HASH_LENGTH = 160;
/**
* Compute each block of the key using HMAC-SHA1.
* @param {!Array.<number>} index Byte array representation of the index of
* the block to be computed.
* @return {!Array.<number>} Byte array representation of the output block.
*/
var computeBlock = function(index) {
// Initialize the result to be array of 0 such that its xor with the first
// block would be the first block.
var result = goog.array.repeat(0, HASH_LENGTH / 8);
// Initialize the salt of the first iteration to initialSalt || i.
var salt = initialSalt.concat(index);
var hmac = new goog.crypt.Hmac(new goog.crypt.Sha1(), password, 64);
// Compute and XOR each iteration.
for (var i = 0; i < iterations; i++) {
// The salt of the next iteration is the result of the current iteration.
salt = hmac.getHmac(salt);
result = goog.crypt.xorByteArray(result, salt);
}
return result;
};
return goog.crypt.pbkdf2.deriveKeyFromPassword_(
computeBlock, HASH_LENGTH, keyLength);
};
/**
* Compute each block of the key using PBKDF2.
* @param {Function} computeBlock Function to compute each block of the output
* key.
* @param {number} hashLength Length of each block in bits. This is determined
* by the specific hash function used. Must be multiple of 8.
* @param {number} keyLength Length of the output key in bits.
* Must be multiple of 8.
* @return {!Array.<number>} Byte array representation of the output key.
* @private
*/
goog.crypt.pbkdf2.deriveKeyFromPassword_ =
function(computeBlock, hashLength, keyLength) {
goog.asserts.assert(keyLength % 8 == 0, 'invalid output key length');
// Compute and concactate each block of the output key.
var numBlocks = Math.ceil(keyLength / hashLength);
goog.asserts.assert(numBlocks >= 1, 'invalid number of blocks');
var result = [];
for (var i = 1; i <= numBlocks; i++) {
var indexBytes = goog.crypt.pbkdf2.integerToByteArray_(i);
result = result.concat(computeBlock(indexBytes));
}
// Trim the last block if needed.
var lastBlockSize = keyLength % hashLength;
if (lastBlockSize != 0) {
var desiredBytes = ((numBlocks - 1) * hashLength + lastBlockSize) / 8;
result.splice(desiredBytes, (hashLength - lastBlockSize) / 8);
}
return result;
};
/**
* Converts an integer number to a 32-bit big endian byte array.
* @param {number} n Integer number to be converted.
* @return {!Array.<number>} Byte Array representation of the 32-bit big endian
* encoding of n.
* @private
*/
goog.crypt.pbkdf2.integerToByteArray_ = function(n) {
var result = new Array(4);
result[0] = n >> 24 & 0xFF;
result[1] = n >> 16 & 0xFF;
result[2] = n >> 8 & 0xFF;
result[3] = n & 0xFF;
return result;
};

View File

@@ -0,0 +1,275 @@
// Copyright 2005 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 SHA-1 cryptographic hash.
* Variable names follow the notation in FIPS PUB 180-3:
* http://csrc.nist.gov/publications/fips/fips180-3/fips180-3_final.pdf.
*
* Usage:
* var sha1 = new goog.crypt.sha1();
* sha1.update(bytes);
* var hash = sha1.digest();
*
* Performance:
* Chrome 23: ~400 Mbit/s
* Firefox 16: ~250 Mbit/s
*
*/
goog.provide('goog.crypt.Sha1');
goog.require('goog.crypt.Hash');
/**
* SHA-1 cryptographic hash constructor.
*
* The properties declared here are discussed in the above algorithm document.
* @constructor
* @extends {goog.crypt.Hash}
*/
goog.crypt.Sha1 = function() {
goog.base(this);
/**
* Holds the previous values of accumulated variables a-e in the compress_
* function.
* @type {Array.<number>}
* @private
*/
this.chain_ = [];
/**
* A buffer holding the partially computed hash result.
* @type {Array.<number>}
* @private
*/
this.buf_ = [];
/**
* An array of 80 bytes, each a part of the message to be hashed. Referred to
* as the message schedule in the docs.
* @type {Array.<number>}
* @private
*/
this.W_ = [];
/**
* Contains data needed to pad messages less than 64 bytes.
* @type {Array.<number>}
* @private
*/
this.pad_ = [];
this.pad_[0] = 128;
for (var i = 1; i < 64; ++i) {
this.pad_[i] = 0;
}
this.reset();
};
goog.inherits(goog.crypt.Sha1, goog.crypt.Hash);
/** @override */
goog.crypt.Sha1.prototype.reset = function() {
this.chain_[0] = 0x67452301;
this.chain_[1] = 0xefcdab89;
this.chain_[2] = 0x98badcfe;
this.chain_[3] = 0x10325476;
this.chain_[4] = 0xc3d2e1f0;
this.inbuf_ = 0;
this.total_ = 0;
};
/**
* Internal compress helper function.
* @param {Array.<number>|Uint8Array|string} buf Block to compress.
* @param {number=} opt_offset Offset of the block in the buffer.
* @private
*/
goog.crypt.Sha1.prototype.compress_ = function(buf, opt_offset) {
if (!opt_offset) {
opt_offset = 0;
}
var W = this.W_;
// get 16 big endian words
if (goog.isString(buf)) {
for (var i = 0; i < 16; i++) {
// TODO(user): [bug 8140122] Recent versions of Safari for Mac OS and iOS
// have a bug that turns the post-increment ++ operator into pre-increment
// during JIT compilation. We have code that depends heavily on SHA-1 for
// correctness and which is affected by this bug, so I've removed all uses
// of post-increment ++ in which the result value is used. We can revert
// this change once the Safari bug
// (https://bugs.webkit.org/show_bug.cgi?id=109036) has been fixed and
// most clients have been updated.
W[i] = (buf.charCodeAt(opt_offset) << 24) |
(buf.charCodeAt(opt_offset + 1) << 16) |
(buf.charCodeAt(opt_offset + 2) << 8) |
(buf.charCodeAt(opt_offset + 3));
opt_offset += 4;
}
} else {
for (var i = 0; i < 16; i++) {
W[i] = (buf[opt_offset] << 24) |
(buf[opt_offset + 1] << 16) |
(buf[opt_offset + 2] << 8) |
(buf[opt_offset + 3]);
opt_offset += 4;
}
}
// expand to 80 words
for (var i = 16; i < 80; i++) {
var t = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16];
W[i] = ((t << 1) | (t >>> 31)) & 0xffffffff;
}
var a = this.chain_[0];
var b = this.chain_[1];
var c = this.chain_[2];
var d = this.chain_[3];
var e = this.chain_[4];
var f, k;
// TODO(user): Try to unroll this loop to speed up the computation.
for (var i = 0; i < 80; i++) {
if (i < 40) {
if (i < 20) {
f = d ^ (b & (c ^ d));
k = 0x5a827999;
} else {
f = b ^ c ^ d;
k = 0x6ed9eba1;
}
} else {
if (i < 60) {
f = (b & c) | (d & (b | c));
k = 0x8f1bbcdc;
} else {
f = b ^ c ^ d;
k = 0xca62c1d6;
}
}
var t = (((a << 5) | (a >>> 27)) + f + e + k + W[i]) & 0xffffffff;
e = d;
d = c;
c = ((b << 30) | (b >>> 2)) & 0xffffffff;
b = a;
a = t;
}
this.chain_[0] = (this.chain_[0] + a) & 0xffffffff;
this.chain_[1] = (this.chain_[1] + b) & 0xffffffff;
this.chain_[2] = (this.chain_[2] + c) & 0xffffffff;
this.chain_[3] = (this.chain_[3] + d) & 0xffffffff;
this.chain_[4] = (this.chain_[4] + e) & 0xffffffff;
};
/** @override */
goog.crypt.Sha1.prototype.update = function(bytes, opt_length) {
if (!goog.isDef(opt_length)) {
opt_length = bytes.length;
}
var lengthMinusBlock = opt_length - 64;
var n = 0;
// Using local instead of member variables gives ~5% speedup on Firefox 16.
var buf = this.buf_;
var inbuf = this.inbuf_;
// The outer while loop should execute at most twice.
while (n < opt_length) {
// When we have no data in the block to top up, we can directly process the
// input buffer (assuming it contains sufficient data). This gives ~25%
// speedup on Chrome 23 and ~15% speedup on Firefox 16, but requires that
// the data is provided in large chunks (or in multiples of 64 bytes).
if (inbuf == 0) {
while (n <= lengthMinusBlock) {
this.compress_(bytes, n);
n += 64;
}
}
if (goog.isString(bytes)) {
while (n < opt_length) {
buf[inbuf] = bytes.charCodeAt(n);
++inbuf;
++n;
if (inbuf == 64) {
this.compress_(buf);
inbuf = 0;
// Jump to the outer loop so we use the full-block optimization.
break;
}
}
} else {
while (n < opt_length) {
buf[inbuf] = bytes[n];
++inbuf;
++n;
if (inbuf == 64) {
this.compress_(buf);
inbuf = 0;
// Jump to the outer loop so we use the full-block optimization.
break;
}
}
}
}
this.inbuf_ = inbuf;
this.total_ += opt_length;
};
/** @override */
goog.crypt.Sha1.prototype.digest = function() {
var digest = [];
var totalBits = this.total_ * 8;
// Add pad 0x80 0x00*.
if (this.inbuf_ < 56) {
this.update(this.pad_, 56 - this.inbuf_);
} else {
this.update(this.pad_, 64 - (this.inbuf_ - 56));
}
// Add # bits.
for (var i = 63; i >= 56; i--) {
this.buf_[i] = totalBits & 255;
totalBits /= 256; // Don't use bit-shifting here!
}
this.compress_(this.buf_);
var n = 0;
for (var i = 0; i < 5; i++) {
for (var j = 24; j >= 0; j -= 8) {
digest[n] = (this.chain_[i] >> j) & 255;
++n;
}
}
return digest;
};

View File

@@ -0,0 +1,275 @@
// Copyright 2012 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 Base class for SHA-2 cryptographic hash.
*
* Variable names follow the notation in FIPS PUB 180-3:
* http://csrc.nist.gov/publications/fips/fips180-3/fips180-3_final.pdf.
*
* To implement specific SHA-2 such as SHA-256, create a sub-class with
* overridded reset(). See sha256.js for an example.
*
* TODO(user): SHA-512/384 are not currently implemented. Could be added
* if needed.
*
* Some code similar to SHA1 are borrowed from sha1.js written by mschilder@.
*
*/
goog.provide('goog.crypt.Sha2');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.crypt.Hash');
/**
* SHA-2 cryptographic hash constructor.
* This constructor should not be used directly to create the object. Rather,
* one should use the constructor of the sub-classes.
* @constructor
* @extends {goog.crypt.Hash}
*/
goog.crypt.Sha2 = function() {
goog.base(this);
/**
* A chunk holding the currently processed message bytes. Once the chunk has
* 64 bytes, we feed it into computeChunk_ function and reset this.chunk_.
* Sub-class needs to reset it when overriding reset().
* @type {!Array.<number>}
* @protected
*/
this.chunk = [];
/**
* Current number of bytes in this.chunk_.
* Sub-class needs to reset it when overriding reset().
* @type {number}
* @protected
*/
this.inChunk = 0;
/**
* Total number of bytes in currently processed message.
* Sub-class needs to reset it when overriding reset().
* @type {number}
* @protected
*/
this.total = 0;
/**
* Contains data needed to pad messages less than 64 bytes.
* @type {!Array.<number>}
* @private
*/
this.pad_ = goog.array.repeat(0, 64);
this.pad_[0] = 128;
/**
* Holds the previous values of accumulated hash a-h in the computeChunk_
* function.
* It is a subclass-dependent value. Sub-class needs to explicitly set it
* when overriding reset().
* @type {!Array.<number>}
* @protected
*/
this.hash = [];
/**
* The number of output hash blocks (each block is 4 bytes long).
* It is a subclass-dependent value. Sub-class needs to explicitly set it
* when overriding reset().
* @type {number}
* @protected
*/
this.numHashBlocks = 0;
this.reset();
};
goog.inherits(goog.crypt.Sha2, goog.crypt.Hash);
/** @override */
goog.crypt.Sha2.prototype.reset = goog.abstractMethod;
/**
* Helper function to compute the hashes for a given 512-bit message chunk.
* @param {!Array.<number>} chunk A 512-bit message chunk to be processed.
* @private
*/
goog.crypt.Sha2.prototype.computeChunk_ = function(chunk) {
goog.asserts.assert(chunk.length == 64);
// Divide the chunk into 16 32-bit-words.
var w = [];
var index = 0;
var offset = 0;
while (offset < chunk.length) {
w[index++] = (chunk[offset] << 24) |
(chunk[offset + 1] << 16) |
(chunk[offset + 2] << 8) |
(chunk[offset + 3]);
offset = index * 4;
}
// Expand to 64 32-bit-words
for (var i = 16; i < 64; i++) {
var s0 = ((w[i - 15] >>> 7) | (w[i - 15] << 25)) ^
((w[i - 15] >>> 18) | (w[i - 15] << 14)) ^
(w[i - 15] >>> 3);
var s1 = ((w[i - 2] >>> 17) | (w[i - 2] << 15)) ^
((w[i - 2] >>> 19) | (w[i - 2] << 13)) ^
(w[i - 2] >>> 10);
w[i] = (w[i - 16] + s0 + w[i - 7] + s1) & 0xffffffff;
}
var a = this.hash[0];
var b = this.hash[1];
var c = this.hash[2];
var d = this.hash[3];
var e = this.hash[4];
var f = this.hash[5];
var g = this.hash[6];
var h = this.hash[7];
for (var i = 0; i < 64; i++) {
var S0 = ((a >>> 2) | (a << 30)) ^
((a >>> 13) | (a << 19)) ^
((a >>> 22) | (a << 10));
var maj = ((a & b) ^ (a & c) ^ (b & c));
var t2 = (S0 + maj) & 0xffffffff;
var S1 = ((e >>> 6) | (e << 26)) ^
((e >>> 11) | (e << 21)) ^
((e >>> 25) | (e << 7));
var ch = ((e & f) ^ ((~ e) & g));
var t1 = (h + S1 + ch + this.K_[i] + w[i]) & 0xffffffff;
h = g;
g = f;
f = e;
e = (d + t1) & 0xffffffff;
d = c;
c = b;
b = a;
a = (t1 + t2) & 0xffffffff;
}
this.hash[0] = (this.hash[0] + a) & 0xffffffff;
this.hash[1] = (this.hash[1] + b) & 0xffffffff;
this.hash[2] = (this.hash[2] + c) & 0xffffffff;
this.hash[3] = (this.hash[3] + d) & 0xffffffff;
this.hash[4] = (this.hash[4] + e) & 0xffffffff;
this.hash[5] = (this.hash[5] + f) & 0xffffffff;
this.hash[6] = (this.hash[6] + g) & 0xffffffff;
this.hash[7] = (this.hash[7] + h) & 0xffffffff;
};
/** @override */
goog.crypt.Sha2.prototype.update = function(message, opt_length) {
if (!goog.isDef(opt_length)) {
opt_length = message.length;
}
// Process the message from left to right up to |opt_length| bytes.
// When we get a 512-bit chunk, compute the hash of it and reset
// this.chunk_. The message might not be multiple of 512 bits so we
// might end up with a chunk that is less than 512 bits. We store
// such partial chunk in this.chunk_ and it will be filled up later
// in digest().
var n = 0;
var inChunk = this.inChunk;
// The input message could be either byte array of string.
if (goog.isString(message)) {
while (n < opt_length) {
this.chunk[inChunk++] = message.charCodeAt(n++);
if (inChunk == 64) {
this.computeChunk_(this.chunk);
inChunk = 0;
}
}
} else {
while (n < opt_length) {
this.chunk[inChunk++] = message[n++];
if (inChunk == 64) {
this.computeChunk_(this.chunk);
inChunk = 0;
}
}
}
// Record the current bytes in chunk to support partial update.
this.inChunk = inChunk;
// Record total message bytes we have processed so far.
this.total += opt_length;
};
/** @override */
goog.crypt.Sha2.prototype.digest = function() {
var digest = [];
var totalBits = this.total * 8;
// Append pad 0x80 0x00*.
if (this.inChunk < 56) {
this.update(this.pad_, 56 - this.inChunk);
} else {
this.update(this.pad_, 64 - (this.inChunk - 56));
}
// Append # bits in the 64-bit big-endian format.
for (var i = 63; i >= 56; i--) {
this.chunk[i] = totalBits & 255;
totalBits /= 256; // Don't use bit-shifting here!
}
this.computeChunk_(this.chunk);
// Finally, output the result digest.
var n = 0;
for (var i = 0; i < this.numHashBlocks; i++) {
for (var j = 24; j >= 0; j -= 8) {
digest[n++] = ((this.hash[i] >> j) & 255);
}
}
return digest;
};
/**
* Constants used in SHA-2.
* @const
* @private
*/
goog.crypt.Sha2.prototype.K_ = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2];

View File

@@ -0,0 +1,53 @@
// Copyright 2012 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 SHA-224 cryptographic hash.
*
* Usage:
* var sha224 = new goog.crypt.Sha224();
* sha224.update(bytes);
* var hash = sha224.digest();
*
*/
goog.provide('goog.crypt.Sha224');
goog.require('goog.crypt.Sha2');
/**
* SHA-224 cryptographic hash constructor.
*
* @constructor
* @extends {goog.crypt.Sha2}
*/
goog.crypt.Sha224 = function() {
goog.base(this);
};
goog.inherits(goog.crypt.Sha224, goog.crypt.Sha2);
/** @override */
goog.crypt.Sha224.prototype.reset = function() {
this.chunk = [];
this.inChunk = 0;
this.total = 0;
this.hash = [
0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939,
0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4];
this.numHashBlocks = 7;
};

View File

@@ -0,0 +1,53 @@
// Copyright 2012 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 SHA-256 cryptographic hash.
*
* Usage:
* var sha256 = new goog.crypt.Sha256();
* sha256.update(bytes);
* var hash = sha256.digest();
*
*/
goog.provide('goog.crypt.Sha256');
goog.require('goog.crypt.Sha2');
/**
* SHA-256 cryptographic hash constructor.
*
* @constructor
* @extends {goog.crypt.Sha2}
*/
goog.crypt.Sha256 = function() {
goog.base(this);
};
goog.inherits(goog.crypt.Sha256, goog.crypt.Sha2);
/** @override */
goog.crypt.Sha256.prototype.reset = function() {
this.chunk = [];
this.inChunk = 0;
this.total = 0;
this.hash = [
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
this.numHashBlocks = 8;
};

View File

@@ -0,0 +1,449 @@
// Copyright 2008 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 CSS Object Model helper functions.
* References:
* - W3C: http://dev.w3.org/csswg/cssom/
* - MSDN: http://msdn.microsoft.com/en-us/library/ms531209(VS.85).aspx.
* @supported in FF3, IE6, IE7, Safari 3.1.2, Chrome
* TODO(user): Fix in Opera.
* TODO(user): Consider hacking page, media, etc.. to work.
* This would be pretty challenging. IE returns the text for any rule
* regardless of whether or not the media is correct or not. Firefox at
* least supports CSSRule.type to figure out if it's a media type and then
* we could do something interesting, but IE offers no way for us to tell.
*/
goog.provide('goog.cssom');
goog.provide('goog.cssom.CssRuleType');
goog.require('goog.array');
goog.require('goog.dom');
/**
* Enumeration of {@code CSSRule} types.
* @enum {number}
*/
goog.cssom.CssRuleType = {
STYLE: 1,
IMPORT: 3,
MEDIA: 4,
FONT_FACE: 5,
PAGE: 6,
NAMESPACE: 7
};
/**
* Recursively gets all CSS as text, optionally starting from a given
* CSSStyleSheet.
* @param {(CSSStyleSheet|StyleSheetList)=} opt_styleSheet The CSSStyleSheet.
* @return {string} css text.
*/
goog.cssom.getAllCssText = function(opt_styleSheet) {
var styleSheet = opt_styleSheet || document.styleSheets;
return /** @type {string} */ (goog.cssom.getAllCss_(styleSheet, true));
};
/**
* Recursively gets all CSSStyleRules, optionally starting from a given
* CSSStyleSheet.
* Note that this excludes any CSSImportRules, CSSMediaRules, etc..
* @param {(CSSStyleSheet|StyleSheetList)=} opt_styleSheet The CSSStyleSheet.
* @return {Array.<CSSStyleRule>} A list of CSSStyleRules.
*/
goog.cssom.getAllCssStyleRules = function(opt_styleSheet) {
var styleSheet = opt_styleSheet || document.styleSheets;
return /** @type {Array.<CSSStyleRule>} */ (
goog.cssom.getAllCss_(styleSheet, false));
};
/**
* Returns the CSSRules from a styleSheet.
* Worth noting here is that IE and FF differ in terms of what they will return.
* Firefox will return styleSheet.cssRules, which includes ImportRules and
* anything which implements the CSSRules interface. IE returns simply a list of
* CSSRules.
* @param {CSSStyleSheet} styleSheet The CSSStyleSheet.
* @throws {Error} If we cannot access the rules on a stylesheet object - this
* can happen if a stylesheet object's rules are accessed before the rules
* have been downloaded and parsed and are "ready".
* @return {CSSRuleList} An array of CSSRules or null.
*/
goog.cssom.getCssRulesFromStyleSheet = function(styleSheet) {
var cssRuleList = null;
try {
// IE is .rules, W3c is cssRules.
cssRuleList = styleSheet.rules || styleSheet.cssRules;
} catch (e) {
// This can happen if we try to access the CSSOM before it's "ready".
if (e.code == 15) {
// Firefox throws an NS_ERROR_DOM_INVALID_ACCESS_ERR error if a stylesheet
// is read before it has been fully parsed. Let the caller know which
// stylesheet failed.
e.styleSheet = styleSheet;
throw e;
}
}
return cssRuleList;
};
/**
* Gets all CSSStyleSheet objects starting from some CSSStyleSheet. Note that we
* want to return the sheets in the order of the cascade, therefore if we
* encounter an import, we will splice that CSSStyleSheet object in front of
* the CSSStyleSheet that contains it in the returned array of CSSStyleSheets.
* @param {(CSSStyleSheet|StyleSheetList)=} opt_styleSheet A CSSStyleSheet.
* @param {boolean=} opt_includeDisabled If true, includes disabled stylesheets,
* defaults to false.
* @return {Array.<CSSStyleSheet>} A list of CSSStyleSheet objects.
*/
goog.cssom.getAllCssStyleSheets = function(opt_styleSheet,
opt_includeDisabled) {
var styleSheetsOutput = [];
var styleSheet = opt_styleSheet || document.styleSheets;
var includeDisabled = goog.isDef(opt_includeDisabled) ? opt_includeDisabled :
false;
// Imports need to go first.
if (styleSheet.imports && styleSheet.imports.length) {
for (var i = 0, n = styleSheet.imports.length; i < n; i++) {
goog.array.extend(styleSheetsOutput,
goog.cssom.getAllCssStyleSheets(styleSheet.imports[i]));
}
} else if (styleSheet.length) {
// In case we get a StyleSheetList object.
// http://dev.w3.org/csswg/cssom/#the-stylesheetlist
for (var i = 0, n = styleSheet.length; i < n; i++) {
goog.array.extend(styleSheetsOutput,
goog.cssom.getAllCssStyleSheets(styleSheet[i]));
}
} else {
// We need to walk through rules in browsers which implement .cssRules
// to see if there are styleSheets buried in there.
// If we have a CSSStyleSheet within CssRules.
var cssRuleList = goog.cssom.getCssRulesFromStyleSheet(
/** @type {CSSStyleSheet} */ (styleSheet));
if (cssRuleList && cssRuleList.length) {
// Chrome does not evaluate cssRuleList[i] to undefined when i >=n;
// so we use a (i < n) check instead of cssRuleList[i] in the loop below
// and in other places where we iterate over a rules list.
// See issue # 5917 in Chromium.
for (var i = 0, n = cssRuleList.length, cssRule; i < n; i++) {
cssRule = cssRuleList[i];
// There are more stylesheets to get on this object..
if (cssRule.styleSheet) {
goog.array.extend(styleSheetsOutput,
goog.cssom.getAllCssStyleSheets(cssRule.styleSheet));
}
}
}
}
// This is a CSSStyleSheet. (IE uses .rules, W3c and Opera cssRules.)
if ((styleSheet.type || styleSheet.rules || styleSheet.cssRules) &&
(!styleSheet.disabled || includeDisabled)) {
styleSheetsOutput.push(styleSheet);
}
return styleSheetsOutput;
};
/**
* Gets the cssText from a CSSRule object cross-browserly.
* @param {CSSRule} cssRule A CSSRule.
* @return {string} cssText The text for the rule, including the selector.
*/
goog.cssom.getCssTextFromCssRule = function(cssRule) {
var cssText = '';
if (cssRule.cssText) {
// W3C.
cssText = cssRule.cssText;
} else if (cssRule.style && cssRule.style.cssText && cssRule.selectorText) {
// IE: The spacing here is intended to make the result consistent with
// FF and Webkit.
// We also remove the special properties that we may have added in
// getAllCssStyleRules since IE includes those in the cssText.
var styleCssText = cssRule.style.cssText.
replace(/\s*-closure-parent-stylesheet:\s*\[object\];?\s*/gi, '').
replace(/\s*-closure-rule-index:\s*[\d]+;?\s*/gi, '');
var thisCssText = cssRule.selectorText + ' { ' + styleCssText + ' }';
cssText = thisCssText;
}
return cssText;
};
/**
* Get the index of the CSSRule in it's CSSStyleSheet.
* @param {CSSRule} cssRule A CSSRule.
* @param {CSSStyleSheet=} opt_parentStyleSheet A reference to the stylesheet
* object this cssRule belongs to.
* @throws {Error} When we cannot get the parentStyleSheet.
* @return {number} The index of the CSSRule, or -1.
*/
goog.cssom.getCssRuleIndexInParentStyleSheet = function(cssRule,
opt_parentStyleSheet) {
// Look for our special style.ruleIndex property from getAllCss.
if (cssRule.style && cssRule.style['-closure-rule-index']) {
return cssRule.style['-closure-rule-index'];
}
var parentStyleSheet = opt_parentStyleSheet ||
goog.cssom.getParentStyleSheet(cssRule);
if (!parentStyleSheet) {
// We could call getAllCssStyleRules() here to get our special indexes on
// the style object, but that seems like it could be wasteful.
throw Error('Cannot find a parentStyleSheet.');
}
var cssRuleList = goog.cssom.getCssRulesFromStyleSheet(parentStyleSheet);
if (cssRuleList && cssRuleList.length) {
for (var i = 0, n = cssRuleList.length, thisCssRule; i < n; i++) {
thisCssRule = cssRuleList[i];
if (thisCssRule == cssRule) {
return i;
}
}
}
return -1;
};
/**
* We do some trickery in getAllCssStyleRules that hacks this in for IE.
* If the cssRule object isn't coming from a result of that function call, this
* method will return undefined in IE.
* @param {CSSRule} cssRule The CSSRule.
* @return {CSSStyleSheet} A styleSheet object.
*/
goog.cssom.getParentStyleSheet = function(cssRule) {
return cssRule.parentStyleSheet ||
cssRule.style &&
cssRule.style['-closure-parent-stylesheet'];
};
/**
* Replace a cssRule with some cssText for a new rule.
* If the cssRule object is not one of objects returned by
* getAllCssStyleRules, then you'll need to provide both the styleSheet and
* possibly the index, since we can't infer them from the standard cssRule
* object in IE. We do some trickery in getAllCssStyleRules to hack this in.
* @param {CSSRule} cssRule A CSSRule.
* @param {string} cssText The text for the new CSSRule.
* @param {CSSStyleSheet=} opt_parentStyleSheet A reference to the stylesheet
* object this cssRule belongs to.
* @param {number=} opt_index The index of the cssRule in its parentStylesheet.
* @throws {Error} If we cannot find a parentStyleSheet.
* @throws {Error} If we cannot find a css rule index.
*/
goog.cssom.replaceCssRule = function(cssRule, cssText, opt_parentStyleSheet,
opt_index) {
var parentStyleSheet = opt_parentStyleSheet ||
goog.cssom.getParentStyleSheet(cssRule);
if (parentStyleSheet) {
var index = opt_index >= 0 ? opt_index :
goog.cssom.getCssRuleIndexInParentStyleSheet(cssRule, parentStyleSheet);
if (index >= 0) {
goog.cssom.removeCssRule(parentStyleSheet, index);
goog.cssom.addCssRule(parentStyleSheet, cssText, index);
} else {
throw Error('Cannot proceed without the index of the cssRule.');
}
} else {
throw Error('Cannot proceed without the parentStyleSheet.');
}
};
/**
* Cross browser function to add a CSSRule into a CSSStyleSheet, optionally
* at a given index.
* @param {CSSStyleSheet} cssStyleSheet The CSSRule's parentStyleSheet.
* @param {string} cssText The text for the new CSSRule.
* @param {number=} opt_index The index of the cssRule in its parentStylesheet.
* @throws {Error} If the css rule text appears to be ill-formatted.
* TODO(bowdidge): Inserting at index 0 fails on Firefox 2 and 3 with an
* exception warning "Node cannot be inserted at the specified point in
* the hierarchy."
*/
goog.cssom.addCssRule = function(cssStyleSheet, cssText, opt_index) {
var index = opt_index;
if (index < 0 || index == undefined) {
// If no index specified, insert at the end of the current list
// of rules.
// If on IE, use rules property, otherwise use cssRules property.
var rules = cssStyleSheet.rules || cssStyleSheet.cssRules;
index = rules.length;
}
if (cssStyleSheet.insertRule) {
// W3C.
cssStyleSheet.insertRule(cssText, index);
} else {
// IE: We have to parse the cssRule text to get the selector separated
// from the style text.
// aka Everything that isn't a colon, followed by a colon, then
// the rest is the style part.
var matches = /^([^\{]+)\{([^\{]+)\}/.exec(cssText);
if (matches.length == 3) {
var selector = matches[1];
var style = matches[2];
cssStyleSheet.addRule(selector, style, index);
} else {
throw Error('Your CSSRule appears to be ill-formatted.');
}
}
};
/**
* Cross browser function to remove a CSSRule in a CSSStyleSheet at an index.
* @param {CSSStyleSheet} cssStyleSheet The CSSRule's parentStyleSheet.
* @param {number} index The CSSRule's index in the parentStyleSheet.
*/
goog.cssom.removeCssRule = function(cssStyleSheet, index) {
if (cssStyleSheet.deleteRule) {
// W3C.
cssStyleSheet.deleteRule(index);
} else {
// IE.
cssStyleSheet.removeRule(index);
}
};
/**
* Appends a DOM node to HEAD containing the css text that's passed in.
* @param {string} cssText CSS to add to the end of the document.
* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper user for
* document interactions.
* @return {Element} The newly created STYLE element.
*/
goog.cssom.addCssText = function(cssText, opt_domHelper) {
var document = opt_domHelper ? opt_domHelper.getDocument() :
goog.dom.getDocument();
var cssNode = document.createElement('style');
cssNode.type = 'text/css';
var head = document.getElementsByTagName('head')[0];
head.appendChild(cssNode);
if (cssNode.styleSheet) {
// IE.
cssNode.styleSheet.cssText = cssText;
} else {
// W3C.
var cssTextNode = document.createTextNode(cssText);
cssNode.appendChild(cssTextNode);
}
return cssNode;
};
/**
* Cross browser method to get the filename from the StyleSheet's href.
* Explorer only returns the filename in the href, while other agents return
* the full path.
* @param {!StyleSheet} styleSheet Any valid StyleSheet object with an href.
* @throws {Error} When there's no href property found.
* @return {?string} filename The filename, or null if not an external
* styleSheet.
*/
goog.cssom.getFileNameFromStyleSheet = function(styleSheet) {
var href = styleSheet.href;
// Another IE/FF difference. IE returns an empty string, while FF and others
// return null for CSSStyleSheets not from an external file.
if (!href) {
return null;
}
// We need the regexp to ensure we get the filename minus any query params.
var matches = /([^\/\?]+)[^\/]*$/.exec(href);
var filename = matches[1];
return filename;
};
/**
* Recursively gets all CSS text or rules.
* @param {CSSStyleSheet|StyleSheetList} styleSheet The CSSStyleSheet.
* @param {boolean} isTextOutput If true, output is cssText, otherwise cssRules.
* @return {string|Array.<CSSRule>} cssText or cssRules.
* @private
*/
goog.cssom.getAllCss_ = function(styleSheet, isTextOutput) {
var cssOut = [];
var styleSheets = goog.cssom.getAllCssStyleSheets(styleSheet);
for (var i = 0; styleSheet = styleSheets[i]; i++) {
var cssRuleList = goog.cssom.getCssRulesFromStyleSheet(styleSheet);
if (cssRuleList && cssRuleList.length) {
// We're going to track cssRule index if we want rule output.
if (!isTextOutput) {
var ruleIndex = 0;
}
for (var j = 0, n = cssRuleList.length, cssRule; j < n; j++) {
cssRule = cssRuleList[j];
// Gets cssText output, ignoring CSSImportRules.
if (isTextOutput && !cssRule.href) {
var res = goog.cssom.getCssTextFromCssRule(cssRule);
cssOut.push(res);
} else if (!cssRule.href) {
// Gets cssRules output, ignoring CSSImportRules.
if (cssRule.style) {
// This is a fun little hack to get parentStyleSheet into the rule
// object for IE since it failed to implement rule.parentStyleSheet.
// We can later read this property when doing things like hunting
// for indexes in order to delete a given CSSRule.
// Unfortunately we have to use the style object to store these
// pieces of info since the rule object is read-only.
if (!cssRule.parentStyleSheet) {
cssRule.style['-closure-parent-stylesheet'] = styleSheet;
}
// This is a hack to help with possible removal of the rule later,
// where we just append the rule's index in its parentStyleSheet
// onto the style object as a property.
// Unfortunately we have to use the style object to store these
// pieces of info since the rule object is read-only.
cssRule.style['-closure-rule-index'] = ruleIndex;
}
cssOut.push(cssRule);
}
if (!isTextOutput) {
ruleIndex++;
}
}
}
}
return isTextOutput ? cssOut.join(' ') : cssOut;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,559 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview
* Central class for registering and accessing data sources
* Also handles processing of data events.
*
* There is a shared global instance that most client code should access via
* goog.ds.DataManager.getInstance(). However you can also create your own
* DataManager using new
*
* Implements DataNode to provide the top element in a data registry
* Prepends '$' to top level data names in path to denote they are root object
*
*/
goog.provide('goog.ds.DataManager');
goog.require('goog.ds.BasicNodeList');
goog.require('goog.ds.DataNode');
goog.require('goog.ds.Expr');
goog.require('goog.string');
goog.require('goog.structs');
goog.require('goog.structs.Map');
/**
* Create a DataManger
* @extends {goog.ds.DataNode}
* @constructor
*/
goog.ds.DataManager = function() {
this.dataSources_ = new goog.ds.BasicNodeList();
this.autoloads_ = new goog.structs.Map();
this.listenerMap_ = {};
this.listenersByFunction_ = {};
this.aliases_ = {};
this.eventCount_ = 0;
this.indexedListenersByFunction_ = {};
};
/**
* Global instance
* @private
*/
goog.ds.DataManager.instance_ = null;
goog.inherits(goog.ds.DataManager, goog.ds.DataNode);
/**
* Get the global instance
* @return {goog.ds.DataManager} The data manager singleton.
*/
goog.ds.DataManager.getInstance = function() {
if (!goog.ds.DataManager.instance_) {
goog.ds.DataManager.instance_ = new goog.ds.DataManager();
}
return goog.ds.DataManager.instance_;
};
/**
* Clears the global instance (for unit tests to reset state).
*/
goog.ds.DataManager.clearInstance = function() {
goog.ds.DataManager.instance_ = null;
};
/**
* Add a data source
* @param {goog.ds.DataNode} ds The data source.
* @param {boolean=} opt_autoload Whether to automatically load the data,
* defaults to false.
* @param {string=} opt_name Optional name, can also get name
* from the datasource.
*/
goog.ds.DataManager.prototype.addDataSource = function(ds, opt_autoload,
opt_name) {
var autoload = !!opt_autoload;
var name = opt_name || ds.getDataName();
if (!goog.string.startsWith(name, '$')) {
name = '$' + name;
}
ds.setDataName(name);
this.dataSources_.add(ds);
this.autoloads_.set(name, autoload);
};
/**
* Create an alias for a data path, very similar to assigning a variable.
* For example, you can set $CurrentContact -> $Request/Contacts[5], and all
* references to $CurrentContact will be procesed on $Request/Contacts[5].
*
* Aliases will hide datasources of the same name.
*
* @param {string} name Alias name, must be a top level path ($Foo).
* @param {string} dataPath Data path being aliased.
*/
goog.ds.DataManager.prototype.aliasDataSource = function(name, dataPath) {
if (!this.aliasListener_) {
this.aliasListener_ = goog.bind(this.listenForAlias_, this);
}
if (this.aliases_[name]) {
var oldPath = this.aliases_[name].getSource();
this.removeListeners(this.aliasListener_, oldPath + '/...', name);
}
this.aliases_[name] = goog.ds.Expr.create(dataPath);
this.addListener(this.aliasListener_, dataPath + '/...', name);
this.fireDataChange(name);
};
/**
* Listener function for matches of paths that have been aliased.
* Fires a data change on the alias as well.
*
* @param {string} dataPath Path of data event fired.
* @param {string} name Name of the alias.
* @private
*/
goog.ds.DataManager.prototype.listenForAlias_ = function(dataPath, name) {
var aliasedExpr = this.aliases_[name];
if (aliasedExpr) {
// If it's a subpath, appends the subpath to the alias name
// otherwise just fires on the top level alias
var aliasedPath = aliasedExpr.getSource();
if (dataPath.indexOf(aliasedPath) == 0) {
this.fireDataChange(name + dataPath.substring(aliasedPath.length));
} else {
this.fireDataChange(name);
}
}
};
/**
* Gets a named child node of the current node.
*
* @param {string} name The node name.
* @return {goog.ds.DataNode} The child node,
* or null if no node of this name exists.
*/
goog.ds.DataManager.prototype.getDataSource = function(name) {
if (this.aliases_[name]) {
return this.aliases_[name].getNode();
} else {
return this.dataSources_.get(name);
}
};
/**
* Get the value of the node
* @return {Object} The value of the node, or null if no value.
* @override
*/
goog.ds.DataManager.prototype.get = function() {
return this.dataSources_;
};
/** @override */
goog.ds.DataManager.prototype.set = function(value) {
throw Error('Can\'t set on DataManager');
};
/** @override */
goog.ds.DataManager.prototype.getChildNodes = function(opt_selector) {
if (opt_selector) {
return new goog.ds.BasicNodeList(
[this.getChildNode(/** @type {string} */(opt_selector))]);
} else {
return this.dataSources_;
}
};
/**
* Gets a named child node of the current node
* @param {string} name The node name.
* @return {goog.ds.DataNode} The child node,
* or null if no node of this name exists.
* @override
*/
goog.ds.DataManager.prototype.getChildNode = function(name) {
return this.getDataSource(name);
};
/** @override */
goog.ds.DataManager.prototype.getChildNodeValue = function(name) {
var ds = this.getDataSource(name);
return ds ? ds.get() : null;
};
/**
* Get the name of the node relative to the parent node
* @return {string} The name of the node.
* @override
*/
goog.ds.DataManager.prototype.getDataName = function() {
return '';
};
/**
* Gets the a qualified data path to this node
* @return {string} The data path.
* @override
*/
goog.ds.DataManager.prototype.getDataPath = function() {
return '';
};
/**
* Load or reload the backing data for this node
* only loads datasources flagged with autoload
* @override
*/
goog.ds.DataManager.prototype.load = function() {
var len = this.dataSources_.getCount();
for (var i = 0; i < len; i++) {
var ds = this.dataSources_.getByIndex(i);
var autoload = this.autoloads_.get(ds.getDataName());
if (autoload) {
ds.load();
}
}
};
/**
* Gets the state of the backing data for this node
* @return {goog.ds.LoadState} The state.
* @override
*/
goog.ds.DataManager.prototype.getLoadState = goog.abstractMethod;
/**
* Whether the value of this node is a homogeneous list of data
* @return {boolean} True if a list.
* @override
*/
goog.ds.DataManager.prototype.isList = function() {
return false;
};
/**
* Get the total count of events fired (mostly for debugging)
* @return {number} Count of events.
*/
goog.ds.DataManager.prototype.getEventCount = function() {
return this.eventCount_;
};
/**
* Adds a listener
* Listeners should fire when any data with path that has dataPath as substring
* is changed.
* TODO(user) Look into better listener handling
*
* @param {Function} fn Callback function, signature function(dataPath, id).
* @param {string} dataPath Fully qualified data path.
* @param {string=} opt_id A value passed back to the listener when the dataPath
* is matched.
*/
goog.ds.DataManager.prototype.addListener = function(fn, dataPath, opt_id) {
// maxAncestor sets how distant an ancestor you can be of the fired event
// and still fire (you always fire if you are a descendant).
// 0 means you don't fire if you are an ancestor
// 1 means you only fire if you are parent
// 1000 means you will fire if you are ancestor (effectively infinite)
var maxAncestors = 0;
if (goog.string.endsWith(dataPath, '/...')) {
maxAncestors = 1000;
dataPath = dataPath.substring(0, dataPath.length - 4);
} else if (goog.string.endsWith(dataPath, '/*')) {
maxAncestors = 1;
dataPath = dataPath.substring(0, dataPath.length - 2);
}
opt_id = opt_id || '';
var key = dataPath + ':' + opt_id + ':' + goog.getUid(fn);
var listener = {dataPath: dataPath, id: opt_id, fn: fn};
var expr = goog.ds.Expr.create(dataPath);
var fnUid = goog.getUid(fn);
if (!this.listenersByFunction_[fnUid]) {
this.listenersByFunction_[fnUid] = {};
}
this.listenersByFunction_[fnUid][key] = {listener: listener, items: []};
while (expr) {
var listenerSpec = {listener: listener, maxAncestors: maxAncestors};
var matchingListeners = this.listenerMap_[expr.getSource()];
if (matchingListeners == null) {
matchingListeners = {};
this.listenerMap_[expr.getSource()] = matchingListeners;
}
matchingListeners[key] = listenerSpec;
maxAncestors = 0;
expr = expr.getParent();
this.listenersByFunction_[fnUid][key].items.push(
{key: key, obj: matchingListeners});
}
};
/**
* Adds an indexed listener.
*
* Indexed listeners allow for '*' in data paths. If a * exists, will match
* all values and return the matched values in an array to the callback.
*
* Currently uses a promiscuous match algorithm: Matches everything before the
* first '*', and then does a regex match for all of the returned events.
* Although this isn't optimized, it is still an improvement as you can collapse
* 100's of listeners into a single regex match
*
* @param {Function} fn Callback function, signature (dataPath, id, indexes).
* @param {string} dataPath Fully qualified data path.
* @param {string=} opt_id A value passed back to the listener when the dataPath
* is matched.
*/
goog.ds.DataManager.prototype.addIndexedListener = function(fn, dataPath,
opt_id) {
var firstStarPos = dataPath.indexOf('*');
// Just need a regular listener
if (firstStarPos == -1) {
this.addListener(fn, dataPath, opt_id);
return;
}
var listenPath = dataPath.substring(0, firstStarPos) + '...';
// Create regex that matches * to any non '\' character
var ext = '$';
if (goog.string.endsWith(dataPath, '/...')) {
dataPath = dataPath.substring(0, dataPath.length - 4);
ext = '';
}
var regExpPath = goog.string.regExpEscape(dataPath);
var matchRegExp = regExpPath.replace(/\\\*/g, '([^\\\/]+)') + ext;
// Matcher function applies the regex and calls back the original function
// if the regex matches, passing in an array of the matched values
var matchRegExpRe = new RegExp(matchRegExp);
var matcher = function(path, id) {
var match = matchRegExpRe.exec(path);
if (match) {
match.shift();
fn(path, opt_id, match);
}
};
this.addListener(matcher, listenPath, opt_id);
// Add the indexed listener to the map so that we can remove it later.
var fnUid = goog.getUid(fn);
if (!this.indexedListenersByFunction_[fnUid]) {
this.indexedListenersByFunction_[fnUid] = {};
}
var key = dataPath + ':' + opt_id;
this.indexedListenersByFunction_[fnUid][key] = {
listener: {dataPath: listenPath, fn: matcher, id: opt_id}
};
};
/**
* Removes indexed listeners with a given callback function, and optional
* matching datapath and matching id.
*
* @param {Function} fn Callback function, signature function(dataPath, id).
* @param {string=} opt_dataPath Fully qualified data path.
* @param {string=} opt_id A value passed back to the listener when the dataPath
* is matched.
*/
goog.ds.DataManager.prototype.removeIndexedListeners = function(
fn, opt_dataPath, opt_id) {
this.removeListenersByFunction_(
this.indexedListenersByFunction_, true, fn, opt_dataPath, opt_id);
};
/**
* Removes listeners with a given callback function, and optional
* matching dataPath and matching id
*
* @param {Function} fn Callback function, signature function(dataPath, id).
* @param {string=} opt_dataPath Fully qualified data path.
* @param {string=} opt_id A value passed back to the listener when the dataPath
* is matched.
*/
goog.ds.DataManager.prototype.removeListeners = function(fn, opt_dataPath,
opt_id) {
// Normalize data path root
if (opt_dataPath && goog.string.endsWith(opt_dataPath, '/...')) {
opt_dataPath = opt_dataPath.substring(0, opt_dataPath.length - 4);
} else if (opt_dataPath && goog.string.endsWith(opt_dataPath, '/*')) {
opt_dataPath = opt_dataPath.substring(0, opt_dataPath.length - 2);
}
this.removeListenersByFunction_(
this.listenersByFunction_, false, fn, opt_dataPath, opt_id);
};
/**
* Removes listeners with a given callback function, and optional
* matching dataPath and matching id from the given listenersByFunction
* data structure.
*
* @param {Object} listenersByFunction The listeners by function.
* @param {boolean} indexed Indicates whether the listenersByFunction are
* indexed or not.
* @param {Function} fn Callback function, signature function(dataPath, id).
* @param {string=} opt_dataPath Fully qualified data path.
* @param {string=} opt_id A value passed back to the listener when the dataPath
* is matched.
* @private
*/
goog.ds.DataManager.prototype.removeListenersByFunction_ = function(
listenersByFunction, indexed, fn, opt_dataPath, opt_id) {
var fnUid = goog.getUid(fn);
var functionMatches = listenersByFunction[fnUid];
if (functionMatches != null) {
for (var key in functionMatches) {
var functionMatch = functionMatches[key];
var listener = functionMatch.listener;
if ((!opt_dataPath || opt_dataPath == listener.dataPath) &&
(!opt_id || opt_id == listener.id)) {
if (indexed) {
this.removeListeners(
listener.fn, listener.dataPath, listener.id);
}
if (functionMatch.items) {
for (var i = 0; i < functionMatch.items.length; i++) {
var item = functionMatch.items[i];
delete item.obj[item.key];
}
}
delete functionMatches[key];
}
}
}
};
/**
* Get the total number of listeners (per expression listened to, so may be
* more than number of times addListener() has been called
* @return {number} Number of listeners.
*/
goog.ds.DataManager.prototype.getListenerCount = function() {
var count = 0;
goog.structs.forEach(this.listenerMap_, function(matchingListeners) {
count += goog.structs.getCount(matchingListeners);
});
return count;
};
/**
* Disables the sending of all data events during the execution of the given
* callback. This provides a way to avoid useless notifications of small changes
* when you will eventually send a data event manually that encompasses them
* all.
*
* Note that this function can not be called reentrantly.
*
* @param {Function} callback Zero-arg function to execute.
*/
goog.ds.DataManager.prototype.runWithoutFiringDataChanges = function(callback) {
if (this.disableFiring_) {
throw Error('Can not nest calls to runWithoutFiringDataChanges');
}
this.disableFiring_ = true;
try {
callback();
} finally {
this.disableFiring_ = false;
}
};
/**
* Fire a data change event to all listeners
*
* If the path matches the path of a listener, the listener will fire
*
* If your path is the parent of a listener, the listener will fire. I.e.
* if $Contacts/bob@bob.com changes, then we will fire listener for
* $Contacts/bob@bob.com/Name as well, as the assumption is that when
* a parent changes, all children are invalidated.
*
* If your path is the child of a listener, the listener may fire, depending
* on the ancestor depth.
*
* A listener for $Contacts might only be interested if the contact name changes
* (i.e. $Contacts doesn't fire on $Contacts/bob@bob.com/Name),
* while a listener for a specific contact might
* (i.e. $Contacts/bob@bob.com would fire on $Contacts/bob@bob.com/Name).
* Adding "/..." to a lisetener path listens to all children, and adding "/*" to
* a listener path listens only to direct children
*
* @param {string} dataPath Fully qualified data path.
*/
goog.ds.DataManager.prototype.fireDataChange = function(dataPath) {
if (this.disableFiring_) {
return;
}
var expr = goog.ds.Expr.create(dataPath);
var ancestorDepth = 0;
// Look for listeners for expression and all its parents.
// Parents of listener expressions are all added to the listenerMap as well,
// so this will evaluate inner loop every time the dataPath is a child or
// an ancestor of the original listener path
while (expr) {
var matchingListeners = this.listenerMap_[expr.getSource()];
if (matchingListeners) {
for (var id in matchingListeners) {
var match = matchingListeners[id];
var listener = match.listener;
if (ancestorDepth <= match.maxAncestors) {
listener.fn(dataPath, listener.id);
}
}
}
ancestorDepth++;
expr = expr.getParent();
}
this.eventCount_++;
};

View File

@@ -0,0 +1,656 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Generic rich data access API.
*
* Abstraction for data sources that allows listening for changes at different
* levels of the data tree and updating the data via XHR requests
*
*/
goog.provide('goog.ds.BaseDataNode');
goog.provide('goog.ds.BasicNodeList');
goog.provide('goog.ds.DataNode');
goog.provide('goog.ds.DataNodeList');
goog.provide('goog.ds.EmptyNodeList');
goog.provide('goog.ds.LoadState');
goog.provide('goog.ds.SortedNodeList');
goog.provide('goog.ds.Util');
goog.provide('goog.ds.logger');
goog.require('goog.array');
goog.require('goog.log');
/**
* Interface for node in rich data tree.
*
* Names that are reserved for system use and shouldn't be used for data node
* names: eval, toSource, toString, unwatch, valueOf, watch. Behavior is
* undefined if these names are used.
*
* @constructor
*/
goog.ds.DataNode = function() {};
/**
* Get the value of the node
* @param {...?} var_args Do not check arity of arguments, because
* some subclasses require args.
* @return {*} The value of the node, or null if no value.
*/
goog.ds.DataNode.prototype.get = goog.abstractMethod;
/**
* Set the value of the node
* @param {*} value The new value of the node.
*/
goog.ds.DataNode.prototype.set = goog.abstractMethod;
/**
* Gets all of the child nodes of the current node.
* Should return an empty DataNode list if no child nodes.
* @param {string=} opt_selector String selector to choose child nodes.
* @return {goog.ds.DataNodeList} The child nodes.
*/
goog.ds.DataNode.prototype.getChildNodes = goog.abstractMethod;
/**
* Gets a named child node of the current node
* @param {string} name The node name.
* @param {boolean=} opt_canCreate Whether to create a child node if it does not
* exist.
* @return {goog.ds.DataNode} The child node, or null
* if no node of this name exists.
*/
goog.ds.DataNode.prototype.getChildNode = goog.abstractMethod;
/**
* Gets the value of a child node
* @param {string} name The node name.
* @return {*} The value of the node, or null if no value or the child node
* doesn't exist.
*/
goog.ds.DataNode.prototype.getChildNodeValue = goog.abstractMethod;
/**
* Sets a named child node of the current node.
*
* @param {string} name The node name.
* @param {Object} value The value to set, can be DataNode, object, property,
* or null. If value is null, removes the child node.
* @return {Object} The child node, if the node was set.
*/
goog.ds.DataNode.prototype.setChildNode = goog.abstractMethod;
/**
* Get the name of the node relative to the parent node
* @return {string} The name of the node.
*/
goog.ds.DataNode.prototype.getDataName = goog.abstractMethod;
/**
* Set the name of the node relative to the parent node
* @param {string} name The name of the node.
*/
goog.ds.DataNode.prototype.setDataName = goog.abstractMethod;
/**
* Gets the a qualified data path to this node
* @return {string} The data path.
*/
goog.ds.DataNode.prototype.getDataPath = goog.abstractMethod;
/**
* Load or reload the backing data for this node
*/
goog.ds.DataNode.prototype.load = goog.abstractMethod;
/**
* Gets the state of the backing data for this node
* @return {goog.ds.LoadState} The state.
*/
goog.ds.DataNode.prototype.getLoadState = goog.abstractMethod;
/**
* Whether the value of this node is a homogeneous list of data
* @return {boolean} True if a list.
*/
goog.ds.DataNode.prototype.isList = goog.abstractMethod;
/**
* Enum for load state of a DataNode.
* @enum {string}
*/
goog.ds.LoadState = {
LOADED: 'LOADED',
LOADING: 'LOADING',
FAILED: 'FAILED',
NOT_LOADED: 'NOT_LOADED'
};
/**
* Base class for data node functionality, has default implementations for
* many of the functions.
*
* implements {goog.ds.DataNode}
* @constructor
*/
goog.ds.BaseDataNode = function() {};
/**
* Set the value of the node
* @param {Object} value The new value of the node.
*/
goog.ds.BaseDataNode.prototype.set = goog.abstractMethod;
/**
* Gets all of the child nodes of the current node.
* Should return an empty DataNode list if no child nodes.
* @param {string=} opt_selector String selector to choose child nodes.
* @return {goog.ds.DataNodeList} The child nodes.
*/
goog.ds.BaseDataNode.prototype.getChildNodes = function(opt_selector) {
return new goog.ds.EmptyNodeList();
};
/**
* Gets a named child node of the current node
* @param {string} name The node name.
* @param {boolean=} opt_canCreate Whether you can create the child node if
* it doesn't exist already.
* @return {goog.ds.DataNode} The child node, or null if no node of
* this name exists and opt_create is false.
*/
goog.ds.BaseDataNode.prototype.getChildNode = function(name, opt_canCreate) {
return null;
};
/**
* Gets the value of a child node
* @param {string} name The node name.
* @return {Object} The value of the node, or null if no value or the
* child node doesn't exist.
*/
goog.ds.BaseDataNode.prototype.getChildNodeValue = function(name) {
return null;
};
/**
* Get the name of the node relative to the parent node
* @return {string} The name of the node.
*/
goog.ds.BaseDataNode.prototype.getDataName = goog.abstractMethod;
/**
* Gets the a qualified data path to this node
* @return {string} The data path.
*/
goog.ds.BaseDataNode.prototype.getDataPath = function() {
var parentPath = '';
var myName = this.getDataName();
if (this.getParent && this.getParent()) {
parentPath = this.getParent().getDataPath() +
(myName.indexOf(goog.ds.STR_ARRAY_START) != -1 ? '' :
goog.ds.STR_PATH_SEPARATOR);
}
return parentPath + myName;
};
/**
* Load or reload the backing data for this node
*/
goog.ds.BaseDataNode.prototype.load = goog.nullFunction;
/**
* Gets the state of the backing data for this node
* @return {goog.ds.LoadState} The state.
*/
goog.ds.BaseDataNode.prototype.getLoadState = function() {
return goog.ds.LoadState.LOADED;
};
/**
* Gets the parent node. Subclasses implement this function
* @type {Function}
* @protected
*/
goog.ds.BaseDataNode.prototype.getParent = null;
/**
* Interface for node list in rich data tree.
*
* Has both map and list-style accessors
*
* @constructor
* @extends {goog.ds.DataNode}
*/
// TODO(arv): Use interfaces when available.
goog.ds.DataNodeList = function() {};
/**
* Add a node to the node list.
* If the node has a dataName, uses this for the key in the map.
*
* @param {goog.ds.DataNode} node The node to add.
*/
goog.ds.DataNodeList.prototype.add = goog.abstractMethod;
/**
* Get a node by string key.
* Returns null if node doesn't exist.
*
* @param {string} key String lookup key.
* @return {*} The node, or null if doesn't exist.
* @override
*/
goog.ds.DataNodeList.prototype.get = goog.abstractMethod;
/**
* Get a node by index
* Returns null if the index is out of range
*
* @param {number} index The index of the node.
* @return {goog.ds.DataNode} The node, or null if doesn't exist.
*/
goog.ds.DataNodeList.prototype.getByIndex = goog.abstractMethod;
/**
* Gets the size of the node list
*
* @return {number} The size of the list.
*/
goog.ds.DataNodeList.prototype.getCount = goog.abstractMethod;
/**
* Sets a node in the list of a given name
* @param {string} name Name of the node.
* @param {goog.ds.DataNode} node The node.
*/
goog.ds.DataNodeList.prototype.setNode = goog.abstractMethod;
/**
* Removes a node in the list of a given name
* @param {string} name Name of the node.
* @return {boolean} True if node existed and was deleted.
*/
goog.ds.DataNodeList.prototype.removeNode = goog.abstractMethod;
/**
* Simple node list implementation with underlying array and map
* implements goog.ds.DataNodeList.
*
* Names that are reserved for system use and shouldn't be used for data node
* names: eval, toSource, toString, unwatch, valueOf, watch. Behavior is
* undefined if these names are used.
*
* @param {Array.<goog.ds.DataNode>=} opt_nodes optional nodes to add to list.
* @constructor
* @extends {goog.ds.DataNodeList}
*/
// TODO(arv): Use interfaces when available.
goog.ds.BasicNodeList = function(opt_nodes) {
this.map_ = {};
this.list_ = [];
this.indexMap_ = {};
if (opt_nodes) {
for (var i = 0, node; node = opt_nodes[i]; i++) {
this.add(node);
}
}
};
/**
* Add a node to the node list.
* If the node has a dataName, uses this for the key in the map.
* TODO(user) Remove function as well
*
* @param {goog.ds.DataNode} node The node to add.
* @override
*/
goog.ds.BasicNodeList.prototype.add = function(node) {
this.list_.push(node);
var dataName = node.getDataName();
if (dataName) {
this.map_[dataName] = node;
this.indexMap_[dataName] = this.list_.length - 1;
}
};
/**
* Get a node by string key.
* Returns null if node doesn't exist.
*
* @param {string} key String lookup key.
* @return {goog.ds.DataNode} The node, or null if doesn't exist.
* @override
*/
goog.ds.BasicNodeList.prototype.get = function(key) {
return this.map_[key] || null;
};
/**
* Get a node by index
* Returns null if the index is out of range
*
* @param {number} index The index of the node.
* @return {goog.ds.DataNode} The node, or null if doesn't exist.
* @override
*/
goog.ds.BasicNodeList.prototype.getByIndex = function(index) {
return this.list_[index] || null;
};
/**
* Gets the size of the node list
*
* @return {number} The size of the list.
* @override
*/
goog.ds.BasicNodeList.prototype.getCount = function() {
return this.list_.length;
};
/**
* Sets a node in the list of a given name
* @param {string} name Name of the node.
* @param {goog.ds.DataNode} node The node.
* @override
*/
goog.ds.BasicNodeList.prototype.setNode = function(name, node) {
if (node == null) {
this.removeNode(name);
} else {
var existingNode = this.indexMap_[name];
if (existingNode != null) {
this.map_[name] = node;
this.list_[existingNode] = node;
} else {
this.add(node);
}
}
};
/**
* Removes a node in the list of a given name
* @param {string} name Name of the node.
* @return {boolean} True if node existed and was deleted.
* @override
*/
goog.ds.BasicNodeList.prototype.removeNode = function(name) {
var existingNode = this.indexMap_[name];
if (existingNode != null) {
this.list_.splice(existingNode, 1);
delete this.map_[name];
delete this.indexMap_[name];
for (var index in this.indexMap_) {
if (this.indexMap_[index] > existingNode) {
this.indexMap_[index]--;
}
}
}
return existingNode != null;
};
/**
* Get the index of a named node
* @param {string} name The name of the node to get the index of.
* @return {number|undefined} The index.
*/
goog.ds.BasicNodeList.prototype.indexOf = function(name) {
return this.indexMap_[name];
};
/**
* Immulatable empty node list
* @extends {goog.ds.BasicNodeList}
* @constructor
*/
goog.ds.EmptyNodeList = function() {
goog.ds.BasicNodeList.call(this);
};
goog.inherits(goog.ds.EmptyNodeList, goog.ds.BasicNodeList);
/**
* Add a node to the node list.
* If the node has a dataName, uses this for the key in the map.
*
* @param {goog.ds.DataNode} node The node to add.
* @override
*/
goog.ds.EmptyNodeList.prototype.add = function(node) {
throw Error('Can\'t add to EmptyNodeList');
};
/**
* Node list implementation which maintains sort order during insertion and
* modification operations based on a comparison function.
*
* The SortedNodeList does not guarantee sort order will be maintained if
* the underlying data nodes are modified externally.
*
* Names that are reserved for system use and shouldn't be used for data node
* names: eval, toSource, toString, unwatch, valueOf, watch. Behavior is
* undefined if these names are used.
*
* @param {Function} compareFn Comparison function by which the
* node list is sorted. Should take 2 arguments to compare, and return a
* negative integer, zero, or a positive integer depending on whether the
* first argument is less than, equal to, or greater than the second.
* @param {Array.<goog.ds.DataNode>=} opt_nodes optional nodes to add to list;
* these are assumed to be in sorted order.
* @extends {goog.ds.BasicNodeList}
* @constructor
*/
goog.ds.SortedNodeList = function(compareFn, opt_nodes) {
this.compareFn_ = compareFn;
goog.ds.BasicNodeList.call(this, opt_nodes);
};
goog.inherits(goog.ds.SortedNodeList, goog.ds.BasicNodeList);
/**
* Add a node to the node list, maintaining sort order.
* If the node has a dataName, uses this for the key in the map.
*
* @param {goog.ds.DataNode} node The node to add.
* @override
*/
goog.ds.SortedNodeList.prototype.add = function(node) {
if (!this.compareFn_) {
this.append(node);
return;
}
var searchLoc = goog.array.binarySearch(this.list_, node, this.compareFn_);
// if there is another node that is "equal" according to the comparison
// function, insert before that one; otherwise insert at the location
// goog.array.binarySearch indicated
if (searchLoc < 0) {
searchLoc = -(searchLoc + 1);
}
// update any indexes that are after the insertion point
for (var index in this.indexMap_) {
if (this.indexMap_[index] >= searchLoc) {
this.indexMap_[index]++;
}
}
goog.array.insertAt(this.list_, node, searchLoc);
var dataName = node.getDataName();
if (dataName) {
this.map_[dataName] = node;
this.indexMap_[dataName] = searchLoc;
}
};
/**
* Adds the given node to the end of the SortedNodeList. This should
* only be used when the caller can guarantee that the sort order will
* be maintained according to this SortedNodeList's compareFn (e.g.
* when initializing a new SortedNodeList from a list of nodes that has
* already been sorted).
* @param {goog.ds.DataNode} node The node to append.
*/
goog.ds.SortedNodeList.prototype.append = function(node) {
goog.ds.SortedNodeList.superClass_.add.call(this, node);
};
/**
* Sets a node in the list of a given name, maintaining sort order.
* @param {string} name Name of the node.
* @param {goog.ds.DataNode} node The node.
* @override
*/
goog.ds.SortedNodeList.prototype.setNode = function(name, node) {
if (node == null) {
this.removeNode(name);
} else {
var existingNode = this.indexMap_[name];
if (existingNode != null) {
if (this.compareFn_) {
var compareResult = this.compareFn_(this.list_[existingNode], node);
if (compareResult == 0) {
// the new node can just replace the old one
this.map_[name] = node;
this.list_[existingNode] = node;
} else {
// remove the old node, then add the new one
this.removeNode(name);
this.add(node);
}
}
} else {
this.add(node);
}
}
};
/**
* The character denoting an attribute.
* @type {string}
*/
goog.ds.STR_ATTRIBUTE_START = '@';
/**
* The character denoting all children.
* @type {string}
*/
goog.ds.STR_ALL_CHILDREN_SELECTOR = '*';
/**
* The wildcard character.
* @type {string}
*/
goog.ds.STR_WILDCARD = '*';
/**
* The character denoting path separation.
* @type {string}
*/
goog.ds.STR_PATH_SEPARATOR = '/';
/**
* The character denoting the start of an array.
* @type {string}
*/
goog.ds.STR_ARRAY_START = '[';
/**
* Shared logger instance for data package
* @type {goog.log.Logger}
*/
goog.ds.logger = goog.log.getLogger('goog.ds');
/**
* Create a data node that references another data node,
* useful for pointer-like functionality.
* All functions will return same values as the original node except for
* getDataName()
* @param {!goog.ds.DataNode} node The original node.
* @param {string} name The new name.
* @return {!goog.ds.DataNode} The new data node.
*/
goog.ds.Util.makeReferenceNode = function(node, name) {
/**
* @constructor
* @extends {goog.ds.DataNode}
*/
var nodeCreator = function() {};
nodeCreator.prototype = node;
var newNode = new nodeCreator();
newNode.getDataName = function() {
return name;
};
return newNode;
};

View File

@@ -0,0 +1,544 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview
* Expression evaluation utilities. Expression format is very similar to XPath.
*
* Expression details:
* - Of format A/B/C, which will evaluate getChildNode('A').getChildNode('B').
* getChildNodes('C')|getChildNodeValue('C')|getChildNode('C') depending on
* call
* - If expression ends with '/name()', will get the name() of the node
* referenced by the preceding path.
* - If expression ends with '/count()', will get the count() of the nodes that
* match the expression referenced by the preceding path.
* - If expression ends with '?', the value is OK to evaluate to null. This is
* not enforced by the expression evaluation functions, instead it is
* provided as a flag for client code which may ignore depending on usage
* - If expression has [INDEX], will use getChildNodes().getByIndex(INDEX)
*
*/
goog.provide('goog.ds.Expr');
goog.require('goog.ds.BasicNodeList');
goog.require('goog.ds.EmptyNodeList');
goog.require('goog.string');
/**
* Create a new expression. An expression uses a string expression language, and
* from this string and a passed in DataNode can evaluate to a value, DataNode,
* or a DataNodeList.
*
* @param {string=} opt_expr The string expression.
* @constructor
*/
goog.ds.Expr = function(opt_expr) {
if (opt_expr) {
this.setSource_(opt_expr);
}
};
/**
* Set the source expression text & parse
*
* @param {string} expr The string expression source.
* @param {Array=} opt_parts Array of the parts of an expression.
* @param {goog.ds.Expr=} opt_childExpr Optional child of this expression,
* passed in as a hint for processing.
* @param {goog.ds.Expr=} opt_prevExpr Optional preceding expression
* (i.e. $A/B/C is previous expression to B/C) passed in as a hint for
* processing.
* @private
*/
goog.ds.Expr.prototype.setSource_ = function(expr, opt_parts,
opt_childExpr, opt_prevExpr) {
this.src_ = expr;
if (!opt_childExpr && !opt_prevExpr) {
// Check whether it can be empty
if (goog.string.endsWith(expr, goog.ds.Expr.String_.CAN_BE_EMPTY)) {
this.canBeEmpty_ = true;
expr = expr.substring(0, expr.length - 1);
}
// Check whether this is an node function
if (goog.string.endsWith(expr, '()')) {
if (goog.string.endsWith(expr, goog.ds.Expr.String_.NAME_EXPR) ||
goog.string.endsWith(expr, goog.ds.Expr.String_.COUNT_EXPR) ||
goog.string.endsWith(expr, goog.ds.Expr.String_.POSITION_EXPR)) {
var lastPos = expr.lastIndexOf(goog.ds.Expr.String_.SEPARATOR);
if (lastPos != -1) {
this.exprFn_ = expr.substring(lastPos + 1);
expr = expr.substring(0, lastPos);
} else {
this.exprFn_ = expr;
expr = goog.ds.Expr.String_.CURRENT_NODE_EXPR;
}
if (this.exprFn_ == goog.ds.Expr.String_.COUNT_EXPR) {
this.isCount_ = true;
}
}
}
}
// Split into component parts
this.parts_ = opt_parts || expr.split('/');
this.size_ = this.parts_.length;
this.last_ = this.parts_[this.size_ - 1];
this.root_ = this.parts_[0];
if (this.size_ == 1) {
this.rootExpr_ = this;
this.isAbsolute_ = goog.string.startsWith(expr, '$');
} else {
this.rootExpr_ = goog.ds.Expr.createInternal_(this.root_, null,
this, null);
this.isAbsolute_ = this.rootExpr_.isAbsolute_;
this.root_ = this.rootExpr_.root_;
}
if (this.size_ == 1 && !this.isAbsolute_) {
// Check whether expression maps to current node, for convenience
this.isCurrent_ = (expr == goog.ds.Expr.String_.CURRENT_NODE_EXPR ||
expr == goog.ds.Expr.String_.EMPTY_EXPR);
// Whether this expression is just an attribute (i.e. '@foo')
this.isJustAttribute_ =
goog.string.startsWith(expr, goog.ds.Expr.String_.ATTRIBUTE_START);
// Check whether this is a common node expression
this.isAllChildNodes_ = expr == goog.ds.Expr.String_.ALL_CHILD_NODES_EXPR;
this.isAllAttributes_ = expr == goog.ds.Expr.String_.ALL_ATTRIBUTES_EXPR;
this.isAllElements_ = expr == goog.ds.Expr.String_.ALL_ELEMENTS_EXPR;
}
};
/**
* Get the source data path for the expression
* @return {string} The path.
*/
goog.ds.Expr.prototype.getSource = function() {
return this.src_;
};
/**
* Gets the last part of the expression.
* @return {?string} Last part of the expression.
*/
goog.ds.Expr.prototype.getLast = function() {
return this.last_;
};
/**
* Gets the parent expression of this expression, or null if this is top level
* @return {goog.ds.Expr} The parent.
*/
goog.ds.Expr.prototype.getParent = function() {
if (!this.parentExprSet_) {
if (this.size_ > 1) {
this.parentExpr_ = goog.ds.Expr.createInternal_(null,
this.parts_.slice(0, this.parts_.length - 1), this, null);
}
this.parentExprSet_ = true;
}
return this.parentExpr_;
};
/**
* Gets the parent expression of this expression, or null if this is top level
* @return {goog.ds.Expr} The parent.
*/
goog.ds.Expr.prototype.getNext = function() {
if (!this.nextExprSet_) {
if (this.size_ > 1) {
this.nextExpr_ = goog.ds.Expr.createInternal_(null, this.parts_.slice(1),
null, this);
}
this.nextExprSet_ = true;
}
return this.nextExpr_;
};
/**
* Evaluate an expression on a data node, and return a value
* Recursively walks through child nodes to evaluate
* TODO(user) Support other expression functions
*
* @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against.
* If not provided, evaluates against DataManager global root.
* @return {*} Value of the node, or null if doesn't exist.
*/
goog.ds.Expr.prototype.getValue = function(opt_ds) {
if (opt_ds == null) {
opt_ds = goog.ds.DataManager.getInstance();
} else if (this.isAbsolute_) {
opt_ds = opt_ds.getDataRoot ? opt_ds.getDataRoot() :
goog.ds.DataManager.getInstance();
}
if (this.isCount_) {
var nodes = this.getNodes(opt_ds);
return nodes.getCount();
}
if (this.size_ == 1) {
return opt_ds.getChildNodeValue(this.root_);
} else if (this.size_ == 0) {
return opt_ds.get();
}
var nextDs = opt_ds.getChildNode(this.root_);
if (nextDs == null) {
return null;
} else {
return this.getNext().getValue(nextDs);
}
};
/**
* Evaluate an expression on a data node, and return matching nodes
* Recursively walks through child nodes to evaluate
*
* @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against.
* If not provided, evaluates against data root.
* @param {boolean=} opt_canCreate If true, will try to create new nodes.
* @return {goog.ds.DataNodeList} Matching nodes.
*/
goog.ds.Expr.prototype.getNodes = function(opt_ds, opt_canCreate) {
return /** @type {goog.ds.DataNodeList} */(this.getNodes_(opt_ds,
false, opt_canCreate));
};
/**
* Evaluate an expression on a data node, and return the first matching node
* Recursively walks through child nodes to evaluate
*
* @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against.
* If not provided, evaluates against DataManager global root.
* @param {boolean=} opt_canCreate If true, will try to create new nodes.
* @return {goog.ds.DataNode} Matching nodes, or null if doesn't exist.
*/
goog.ds.Expr.prototype.getNode = function(opt_ds, opt_canCreate) {
return /** @type {goog.ds.DataNode} */(this.getNodes_(opt_ds,
true, opt_canCreate));
};
/**
* Evaluate an expression on a data node, and return the first matching node
* Recursively walks through child nodes to evaluate
*
* @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against.
* If not provided, evaluates against DataManager global root.
* @param {boolean=} opt_selectOne Whether to return single matching DataNode
* or matching nodes in DataNodeList.
* @param {boolean=} opt_canCreate If true, will try to create new nodes.
* @return {goog.ds.DataNode|goog.ds.DataNodeList} Matching node or nodes,
* depending on value of opt_selectOne.
* @private
*/
goog.ds.Expr.prototype.getNodes_ = function(opt_ds, opt_selectOne,
opt_canCreate) {
if (opt_ds == null) {
opt_ds = goog.ds.DataManager.getInstance();
} else if (this.isAbsolute_) {
opt_ds = opt_ds.getDataRoot ? opt_ds.getDataRoot() :
goog.ds.DataManager.getInstance();
}
if (this.size_ == 0 && opt_selectOne) {
return opt_ds;
} else if (this.size_ == 0 && !opt_selectOne) {
return new goog.ds.BasicNodeList([opt_ds]);
} else if (this.size_ == 1) {
if (opt_selectOne) {
return opt_ds.getChildNode(this.root_, opt_canCreate);
}
else {
var possibleListChild = opt_ds.getChildNode(this.root_);
if (possibleListChild && possibleListChild.isList()) {
return possibleListChild.getChildNodes();
} else {
return opt_ds.getChildNodes(this.root_);
}
}
} else {
var nextDs = opt_ds.getChildNode(this.root_, opt_canCreate);
if (nextDs == null && opt_selectOne) {
return null;
} else if (nextDs == null && !opt_selectOne) {
return new goog.ds.EmptyNodeList();
}
return this.getNext().getNodes_(nextDs, opt_selectOne, opt_canCreate);
}
};
/**
* Whether the expression can be null.
*
* @type {boolean}
* @private
*/
goog.ds.Expr.prototype.canBeEmpty_ = false;
/**
* The parsed paths in the expression
*
* @type {Array.<string>}
* @private
*/
goog.ds.Expr.prototype.parts_ = [];
/**
* Number of paths in the expression
*
* @type {?number}
* @private
*/
goog.ds.Expr.prototype.size_ = null;
/**
* The root node path in the expression
*
* @type {string}
* @private
*/
goog.ds.Expr.prototype.root_;
/**
* The last path in the expression
*
* @type {?string}
* @private
*/
goog.ds.Expr.prototype.last_ = null;
/**
* Whether the expression evaluates to current node
*
* @type {boolean}
* @private
*/
goog.ds.Expr.prototype.isCurrent_ = false;
/**
* Whether the expression is just an attribute
*
* @type {boolean}
* @private
*/
goog.ds.Expr.prototype.isJustAttribute_ = false;
/**
* Does this expression select all DOM-style child nodes (element and text)
*
* @type {boolean}
* @private
*/
goog.ds.Expr.prototype.isAllChildNodes_ = false;
/**
* Does this expression select all DOM-style attribute nodes (starts with '@')
*
* @type {boolean}
* @private
*/
goog.ds.Expr.prototype.isAllAttributes_ = false;
/**
* Does this expression select all DOM-style element child nodes
*
* @type {boolean}
* @private
*/
goog.ds.Expr.prototype.isAllElements_ = false;
/**
* The function used by this expression
*
* @type {?string}
* @private
*/
goog.ds.Expr.prototype.exprFn_ = null;
/**
* Cached value for the parent expression.
* @type {goog.ds.Expr?}
* @private
*/
goog.ds.Expr.prototype.parentExpr_ = null;
/**
* Cached value for the next expression.
* @type {goog.ds.Expr?}
* @private
*/
goog.ds.Expr.prototype.nextExpr_ = null;
/**
* Create an expression from a string, can use cached values
*
* @param {string} expr The expression string.
* @return {goog.ds.Expr} The expression object.
*/
goog.ds.Expr.create = function(expr) {
var result = goog.ds.Expr.cache_[expr];
if (result == null) {
result = new goog.ds.Expr(expr);
goog.ds.Expr.cache_[expr] = result;
}
return result;
};
/**
* Create an expression from a string, can use cached values
* Uses hints from related expressions to help in creation
*
* @param {?string=} opt_expr The string expression source.
* @param {Array=} opt_parts Array of the parts of an expression.
* @param {goog.ds.Expr=} opt_childExpr Optional child of this expression,
* passed in as a hint for processing.
* @param {goog.ds.Expr=} opt_prevExpr Optional preceding expression
* (i.e. $A/B/C is previous expression to B/C) passed in as a hint for
* processing.
* @return {goog.ds.Expr} The expression object.
* @private
*/
goog.ds.Expr.createInternal_ = function(opt_expr, opt_parts, opt_childExpr,
opt_prevExpr) {
var expr = opt_expr || opt_parts.join('/');
var result = goog.ds.Expr.cache_[expr];
if (result == null) {
result = new goog.ds.Expr();
result.setSource_(expr, opt_parts, opt_childExpr, opt_prevExpr);
goog.ds.Expr.cache_[expr] = result;
}
return result;
};
/**
* Cache of pre-parsed expressions
* @private
*/
goog.ds.Expr.cache_ = {};
/**
* Commonly used strings in expressions.
* @enum {string}
* @private
*/
goog.ds.Expr.String_ = {
SEPARATOR: '/',
CURRENT_NODE_EXPR: '.',
EMPTY_EXPR: '',
ATTRIBUTE_START: '@',
ALL_CHILD_NODES_EXPR: '*|text()',
ALL_ATTRIBUTES_EXPR: '@*',
ALL_ELEMENTS_EXPR: '*',
NAME_EXPR: 'name()',
COUNT_EXPR: 'count()',
POSITION_EXPR: 'position()',
INDEX_START: '[',
INDEX_END: ']',
CAN_BE_EMPTY: '?'
};
/**
* Standard expressions
*/
/**
* The current node
*/
goog.ds.Expr.CURRENT = goog.ds.Expr.create(
goog.ds.Expr.String_.CURRENT_NODE_EXPR);
/**
* For DOM interop - all DOM child nodes (text + element).
* Text nodes have dataName #text
*/
goog.ds.Expr.ALL_CHILD_NODES =
goog.ds.Expr.create(goog.ds.Expr.String_.ALL_CHILD_NODES_EXPR);
/**
* For DOM interop - all DOM element child nodes
*/
goog.ds.Expr.ALL_ELEMENTS =
goog.ds.Expr.create(goog.ds.Expr.String_.ALL_ELEMENTS_EXPR);
/**
* For DOM interop - all DOM attribute nodes
* Attribute nodes have dataName starting with "@"
*/
goog.ds.Expr.ALL_ATTRIBUTES =
goog.ds.Expr.create(goog.ds.Expr.String_.ALL_ATTRIBUTES_EXPR);
/**
* Get the dataName of a node
*/
goog.ds.Expr.NAME = goog.ds.Expr.create(goog.ds.Expr.String_.NAME_EXPR);
/**
* Get the count of nodes matching an expression
*/
goog.ds.Expr.COUNT = goog.ds.Expr.create(goog.ds.Expr.String_.COUNT_EXPR);
/**
* Get the position of the "current" node in the current node list
* This will only apply for datasources that support the concept of a current
* node (none exist yet). This is similar to XPath position() and concept of
* current node
*/
goog.ds.Expr.POSITION = goog.ds.Expr.create(goog.ds.Expr.String_.POSITION_EXPR);

View File

@@ -0,0 +1,811 @@
// Copyright 2007 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
* Efficient implementation of DataNode API.
*
* The implementation consists of three concrete classes for modelling
* DataNodes with different characteristics: FastDataNode,
* FastPrimitiveDataNode and FastListNode.
*
* FastDataNode is for bean-like or map-like objects that consists of
* key/value mappings and where the primary access pattern is by key.
*
* FastPrimitiveDataNode wraps primitives like strings, boolean, and numbers.
*
* FastListNode is for array-like data nodes. It also supports key-based
* lookups if the data nodes have an "id" property or if child nodes are
* explicitly added by name. It is most efficient if these features are not
* used.
*
* FastDataNodes can be constructed from JSON-like objects via the function
* goog.ds.FastDataNode.fromJs.
*/
goog.provide('goog.ds.AbstractFastDataNode');
goog.provide('goog.ds.FastDataNode');
goog.provide('goog.ds.FastListNode');
goog.provide('goog.ds.PrimitiveFastDataNode');
goog.require('goog.ds.DataManager');
goog.require('goog.ds.EmptyNodeList');
goog.require('goog.string');
/*
* Implementation note: In order to reduce the number of objects,
* FastDataNode stores its key/value mappings directly in the FastDataNode
* object iself (instead of a separate map). To make this work we have to
* sure that there are no name clashes with other attribute names used by
* FastDataNode (like dataName and parent). This is especially difficult in
* the light of automatic renaming by the JavaScript compiler. For this reason,
* all internal attributes start with "__" so that they are not renamed
* by the compiler.
*/
/**
* Creates a new abstract data node.
* @param {string} dataName Name of the datanode.
* @param {goog.ds.DataNode=} opt_parent Parent of this data node.
* @constructor
* @extends {goog.ds.DataNodeList}
*/
// TODO(arv): Use interfaces when available.
goog.ds.AbstractFastDataNode = function(dataName, opt_parent) {
if (!dataName) {
throw Error('Cannot create a fast data node without a data name');
}
this['__dataName'] = dataName;
this['__parent'] = opt_parent;
};
/**
* Return the name of this data node.
* @return {string} Name of this data noden.
* @override
*/
goog.ds.AbstractFastDataNode.prototype.getDataName = function() {
return this['__dataName'];
};
/**
* Set the name of this data node.
* @param {string} value Name.
* @override
*/
goog.ds.AbstractFastDataNode.prototype.setDataName = function(value) {
this['__dataName'] = value;
};
/**
* Get the path leading to this data node.
* @return {string} Data path.
* @override
*/
goog.ds.AbstractFastDataNode.prototype.getDataPath = function() {
var parentPath;
if (this['__parent']) {
parentPath = this['__parent'].getDataPath() + goog.ds.STR_PATH_SEPARATOR;
} else {
parentPath = '';
}
return parentPath + this.getDataName();
};
/**
* Creates a new fast data node, using the properties of root.
* @param {Object} root JSON-like object to initialize data node from.
* @param {string} dataName Name of this data node.
* @param {goog.ds.DataNode=} opt_parent Parent of this data node.
* @extends {goog.ds.AbstractFastDataNode}
* @constructor
*/
goog.ds.FastDataNode = function(root, dataName, opt_parent) {
goog.ds.AbstractFastDataNode.call(this, dataName, opt_parent);
this.extendWith(root);
};
goog.inherits(goog.ds.FastDataNode, goog.ds.AbstractFastDataNode);
/**
* Add all attributes of object to this data node.
* @param {Object} object Object to add attributes from.
* @protected
*/
goog.ds.FastDataNode.prototype.extendWith = function(object) {
for (var key in object) {
this[key] = object[key];
}
};
/**
* Creates a new FastDataNode structure initialized from object. This will
* return an instance of the most suitable sub-class of FastDataNode.
*
* You should not modify object after creating a fast data node from it
* or assume that changing object changes the data node. Doing so results
* in undefined behaviour.
*
* @param {Object|number|boolean|string} object Object to initialize data
* node from.
* @param {string} dataName Name of data node.
* @param {goog.ds.DataNode=} opt_parent Parent of data node.
* @return {goog.ds.AbstractFastDataNode} Data node representing object.
*/
goog.ds.FastDataNode.fromJs = function(object, dataName, opt_parent) {
if (goog.isArray(object)) {
return new goog.ds.FastListNode(object, dataName, opt_parent);
} else if (goog.isObject(object)) {
return new goog.ds.FastDataNode(object, dataName, opt_parent);
} else {
return new goog.ds.PrimitiveFastDataNode(object || !!object,
dataName,
opt_parent);
}
};
/**
* Static instance of an empty list.
* @type {goog.ds.EmptyNodeList}
* @private
*/
goog.ds.FastDataNode.emptyList_ = new goog.ds.EmptyNodeList();
/**
* Not supported for normal FastDataNodes.
* @param {*} value Value to set data node to.
* @override
*/
goog.ds.FastDataNode.prototype.set = function(value) {
throw 'Not implemented yet';
};
/** @override */
goog.ds.FastDataNode.prototype.getChildNodes = function(opt_selector) {
if (!opt_selector || opt_selector == goog.ds.STR_ALL_CHILDREN_SELECTOR) {
return this;
} else if (opt_selector.indexOf(goog.ds.STR_WILDCARD) == -1) {
var child = this.getChildNode(opt_selector);
return child ? new goog.ds.FastListNode([child], '') :
new goog.ds.EmptyNodeList();
} else {
throw Error('Unsupported selector: ' + opt_selector);
}
};
/**
* Makes sure that a named child is wrapped in a data node structure.
* @param {string} name Name of child to wrap.
* @private
*/
goog.ds.FastDataNode.prototype.wrapChild_ = function(name) {
var child = this[name];
if (child != null && !child.getDataName) {
this[name] = goog.ds.FastDataNode.fromJs(this[name], name, this);
}
};
/**
* Get a child node by name.
* @param {string} name Name of child node.
* @param {boolean=} opt_create Whether to create the child if it does not
* exist.
* @return {goog.ds.DataNode} Child node.
* @override
*/
goog.ds.FastDataNode.prototype.getChildNode = function(name, opt_create) {
this.wrapChild_(name);
// this[name] always is a data node object, so using "||" is fine.
var child = this[name] || null;
if (child == null && opt_create) {
child = new goog.ds.FastDataNode({}, name, this);
this[name] = child;
}
return child;
};
/**
* Sets a child node. Creates the child if it does not exist.
*
* Calling this function makes any child nodes previously obtained for name
* invalid. You should not use these child nodes but instead obtain a new
* instance by calling getChildNode.
*
* @override
*/
goog.ds.FastDataNode.prototype.setChildNode = function(name, value) {
if (value != null) {
this[name] = value;
} else {
delete this[name];
}
goog.ds.DataManager.getInstance().fireDataChange(this.getDataPath() +
goog.ds.STR_PATH_SEPARATOR + name);
return null;
};
/**
* Returns the value of a child node. By using this method you can avoid
* the need to create PrimitiveFastData nodes.
* @param {string} name Name of child node.
* @return {Object} Value of child node.
* @override
*/
goog.ds.FastDataNode.prototype.getChildNodeValue = function(name) {
var child = this[name];
if (child != null) {
return (child.getDataName ? child.get() : child);
} else {
return null;
}
};
/**
* Returns whether this data node is a list. Always returns false for
* instances of FastDataNode but may return true for subclasses.
* @return {boolean} Whether this data node is array-like.
* @override
*/
goog.ds.FastDataNode.prototype.isList = function() {
return false;
};
/**
* Returns a javascript object representation of this data node. You should
* not modify the object returned by this function.
* @return {Object} Javascript object representation of this data node.
*/
goog.ds.FastDataNode.prototype.getJsObject = function() {
var result = {};
for (var key in this) {
if (!goog.string.startsWith(key, '__') && !goog.isFunction(this[key])) {
result[key] = (this[key]['__dataName'] ? this[key].getJsObject() :
this[key]);
}
}
return result;
};
/**
* Creates a deep copy of this data node.
* @return {goog.ds.FastDataNode} Clone of this data node.
*/
goog.ds.FastDataNode.prototype.clone = function() {
return /** @type {goog.ds.FastDataNode} */(goog.ds.FastDataNode.fromJs(
this.getJsObject(), this.getDataName()));
};
/*
* Implementation of goog.ds.DataNodeList for FastDataNode.
*/
/**
* Adds a child to this data node.
* @param {goog.ds.DataNode} value Child node to add.
* @override
*/
goog.ds.FastDataNode.prototype.add = function(value) {
this.setChildNode(value.getDataName(), value);
};
/**
* Gets the value of this data node (if called without opt_key) or
* gets a child node (if called with opt_key).
* @param {string=} opt_key Name of child node.
* @return {*} This data node or a child node.
* @override
*/
goog.ds.FastDataNode.prototype.get = function(opt_key) {
if (!goog.isDef(opt_key)) {
// if there is no key, DataNode#get was called
return this;
} else {
return this.getChildNode(opt_key);
}
};
/**
* Gets a child node by index. This method has a complexity of O(n) where
* n is the number of children. If you need a faster implementation of this
* method, you should use goog.ds.FastListNode.
* @param {number} index Index of child node (starting from 0).
* @return {goog.ds.DataNode} Child node at specified index.
* @override
*/
goog.ds.FastDataNode.prototype.getByIndex = function(index) {
var i = 0;
for (var key in this) {
if (!goog.string.startsWith(key, '__') && !goog.isFunction(this[key])) {
if (i == index) {
this.wrapChild_(key);
return this[key];
}
++i;
}
}
return null;
};
/**
* Gets the number of child nodes. This method has a complexity of O(n) where
* n is the number of children. If you need a faster implementation of this
* method, you should use goog.ds.FastListNode.
* @return {number} Number of child nodes.
* @override
*/
goog.ds.FastDataNode.prototype.getCount = function() {
var count = 0;
for (var key in this) {
if (!goog.string.startsWith(key, '__') && !goog.isFunction(this[key])) {
++count;
}
}
// maybe cache this?
return count;
};
/**
* Sets a child node.
* @param {string} name Name of child node.
* @param {Object} value Value of child node.
* @override
*/
goog.ds.FastDataNode.prototype.setNode = function(name, value) {
this.setChildNode(name, value);
};
/**
* Removes a child node.
* @override
*/
goog.ds.FastDataNode.prototype.removeNode = function(name) {
delete this[name];
return false;
};
/**
* Creates a new data node wrapping a primitive value.
* @param {number|boolean|string} value Value the value to wrap.
* @param {string} dataName name Name of this data node.
* @param {goog.ds.DataNode=} opt_parent Parent of this data node.
* @extends {goog.ds.AbstractFastDataNode}
* @constructor
*/
goog.ds.PrimitiveFastDataNode = function(value, dataName, opt_parent) {
this.value_ = value;
goog.ds.AbstractFastDataNode.call(this, dataName, opt_parent);
};
goog.inherits(goog.ds.PrimitiveFastDataNode, goog.ds.AbstractFastDataNode);
/**
* Returns the value of this data node.
* @return {(boolean|number|string)} Value of this data node.
* @override
*/
goog.ds.PrimitiveFastDataNode.prototype.get = function() {
return this.value_;
};
/**
* Sets this data node to a new value.
* @param {*} value Value to set data node to.
* @override
*/
goog.ds.PrimitiveFastDataNode.prototype.set = function(value) {
if (goog.isArray(value) || goog.isObject(value)) {
throw Error('can only set PrimitiveFastDataNode to primitive values');
}
this.value_ = value;
goog.ds.DataManager.getInstance().fireDataChange(this.getDataPath());
};
/**
* Returns child nodes of this data node. Always returns an unmodifiable,
* empty list.
* @return {goog.ds.DataNodeList} (Empty) list of child nodes.
* @override
*/
goog.ds.PrimitiveFastDataNode.prototype.getChildNodes = function() {
return goog.ds.FastDataNode.emptyList_;
};
/**
* Get a child node by name. Always returns null.
* @param {string} name Name of child node.
* @return {goog.ds.DataNode} Child node.
* @override
*/
goog.ds.PrimitiveFastDataNode.prototype.getChildNode = function(name) {
return null;
};
/**
* Returns the value of a child node. Always returns null.
* @param {string} name Name of child node.
* @return {Object} Value of child node.
* @override
*/
goog.ds.PrimitiveFastDataNode.prototype.getChildNodeValue = function(name) {
return null;
};
/**
* Not supported by primitive data nodes.
* @param {string} name Name of child node.
* @param {Object} value Value of child node.
* @override
*/
goog.ds.PrimitiveFastDataNode.prototype.setChildNode =
function(name, value) {
throw Error('Cannot set a child node for a PrimitiveFastDataNode');
};
/**
* Returns whether this data node is a list. Always returns false for
* instances of PrimitiveFastDataNode.
* @return {boolean} Whether this data node is array-like.
* @override
*/
goog.ds.PrimitiveFastDataNode.prototype.isList = function() {
return false;
};
/**
* Returns a javascript object representation of this data node. You should
* not modify the object returned by this function.
* @return {*} Javascript object representation of this data node.
*/
goog.ds.PrimitiveFastDataNode.prototype.getJsObject = function() {
return this.value_;
};
/**
* Creates a new list node from an array.
* @param {Array} values values hold by this list node.
* @param {string} dataName name of this node.
* @param {goog.ds.DataNode=} opt_parent parent of this node.
* @extends {goog.ds.AbstractFastDataNode}
* @constructor
*/
// TODO(arv): Use interfaces when available. This implements DataNodeList
// as well.
goog.ds.FastListNode = function(values, dataName, opt_parent) {
this.values_ = [];
for (var i = 0; i < values.length; ++i) {
var name = values[i].id || ('[' + i + ']');
this.values_.push(goog.ds.FastDataNode.fromJs(values[i], name, this));
if (values[i].id) {
if (!this.map_) {
this.map_ = {};
}
this.map_[values[i].id] = i;
}
}
goog.ds.AbstractFastDataNode.call(this, dataName, opt_parent);
};
goog.inherits(goog.ds.FastListNode, goog.ds.AbstractFastDataNode);
/**
* Not supported for FastListNodes.
* @param {*} value Value to set data node to.
* @override
*/
goog.ds.FastListNode.prototype.set = function(value) {
throw Error('Cannot set a FastListNode to a new value');
};
/**
* Returns child nodes of this data node. Currently, only supports
* returning all children.
* @return {goog.ds.DataNodeList} List of child nodes.
* @override
*/
goog.ds.FastListNode.prototype.getChildNodes = function() {
return this;
};
/**
* Get a child node by name.
* @param {string} key Name of child node.
* @param {boolean=} opt_create Whether to create the child if it does not
* exist.
* @return {goog.ds.DataNode} Child node.
* @override
*/
goog.ds.FastListNode.prototype.getChildNode = function(key, opt_create) {
var index = this.getKeyAsNumber_(key);
if (index == null && this.map_) {
index = this.map_[key];
}
if (index != null && this.values_[index]) {
return this.values_[index];
} else if (opt_create) {
this.setChildNode(key, {});
return this.getChildNode(key);
} else {
return null;
}
};
/**
* Returns the value of a child node.
* @param {string} key Name of child node.
* @return {*} Value of child node.
* @override
*/
goog.ds.FastListNode.prototype.getChildNodeValue = function(key) {
var child = this.getChildNode(key);
return (child ? child.get() : null);
};
/**
* Tries to interpret key as a numeric index enclosed by square brakcets.
* @param {string} key Key that should be interpreted as a number.
* @return {?number} Numeric index or null if key is not of the form
* described above.
* @private
*/
goog.ds.FastListNode.prototype.getKeyAsNumber_ = function(key) {
if (key.charAt(0) == '[' && key.charAt(key.length - 1) == ']') {
return Number(key.substring(1, key.length - 1));
} else {
return null;
}
};
/**
* Sets a child node. Creates the child if it does not exist. To set
* children at a certain index, use a key of the form '[index]'. Note, that
* you can only set values at existing numeric indices. To add a new node
* to this list, you have to use the add method.
*
* Calling this function makes any child nodes previously obtained for name
* invalid. You should not use these child nodes but instead obtain a new
* instance by calling getChildNode.
*
* @override
*/
goog.ds.FastListNode.prototype.setChildNode = function(key, value) {
var count = this.values_.length;
if (value != null) {
if (!value.getDataName) {
value = goog.ds.FastDataNode.fromJs(value, key, this);
}
var index = this.getKeyAsNumber_(key);
if (index != null) {
if (index < 0 || index >= this.values_.length) {
throw Error('List index out of bounds: ' + index);
}
this.values_[key] = value;
} else {
if (!this.map_) {
this.map_ = {};
}
this.values_.push(value);
this.map_[key] = this.values_.length - 1;
}
} else {
this.removeNode(key);
}
var dm = goog.ds.DataManager.getInstance();
dm.fireDataChange(this.getDataPath() + goog.ds.STR_PATH_SEPARATOR + key);
if (this.values_.length != count) {
this.listSizeChanged_();
}
return null;
};
/**
* Fire data changes that are appropriate when the size of this list changes.
* Should be called whenever the list size has changed.
* @private
*/
goog.ds.FastListNode.prototype.listSizeChanged_ = function() {
var dm = goog.ds.DataManager.getInstance();
dm.fireDataChange(this.getDataPath());
dm.fireDataChange(this.getDataPath() + goog.ds.STR_PATH_SEPARATOR +
'count()');
};
/**
* Returns whether this data node is a list. Always returns true.
* @return {boolean} Whether this data node is array-like.
* @override
*/
goog.ds.FastListNode.prototype.isList = function() {
return true;
};
/**
* Returns a javascript object representation of this data node. You should
* not modify the object returned by this function.
* @return {Object} Javascript object representation of this data node.
*/
goog.ds.FastListNode.prototype.getJsObject = function() {
var result = [];
for (var i = 0; i < this.values_.length; ++i) {
result.push(this.values_[i].getJsObject());
}
return result;
};
/*
* Implementation of goog.ds.DataNodeList for FastListNode.
*/
/**
* Adds a child to this data node
* @param {goog.ds.DataNode} value Child node to add.
* @override
*/
goog.ds.FastListNode.prototype.add = function(value) {
if (!value.getDataName) {
value = goog.ds.FastDataNode.fromJs(value,
String('[' + (this.values_.length) + ']'), this);
}
this.values_.push(value);
var dm = goog.ds.DataManager.getInstance();
dm.fireDataChange(this.getDataPath() + goog.ds.STR_PATH_SEPARATOR +
'[' + (this.values_.length - 1) + ']');
this.listSizeChanged_();
};
/**
* Gets the value of this data node (if called without opt_key) or
* gets a child node (if called with opt_key).
* @param {string=} opt_key Name of child node.
* @return {Array|goog.ds.DataNode} Array of child nodes (if called without
* opt_key), or a named child node otherwise.
* @override
*/
goog.ds.FastListNode.prototype.get = function(opt_key) {
// if there are no arguments, DataNode.get was called
if (!goog.isDef(opt_key)) {
return this.values_;
} else {
return this.getChildNode(opt_key);
}
};
/**
* Gets a child node by (numeric) index.
* @param {number} index Index of child node (starting from 0).
* @return {goog.ds.DataNode} Child node at specified index.
* @override
*/
goog.ds.FastListNode.prototype.getByIndex = function(index) {
var child = this.values_[index];
return (child != null ? child : null); // never return undefined
};
/**
* Gets the number of child nodes.
* @return {number} Number of child nodes.
* @override
*/
goog.ds.FastListNode.prototype.getCount = function() {
return this.values_.length;
};
/**
* Sets a child node.
* @param {string} name Name of child node.
* @param {Object} value Value of child node.
* @override
*/
goog.ds.FastListNode.prototype.setNode = function(name, value) {
throw Error('Setting child nodes of a FastListNode is not implemented, yet');
};
/**
* Removes a child node.
* @override
*/
goog.ds.FastListNode.prototype.removeNode = function(name) {
var index = this.getKeyAsNumber_(name);
if (index == null && this.map_) {
index = this.map_[name];
}
if (index != null) {
this.values_.splice(index, 1);
if (this.map_) {
var keyToDelete = null;
for (var key in this.map_) {
if (this.map_[key] == index) {
keyToDelete = key;
} else if (this.map_[key] > index) {
--this.map_[key];
}
}
if (keyToDelete) {
delete this.map_[keyToDelete];
}
}
var dm = goog.ds.DataManager.getInstance();
dm.fireDataChange(this.getDataPath() + goog.ds.STR_PATH_SEPARATOR +
'[' + index + ']');
this.listSizeChanged_();
}
return false;
};
/**
* Returns the index of a named child nodes. This method only works if
* this list uses mixed name/indexed lookup, i.e. if its child node have
* an 'id' attribute.
* @param {string} name Name of child node to determine index of.
* @return {number} Index of child node named name.
*/
goog.ds.FastListNode.prototype.indexOf = function(name) {
var index = this.getKeyAsNumber_(name);
if (index == null && this.map_) {
index = this.map_[name];
}
if (index == null) {
throw Error('Cannot determine index for: ' + name);
}
return /** @type {number} */(index);
};

View File

@@ -0,0 +1,460 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview An implementation of DataNode for wrapping JS data.
*
*/
goog.provide('goog.ds.JsDataSource');
goog.provide('goog.ds.JsPropertyDataSource');
goog.require('goog.ds.BaseDataNode');
goog.require('goog.ds.BasicNodeList');
goog.require('goog.ds.DataManager');
goog.require('goog.ds.EmptyNodeList');
goog.require('goog.ds.LoadState');
/**
* Data source whose backing is JavaScript data
*
* Names that are reserved for system use and shouldn't be used for data node
* names: eval, toSource, toString, unwatch, valueOf, watch. Behavior is
* undefined if these names are used.
*
* @param {Object} root The root JS node.
* @param {string} dataName The name of this node relative to the parent node.
* @param {Object=} opt_parent Optional parent of this JsDataSource.
*
* implements goog.ds.DataNode.
* @constructor
* @extends {goog.ds.DataNode}
*/
// TODO(arv): Use interfaces when available.
goog.ds.JsDataSource = function(root, dataName, opt_parent) {
this.parent_ = opt_parent;
this.dataName_ = dataName;
this.setRoot(root);
};
/**
* The root JS object. Can be null.
* @type {*}
* @protected
* @suppress {underscore}
*/
goog.ds.JsDataSource.prototype.root_;
/**
* Sets the root JS object
* @param {Object} root The root JS object. Can be null.
*
* @protected
*/
goog.ds.JsDataSource.prototype.setRoot = function(root) {
this.root_ = root;
this.childNodeList_ = null;
};
/**
* Set this data source to use list semantics. List data sources:
* - Are assumed to have child nodes of all of the same type of data
* - Fire data changes on the root node of the list whenever children
* are added or removed
* @param {?boolean} isList True to use list semantics.
* @private
*/
goog.ds.JsDataSource.prototype.setIsList_ = function(isList) {
this.isList_ = isList;
};
/** @override */
goog.ds.JsDataSource.prototype.get = function() {
return !goog.isObject(this.root_) ? this.root_ : this.getChildNodes();
};
/**
* Set the value of the node
* @param {*} value The new value of the node.
* @override
*/
goog.ds.JsDataSource.prototype.set = function(value) {
if (value && goog.isObject(this.root_)) {
throw Error('Can\'t set group nodes to new values yet');
}
if (this.parent_) {
this.parent_.root_[this.dataName_] = value;
}
this.root_ = value;
this.childNodeList_ = null;
goog.ds.DataManager.getInstance().fireDataChange(this.getDataPath());
};
/**
* TODO(user) revisit lazy creation.
* @override
*/
goog.ds.JsDataSource.prototype.getChildNodes = function(opt_selector) {
if (!this.root_) {
return new goog.ds.EmptyNodeList();
}
if (!opt_selector || opt_selector == goog.ds.STR_ALL_CHILDREN_SELECTOR) {
this.createChildNodes_(false);
return this.childNodeList_;
} else if (opt_selector.indexOf(goog.ds.STR_WILDCARD) == -1) {
if (this.root_[opt_selector] != null) {
return new goog.ds.BasicNodeList([this.getChildNode(opt_selector)]);
} else {
return new goog.ds.EmptyNodeList();
}
} else {
throw Error('Selector not supported yet (' + opt_selector + ')');
}
};
/**
* Creates the DataNodeList with the child nodes for this element.
* Allows for only building list as needed.
*
* @param {boolean=} opt_force Whether to force recreating child nodes,
* defaults to false.
* @private
*/
goog.ds.JsDataSource.prototype.createChildNodes_ = function(opt_force) {
if (this.childNodeList_ && !opt_force) {
return;
}
if (!goog.isObject(this.root_)) {
this.childNodeList_ = new goog.ds.EmptyNodeList();
return;
}
var childNodeList = new goog.ds.BasicNodeList();
var newNode;
if (goog.isArray(this.root_)) {
var len = this.root_.length;
for (var i = 0; i < len; i++) {
// "id" is reserved node name that will map to a named child node
// TODO(user) Configurable logic for choosing id node
var node = this.root_[i];
var id = node.id;
var name = id != null ? String(id) : '[' + i + ']';
newNode = new goog.ds.JsDataSource(node, name, this);
childNodeList.add(newNode);
}
} else {
for (var name in this.root_) {
var obj = this.root_[name];
// If the node is already a datasource, then add it.
if (obj.getDataName) {
childNodeList.add(obj);
} else if (!goog.isFunction(obj)) {
newNode = new goog.ds.JsDataSource(obj, name, this);
childNodeList.add(newNode);
}
}
}
this.childNodeList_ = childNodeList;
};
/**
* Gets a named child node of the current node
* @param {string} name The node name.
* @param {boolean=} opt_canCreate If true, can create child node.
* @return {goog.ds.DataNode} The child node, or null if no node of
* this name exists.
* @override
*/
goog.ds.JsDataSource.prototype.getChildNode = function(name, opt_canCreate) {
if (!this.root_) {
return null;
}
var node = /** @type {goog.ds.DataNode} */ (this.getChildNodes().get(name));
if (!node && opt_canCreate) {
var newObj = {};
if (goog.isArray(this.root_)) {
newObj['id'] = name;
this.root_.push(newObj);
} else {
this.root_[name] = newObj;
}
node = new goog.ds.JsDataSource(newObj, name, this);
if (this.childNodeList_) {
this.childNodeList_.add(node);
}
}
return node;
};
/**
* Gets the value of a child node
* @param {string} name The node name.
* @return {Object} The value of the node, or null if no value or the child
* node doesn't exist.
* @override
*/
goog.ds.JsDataSource.prototype.getChildNodeValue = function(name) {
if (this.childNodeList_) {
var node = this.getChildNodes().get(name);
return node ? node.get() : null;
} else if (this.root_) {
return this.root_[name];
} else {
return null;
}
};
/**
* Sets a named child node of the current node.
* If value is null, removes the child node.
* @param {string} name The node name.
* @param {Object} value The value to set, can be DataNode, object,
* property, or null.
* @return {Object} The child node, if set.
* @override
*/
goog.ds.JsDataSource.prototype.setChildNode = function(name, value) {
var removedPath = null;
var node = null;
var addedNode = false;
// Set node to the DataNode to add - if the value isn't already a DataNode,
// creates a JsDataSource or JsPropertyDataSource wrapper
if (value != null) {
if (value.getDataName) {
// The value is a DataNode. We must update its parent.
node = value;
node.parent_ = this;
} else {
if (goog.isArray(value) || goog.isObject(value)) {
node = new goog.ds.JsDataSource(value, name, this);
} else {
node = new goog.ds.JsPropertyDataSource(
/** @type {goog.ds.DataNode} */ (this.root_), name, this);
}
}
}
// This logic will get cleaner once we can remove the backing array / object
// and just rely on the childNodeList_. This is needed until dependent code
// is cleaned up.
// TODO(user) Remove backing array / object and just use childNodeList_
if (goog.isArray(this.root_)) {
// To remove by name, need to create a map of the child nodes by ID
this.createChildNodes_();
var index = this.childNodeList_.indexOf(name);
if (value == null) {
// Remove the node
var nodeToRemove = this.childNodeList_.get(name);
if (nodeToRemove) {
removedPath = nodeToRemove.getDataPath();
}
this.root_.splice(index, 1);
} else {
// Add the node
if (index) {
this.root_[index] = value;
} else {
this.root_.push(value);
}
}
if (index == null) {
addedNode = true;
}
this.childNodeList_.setNode(name, /** @type {goog.ds.DataNode} */ (node));
} else if (goog.isObject(this.root_)) {
if (value == null) {
// Remove the node
this.createChildNodes_();
var nodeToRemove = this.childNodeList_.get(name);
if (nodeToRemove) {
removedPath = nodeToRemove.getDataPath();
}
delete this.root_[name];
} else {
// Add the node
if (!this.root_[name]) {
addedNode = true;
}
this.root_[name] = value;
}
// Only need to update childNodeList_ if has been created already
if (this.childNodeList_) {
this.childNodeList_.setNode(name, /** @type {goog.ds.DataNode} */ (node));
}
}
// Fire the event that the node changed
var dm = goog.ds.DataManager.getInstance();
if (node) {
dm.fireDataChange(node.getDataPath());
if (addedNode && this.isList()) {
dm.fireDataChange(this.getDataPath());
dm.fireDataChange(this.getDataPath() + '/count()');
}
} else if (removedPath) {
dm.fireDataChange(removedPath);
if (this.isList()) {
dm.fireDataChange(this.getDataPath());
dm.fireDataChange(this.getDataPath() + '/count()');
}
}
return node;
};
/**
* Get the name of the node relative to the parent node
* @return {string} The name of the node.
* @override
*/
goog.ds.JsDataSource.prototype.getDataName = function() {
return this.dataName_;
};
/**
* Setthe name of the node relative to the parent node
* @param {string} dataName The name of the node.
* @override
*/
goog.ds.JsDataSource.prototype.setDataName = function(dataName) {
this.dataName_ = dataName;
};
/**
* Gets the a qualified data path to this node
* @return {string} The data path.
* @override
*/
goog.ds.JsDataSource.prototype.getDataPath = function() {
var parentPath = '';
if (this.parent_) {
parentPath = this.parent_.getDataPath() + goog.ds.STR_PATH_SEPARATOR;
}
return parentPath + this.dataName_;
};
/**
* Load or reload the backing data for this node
* @override
*/
goog.ds.JsDataSource.prototype.load = function() {
// Nothing to do
};
/**
* Gets the state of the backing data for this node
* TODO(user) Discuss null value handling
* @return {goog.ds.LoadState} The state.
* @override
*/
goog.ds.JsDataSource.prototype.getLoadState = function() {
return (this.root_ == null) ? goog.ds.LoadState.NOT_LOADED :
goog.ds.LoadState.LOADED;
};
/**
* Whether the value of this node is a homogeneous list of data
* @return {boolean} True if a list.
* @override
*/
goog.ds.JsDataSource.prototype.isList = function() {
return this.isList_ != null ? this.isList_ : goog.isArray(this.root_);
};
/**
* Data source for JavaScript properties that arent objects. Contains reference
* to parent object so that you can set the vaule
*
* @param {goog.ds.DataNode} parent Parent object.
* @param {string} dataName Name of this property.
* @param {goog.ds.DataNode=} opt_parentDataNode The parent data node. If
* omitted, assumes that the parent object is the parent data node.
*
* @constructor
* @extends {goog.ds.BaseDataNode}
*/
goog.ds.JsPropertyDataSource = function(parent, dataName, opt_parentDataNode) {
goog.ds.BaseDataNode.call(this);
this.dataName_ = dataName;
this.parent_ = parent;
this.parentDataNode_ = opt_parentDataNode || this.parent_;
};
goog.inherits(goog.ds.JsPropertyDataSource, goog.ds.BaseDataNode);
/**
* Get the value of the node
* @return {Object} The value of the node, or null if no value.
*/
goog.ds.JsPropertyDataSource.prototype.get = function() {
return this.parent_[this.dataName_];
};
/**
* Set the value of the node
* @param {Object} value The new value of the node.
* @override
*/
goog.ds.JsPropertyDataSource.prototype.set = function(value) {
var oldValue = this.parent_[this.dataName_];
this.parent_[this.dataName_] = value;
if (oldValue != value) {
goog.ds.DataManager.getInstance().fireDataChange(this.getDataPath());
}
};
/**
* Get the name of the node relative to the parent node
* @return {string} The name of the node.
* @override
*/
goog.ds.JsPropertyDataSource.prototype.getDataName = function() {
return this.dataName_;
};
/** @override */
goog.ds.JsPropertyDataSource.prototype.getParent = function() {
return this.parentDataNode_;
};

View File

@@ -0,0 +1,148 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Implementation of DataNode for wrapping JSON data.
*
*/
goog.provide('goog.ds.JsonDataSource');
goog.require('goog.Uri');
goog.require('goog.dom');
goog.require('goog.ds.DataManager');
goog.require('goog.ds.JsDataSource');
goog.require('goog.ds.LoadState');
goog.require('goog.ds.logger');
/**
* Data source whose backing is a JSON-like service, in which
* retreiving the resource specified by URL with the additional parameter
* callback. The resource retreived is executable JavaScript that
* makes a call to the named function with a JavaScript object literal
* as the only parameter.
*
* Example URI could be:
* http://www.google.com/data/search?q=monkey&callback=mycb
* which might return the JS:
* mycb({searchresults:
* [{uri: 'http://www.monkey.com', title: 'Site About Monkeys'}]});
*
* TODO(user): Evaluate using goog.net.Jsonp here.
*
* A URI of an empty string will mean that no request is made
* and the data source will be a data source with no child nodes
*
* @param {string|goog.Uri} uri URI for the request.
* @param {string} name Name of the datasource.
* @param {string=} opt_callbackParamName The parameter name that is used to
* specify the callback. Defaults to 'callback'.
*
* @extends {goog.ds.JsDataSource}
* @constructor
*/
goog.ds.JsonDataSource = function(uri, name, opt_callbackParamName) {
goog.ds.JsDataSource.call(this, null, name, null);
if (uri) {
this.uri_ = new goog.Uri(uri);
} else {
this.uri_ = null;
}
/**
* This is the callback parameter name that is added to the uri.
* @type {string}
* @private
*/
this.callbackParamName_ = opt_callbackParamName || 'callback';
};
goog.inherits(goog.ds.JsonDataSource, goog.ds.JsDataSource);
/**
* Default load state is NOT_LOADED
* @private
*/
goog.ds.JsonDataSource.prototype.loadState_ = goog.ds.LoadState.NOT_LOADED;
/**
* Map of all data sources, needed for callbacks
* Doesn't work unless dataSources is exported (not renamed)
*/
goog.ds.JsonDataSource['dataSources'] = {};
/**
* Load or reload the backing data for this node.
* Fires the JsonDataSource
* @override
*/
goog.ds.JsonDataSource.prototype.load = function() {
if (this.uri_) {
// NOTE: "dataSources" is expose above by name so that it will not be
// renamed. It should therefore be accessed via array notation here so
// that it also doesn't get renamed and stops the compiler from complaining
goog.ds.JsonDataSource['dataSources'][this.dataName_] = this;
goog.log.info(goog.ds.logger, 'Sending JS request for DataSource ' +
this.getDataName() + ' to ' + this.uri_);
this.loadState_ = goog.ds.LoadState.LOADING;
var uriToCall = new goog.Uri(this.uri_);
uriToCall.setParameterValue(this.callbackParamName_,
'JsonReceive.' + this.dataName_);
goog.global['JsonReceive'][this.dataName_] =
goog.bind(this.receiveData, this);
var scriptEl = goog.dom.createDom('script', {'src': uriToCall});
goog.dom.getElementsByTagNameAndClass('head')[0].appendChild(scriptEl);
} else {
this.root_ = {};
this.loadState_ = goog.ds.LoadState.NOT_LOADED;
}
};
/**
* Gets the state of the backing data for this node
* @return {goog.ds.LoadState} The state.
* @override
*/
goog.ds.JsonDataSource.prototype.getLoadState = function() {
return this.loadState_;
};
/**
* Receives data from a Json request
* @param {Object} obj The JSON data.
*/
goog.ds.JsonDataSource.prototype.receiveData = function(obj) {
this.setRoot(obj);
this.loadState_ = goog.ds.LoadState.LOADED;
goog.ds.DataManager.getInstance().fireDataChange(this.getDataName());
};
/**
* Temp variable to hold callbacks
* until BUILD supports multiple externs.js files
*/
goog.global['JsonReceive'] = {};

View File

@@ -0,0 +1,195 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview
* DataSource implementation that uses XMLHttpRequest as transport, with
* response as serialized JS object (not required to be JSON) that can
* be evaluated and set to a variable.
*
* Response can have unexecutable starting/ending text to prevent inclusion
* using <script src="...">
*
*/
goog.provide('goog.ds.JsXmlHttpDataSource');
goog.require('goog.Uri');
goog.require('goog.ds.DataManager');
goog.require('goog.ds.FastDataNode');
goog.require('goog.ds.LoadState');
goog.require('goog.ds.logger');
goog.require('goog.events');
goog.require('goog.log');
goog.require('goog.net.EventType');
goog.require('goog.net.XhrIo');
/**
* Similar to JsonDataSource, with using XMLHttpRequest for transport
* Currently requires the result be a JS object that can be evaluated and
* set to a variable and doesn't require strict JSON notation.
*
* @param {string || goog.Uri} uri URI for the request.
* @param {string} name Name of the datasource.
* @param {string=} opt_startText Text to expect/strip before JS response.
* @param {string=} opt_endText Text to expect/strip after JS response.
* @param {boolean=} opt_usePost If true, use POST. Defaults to false (GET).
*
* @extends {goog.ds.FastDataNode}
* @constructor
*/
goog.ds.JsXmlHttpDataSource = function(uri, name, opt_startText, opt_endText,
opt_usePost) {
goog.ds.FastDataNode.call(this, {}, name, null);
if (uri) {
this.uri_ = new goog.Uri(uri);
this.xhr_ = new goog.net.XhrIo();
this.usePost_ = !!opt_usePost;
goog.events.listen(this.xhr_, goog.net.EventType.COMPLETE,
this.completed_, false, this);
} else {
this.uri_ = null;
}
this.startText_ = opt_startText;
this.endText_ = opt_endText;
};
goog.inherits(goog.ds.JsXmlHttpDataSource, goog.ds.FastDataNode);
/**
* Delimiter for start of JSON data in response.
* null = starts at first character of response
* @type {string|undefined}
* @private
*/
goog.ds.JsXmlHttpDataSource.prototype.startText_;
/**
* Delimiter for end of JSON data in response.
* null = ends at last character of response
* @type {string|undefined}
* @private
*/
goog.ds.JsXmlHttpDataSource.prototype.endText_;
/**
* Gets the state of the backing data for this node
* @return {goog.ds.LoadState} The state.
* @override
*/
goog.ds.JsXmlHttpDataSource.prototype.getLoadState = function() {
return this.loadState_;
};
/**
* Sets the request data. This can be used if it is required to
* send a specific body rather than build the body from the query
* parameters. Only used in POST requests.
* @param {string} data The data to send in the request body.
*/
goog.ds.JsXmlHttpDataSource.prototype.setQueryData = function(data) {
this.queryData_ = data;
};
/**
* Load or reload the backing data for this node.
* Fires the JsonDataSource
* @override
*/
goog.ds.JsXmlHttpDataSource.prototype.load = function() {
goog.log.info(goog.ds.logger, 'Sending JS request for DataSource ' +
this.getDataName() + ' to ' + this.uri_);
if (this.uri_) {
if (this.usePost_) {
var queryData;
if (!this.queryData_) {
queryData = this.uri_.getQueryData().toString();
} else {
queryData = this.queryData_;
}
var uriNoQuery = this.uri_.clone();
uriNoQuery.setQueryData(null);
this.xhr_.send(String(uriNoQuery), 'POST', queryData);
} else {
this.xhr_.send(String(this.uri_));
}
} else {
this.loadState_ = goog.ds.LoadState.NOT_LOADED;
}
};
/**
* Called on successful request.
* @private
*/
goog.ds.JsXmlHttpDataSource.prototype.success_ = function() {
goog.ds.DataManager.getInstance().fireDataChange(this.getDataName());
};
/**
* Completed callback. Loads data if successful, otherwise sets
* state to FAILED
* @param {goog.events.Event} e Event object, Xhr is target.
* @private
*/
goog.ds.JsXmlHttpDataSource.prototype.completed_ = function(e) {
if (this.xhr_.isSuccess()) {
goog.log.info(goog.ds.logger,
'Got data for DataSource ' + this.getDataName());
var text = this.xhr_.getResponseText();
// Look for start and end token and trim text
if (this.startText_) {
var startpos = text.indexOf(this.startText_);
text = text.substring(startpos + this.startText_.length);
}
if (this.endText_) {
var endpos = text.lastIndexOf(this.endText_);
text = text.substring(0, endpos);
}
// Eval result
/** @preserveTry */
try {
var jsonObj = /** @type {Object} */ (eval('[' + text + '][0]'));
this.extendWith(jsonObj);
this.loadState_ = goog.ds.LoadState.LOADED;
}
catch (ex) {
// Invalid JS
this.loadState_ = goog.ds.LoadState.FAILED;
goog.log.error(goog.ds.logger, 'Failed to parse data: ' + ex.message);
}
// Call on a timer to avoid threading issues on IE.
goog.global.setTimeout(goog.bind(this.success_, this), 0);
} else {
goog.log.info(goog.ds.logger, 'Data retrieve failed for DataSource ' +
this.getDataName());
this.loadState_ = goog.ds.LoadState.FAILED;
}
};

View File

@@ -0,0 +1,415 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview
* Implementations of DataNode for wrapping XML data.
*
*/
goog.provide('goog.ds.XmlDataSource');
goog.provide('goog.ds.XmlHttpDataSource');
goog.require('goog.Uri');
goog.require('goog.dom.NodeType');
goog.require('goog.dom.xml');
goog.require('goog.ds.BasicNodeList');
goog.require('goog.ds.DataManager');
goog.require('goog.ds.LoadState');
goog.require('goog.ds.logger');
goog.require('goog.net.XhrIo');
goog.require('goog.string');
/**
* Data source whose backing is an xml node
*
* @param {Node} node The XML node. Can be null.
* @param {goog.ds.XmlDataSource} parent Parent of XML element. Can be null.
* @param {string=} opt_name The name of this node relative to the parent node.
*
* @extends {goog.ds.DataNode}
* @constructor
*/
// TODO(arv): Use interfaces when available.
goog.ds.XmlDataSource = function(node, parent, opt_name) {
this.parent_ = parent;
this.dataName_ = opt_name || (node ? node.nodeName : '');
this.setNode_(node);
};
/**
* Constant to select XML attributes for getChildNodes
* @type {string}
* @private
*/
goog.ds.XmlDataSource.ATTRIBUTE_SELECTOR_ = '@*';
/**
* Set the current root nodeof the data source.
* Can be an attribute node, text node, or element node
* @param {Node} node The node. Can be null.
*
* @private
*/
goog.ds.XmlDataSource.prototype.setNode_ = function(node) {
this.node_ = node;
if (node != null) {
switch (node.nodeType) {
case goog.dom.NodeType.ATTRIBUTE:
case goog.dom.NodeType.TEXT:
this.value_ = node.nodeValue;
break;
case goog.dom.NodeType.ELEMENT:
if (node.childNodes.length == 1 &&
node.firstChild.nodeType == goog.dom.NodeType.TEXT) {
this.value_ = node.firstChild.nodeValue;
}
}
}
};
/**
* Creates the DataNodeList with the child nodes for this element.
* Allows for only building list as needed.
*
* @private
*/
goog.ds.XmlDataSource.prototype.createChildNodes_ = function() {
if (this.childNodeList_) {
return;
}
var childNodeList = new goog.ds.BasicNodeList();
if (this.node_ != null) {
var childNodes = this.node_.childNodes;
for (var i = 0, childNode; childNode = childNodes[i]; i++) {
if (childNode.nodeType != goog.dom.NodeType.TEXT ||
!goog.ds.XmlDataSource.isEmptyTextNodeValue_(childNode.nodeValue)) {
var newNode = new goog.ds.XmlDataSource(childNode,
this, childNode.nodeName);
childNodeList.add(newNode);
}
}
}
this.childNodeList_ = childNodeList;
};
/**
* Creates the DataNodeList with the attributes for the element
* Allows for only building list as needed.
*
* @private
*/
goog.ds.XmlDataSource.prototype.createAttributes_ = function() {
if (this.attributes_) {
return;
}
var attributes = new goog.ds.BasicNodeList();
if (this.node_ != null && this.node_.attributes != null) {
var atts = this.node_.attributes;
for (var i = 0, att; att = atts[i]; i++) {
var newNode = new goog.ds.XmlDataSource(att, this, att.nodeName);
attributes.add(newNode);
}
}
this.attributes_ = attributes;
};
/**
* Get the value of the node
* @return {Object} The value of the node, or null if no value.
* @override
*/
goog.ds.XmlDataSource.prototype.get = function() {
this.createChildNodes_();
return this.value_;
};
/**
* Set the value of the node
* @param {*} value The new value of the node.
* @override
*/
goog.ds.XmlDataSource.prototype.set = function(value) {
throw Error('Can\'t set on XmlDataSource yet');
};
/** @override */
goog.ds.XmlDataSource.prototype.getChildNodes = function(opt_selector) {
if (opt_selector && opt_selector ==
goog.ds.XmlDataSource.ATTRIBUTE_SELECTOR_) {
this.createAttributes_();
return this.attributes_;
} else if (opt_selector == null ||
opt_selector == goog.ds.STR_ALL_CHILDREN_SELECTOR) {
this.createChildNodes_();
return this.childNodeList_;
} else {
throw Error('Unsupported selector');
}
};
/**
* Gets a named child node of the current node
* @param {string} name The node name.
* @return {goog.ds.DataNode} The child node, or null if
* no node of this name exists.
* @override
*/
goog.ds.XmlDataSource.prototype.getChildNode = function(name) {
if (goog.string.startsWith(name, goog.ds.STR_ATTRIBUTE_START)) {
var att = this.node_.getAttributeNode(name.substring(1));
return att ? new goog.ds.XmlDataSource(att, this) : null;
} else {
return /** @type {goog.ds.DataNode} */ (this.getChildNodes().get(name));
}
};
/**
* Gets the value of a child node
* @param {string} name The node name.
* @return {*} The value of the node, or null if no value or the child node
* doesn't exist.
* @override
*/
goog.ds.XmlDataSource.prototype.getChildNodeValue = function(name) {
if (goog.string.startsWith(name, goog.ds.STR_ATTRIBUTE_START)) {
var node = this.node_.getAttributeNode(name.substring(1));
return node ? node.nodeValue : null;
} else {
var node = this.getChildNode(name);
return node ? node.get() : null;
}
};
/**
* Get the name of the node relative to the parent node
* @return {string} The name of the node.
* @override
*/
goog.ds.XmlDataSource.prototype.getDataName = function() {
return this.dataName_;
};
/**
* Setthe name of the node relative to the parent node
* @param {string} name The name of the node.
* @override
*/
goog.ds.XmlDataSource.prototype.setDataName = function(name) {
this.dataName_ = name;
};
/**
* Gets the a qualified data path to this node
* @return {string} The data path.
* @override
*/
goog.ds.XmlDataSource.prototype.getDataPath = function() {
var parentPath = '';
if (this.parent_) {
parentPath = this.parent_.getDataPath() +
(this.dataName_.indexOf(goog.ds.STR_ARRAY_START) != -1 ? '' :
goog.ds.STR_PATH_SEPARATOR);
}
return parentPath + this.dataName_;
};
/**
* Load or reload the backing data for this node
* @override
*/
goog.ds.XmlDataSource.prototype.load = function() {
// Nothing to do
};
/**
* Gets the state of the backing data for this node
* @return {goog.ds.LoadState} The state.
* @override
*/
goog.ds.XmlDataSource.prototype.getLoadState = function() {
return this.node_ ? goog.ds.LoadState.LOADED : goog.ds.LoadState.NOT_LOADED;
};
/**
* Check whether a node is an empty text node. Nodes consisting of only white
* space (#x20, #xD, #xA, #x9) can generally be collapsed to a zero length
* text string.
* @param {string} str String to match.
* @return {boolean} True if string equates to empty text node.
* @private
*/
goog.ds.XmlDataSource.isEmptyTextNodeValue_ = function(str) {
return /^[\r\n\t ]*$/.test(str);
};
/**
* Creates an XML document with one empty node.
* Useful for places where you need a node that
* can be queried against.
*
* @return {Document} Document with one empty node.
* @private
*/
goog.ds.XmlDataSource.createChildlessDocument_ = function() {
return goog.dom.xml.createDocument('nothing');
};
/**
* Data source whose backing is an XMLHttpRequest,
*
* A URI of an empty string will mean that no request is made
* and the data source will be a single, empty node.
*
* @param {(string,goog.Uri)} uri URL of the XMLHttpRequest.
* @param {string} name Name of the datasource.
*
* implements goog.ds.XmlHttpDataSource.
* @constructor
* @extends {goog.ds.XmlDataSource}
*/
goog.ds.XmlHttpDataSource = function(uri, name) {
goog.ds.XmlDataSource.call(this, null, null, name);
if (uri) {
this.uri_ = new goog.Uri(uri);
} else {
this.uri_ = null;
}
};
goog.inherits(goog.ds.XmlHttpDataSource, goog.ds.XmlDataSource);
/**
* Default load state is NOT_LOADED
* @private
*/
goog.ds.XmlHttpDataSource.prototype.loadState_ = goog.ds.LoadState.NOT_LOADED;
/**
* Load or reload the backing data for this node.
* Fires the XMLHttpRequest
* @override
*/
goog.ds.XmlHttpDataSource.prototype.load = function() {
if (this.uri_) {
goog.log.info(goog.ds.logger, 'Sending XML request for DataSource ' +
this.getDataName() + ' to ' + this.uri_);
this.loadState_ = goog.ds.LoadState.LOADING;
goog.net.XhrIo.send(this.uri_, goog.bind(this.complete_, this));
} else {
this.node_ = goog.ds.XmlDataSource.createChildlessDocument_();
this.loadState_ = goog.ds.LoadState.NOT_LOADED;
}
};
/**
* Gets the state of the backing data for this node
* @return {goog.ds.LoadState} The state.
* @override
*/
goog.ds.XmlHttpDataSource.prototype.getLoadState = function() {
return this.loadState_;
};
/**
* Handles the completion of an XhrIo request. Dispatches to success or load
* based on the result.
* @param {!goog.events.Event} e The XhrIo event object.
* @private
*/
goog.ds.XmlHttpDataSource.prototype.complete_ = function(e) {
var xhr = /** @type {goog.net.XhrIo} */ (e.target);
if (xhr && xhr.isSuccess()) {
this.success_(xhr);
} else {
this.failure_();
}
};
/**
* Success result. Checks whether valid XML was returned
* and sets the XML and loadstate.
*
* @param {!goog.net.XhrIo} xhr The successful XhrIo object.
* @private
*/
goog.ds.XmlHttpDataSource.prototype.success_ = function(xhr) {
goog.log.info(goog.ds.logger,
'Got data for DataSource ' + this.getDataName());
var xml = xhr.getResponseXml();
// Fix for case where IE returns valid XML as text but
// doesn't parse by default
if (xml && !xml.hasChildNodes() &&
goog.isObject(xhr.getResponseText())) {
xml = goog.dom.xml.loadXml(xhr.getResponseText());
}
// Failure result
if (!xml || !xml.hasChildNodes()) {
this.loadState_ = goog.ds.LoadState.FAILED;
this.node_ = goog.ds.XmlDataSource.createChildlessDocument_();
} else {
this.loadState_ = goog.ds.LoadState.LOADED;
this.node_ = xml.documentElement;
}
if (this.getDataName()) {
goog.ds.DataManager.getInstance().fireDataChange(this.getDataName());
}
};
/**
* Failure result
*
* @private
*/
goog.ds.XmlHttpDataSource.prototype.failure_ = function() {
goog.log.info(goog.ds.logger, 'Data retrieve failed for DataSource ' +
this.getDataName());
this.loadState_ = goog.ds.LoadState.FAILED;
this.node_ = goog.ds.XmlDataSource.createChildlessDocument_();
if (this.getDataName()) {
goog.ds.DataManager.getInstance().fireDataChange(this.getDataName());
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
// 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 Typedefs for working with dates.
*
* @author nicksantos@google.com (Nick Santos)
*/
goog.provide('goog.date.DateLike');
/**
* @typedef {(Date|goog.date.Date)}
*/
goog.date.DateLike;

View File

@@ -0,0 +1,413 @@
// Copyright 2008 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 Date range data structure. Based loosely on
* com.google.common.util.DateRange.
*
*/
goog.provide('goog.date.DateRange');
goog.provide('goog.date.DateRange.Iterator');
goog.provide('goog.date.DateRange.StandardDateRangeKeys');
goog.require('goog.date.Date');
goog.require('goog.date.Interval');
goog.require('goog.iter.Iterator');
goog.require('goog.iter.StopIteration');
/**
* Constructs a date range.
* @constructor
* @param {goog.date.Date} startDate The first date in the range.
* @param {goog.date.Date} endDate The last date in the range.
*/
goog.date.DateRange = function(startDate, endDate) {
/**
* The first date in the range.
* @type {goog.date.Date}
* @private
*/
this.startDate_ = startDate;
/**
* The last date in the range.
* @type {goog.date.Date}
* @private
*/
this.endDate_ = endDate;
};
/**
* The first possible day, as far as this class is concerned.
* @type {goog.date.Date}
*/
goog.date.DateRange.MINIMUM_DATE = new goog.date.Date(0000, 0, 1);
/**
* The last possible day, as far as this class is concerned.
* @type {goog.date.Date}
*/
goog.date.DateRange.MAXIMUM_DATE = new goog.date.Date(9999, 11, 31);
/**
* @return {goog.date.Date} The first date in the range.
*/
goog.date.DateRange.prototype.getStartDate = function() {
return this.startDate_;
};
/**
* @return {goog.date.Date} The last date in the range.
*/
goog.date.DateRange.prototype.getEndDate = function() {
return this.endDate_;
};
/**
* @return {goog.iter.Iterator} An iterator over the date range.
*/
goog.date.DateRange.prototype.iterator = function() {
return new goog.date.DateRange.Iterator(this);
};
/**
* Tests two {@link goog.date.DateRange} objects for equality.
* @param {goog.date.DateRange} a A date range.
* @param {goog.date.DateRange} b A date range.
* @return {boolean} Whether |a| is the same range as |b|.
*/
goog.date.DateRange.equals = function(a, b) {
// Test for same object reference; type conversion is irrelevant.
if (a === b) {
return true;
}
if (a == null || b == null) {
return false;
}
return a.startDate_.equals(b.startDate_) && a.endDate_.equals(b.endDate_);
};
/**
* Calculates a date that is a number of days after a date. Does not modify its
* input.
* @param {goog.date.Date} date The input date.
* @param {number} offset Number of days.
* @return {goog.date.Date} The date that is |offset| days after |date|.
* @private
*/
goog.date.DateRange.offsetInDays_ = function(date, offset) {
var newDate = date.clone();
newDate.add(new goog.date.Interval(goog.date.Interval.DAYS, offset));
return newDate;
};
/**
* Calculates the Monday before a date. If the input is a Monday, returns the
* input. Does not modify its input.
* @param {goog.date.Date} date The input date.
* @return {goog.date.Date} If |date| is a Monday, return |date|; otherwise
* return the Monday before |date|.
* @private
*/
goog.date.DateRange.currentOrLastMonday_ = function(date) {
var newDate = date.clone();
newDate.add(new goog.date.Interval(goog.date.Interval.DAYS,
-newDate.getIsoWeekday()));
return newDate;
};
/**
* Calculates a date that is a number of months after the first day in the
* month that contains its input. Does not modify its input.
* @param {goog.date.Date} date The input date.
* @param {number} offset Number of months.
* @return {goog.date.Date} The date that is |offset| months after the first
* day in the month that contains |date|.
* @private
*/
goog.date.DateRange.offsetInMonths_ = function(date, offset) {
var newDate = date.clone();
newDate.setDate(1);
newDate.add(new goog.date.Interval(goog.date.Interval.MONTHS, offset));
return newDate;
};
/**
* Returns the range from yesterday to yesterday.
* @param {goog.date.Date=} opt_today The date to consider today.
* Defaults to today.
* @return {goog.date.DateRange} The range that includes only yesterday.
*/
goog.date.DateRange.yesterday = function(opt_today) {
var today = goog.date.DateRange.cloneOrCreate_(opt_today);
var yesterday = goog.date.DateRange.offsetInDays_(today, -1);
return new goog.date.DateRange(yesterday, yesterday);
};
/**
* Returns the range from today to today.
* @param {goog.date.Date=} opt_today The date to consider today.
* Defaults to today.
* @return {goog.date.DateRange} The range that includes only today.
*/
goog.date.DateRange.today = function(opt_today) {
var today = goog.date.DateRange.cloneOrCreate_(opt_today);
return new goog.date.DateRange(today, today);
};
/**
* Returns the range that includes the seven days that end yesterday.
* @param {goog.date.Date=} opt_today The date to consider today.
* Defaults to today.
* @return {goog.date.DateRange} The range that includes the seven days that
* end yesterday.
*/
goog.date.DateRange.last7Days = function(opt_today) {
var today = goog.date.DateRange.cloneOrCreate_(opt_today);
var yesterday = goog.date.DateRange.offsetInDays_(today, -1);
return new goog.date.DateRange(goog.date.DateRange.offsetInDays_(today, -7),
yesterday);
};
/**
* Returns the range that starts the first of this month and ends the last day
* of this month.
* @param {goog.date.Date=} opt_today The date to consider today.
* Defaults to today.
* @return {goog.date.DateRange} The range that starts the first of this month
* and ends the last day of this month.
*/
goog.date.DateRange.thisMonth = function(opt_today) {
var today = goog.date.DateRange.cloneOrCreate_(opt_today);
return new goog.date.DateRange(
goog.date.DateRange.offsetInMonths_(today, 0),
goog.date.DateRange.offsetInDays_(
goog.date.DateRange.offsetInMonths_(today, 1),
-1));
};
/**
* Returns the range that starts the first of last month and ends the last day
* of last month.
* @param {goog.date.Date=} opt_today The date to consider today.
* Defaults to today.
* @return {goog.date.DateRange} The range that starts the first of last month
* and ends the last day of last month.
*/
goog.date.DateRange.lastMonth = function(opt_today) {
var today = goog.date.DateRange.cloneOrCreate_(opt_today);
return new goog.date.DateRange(
goog.date.DateRange.offsetInMonths_(today, -1),
goog.date.DateRange.offsetInDays_(
goog.date.DateRange.offsetInMonths_(today, 0),
-1));
};
/**
* Returns the seven-day range that starts on the first day of the week
* (see {@link goog.i18n.DateTimeSymbols.FIRSTDAYOFWEEK}) on or before today.
* @param {goog.date.Date=} opt_today The date to consider today.
* Defaults to today.
* @return {goog.date.DateRange} The range that starts the Monday on or before
* today and ends the Sunday on or after today.
*/
goog.date.DateRange.thisWeek = function(opt_today) {
var today = goog.date.DateRange.cloneOrCreate_(opt_today);
var iso = today.getIsoWeekday();
var firstDay = today.getFirstDayOfWeek();
var i18nFirstDay = (iso >= firstDay) ? iso - firstDay : iso + (7 - firstDay);
var start = goog.date.DateRange.offsetInDays_(today, -i18nFirstDay);
var end = goog.date.DateRange.offsetInDays_(start, 6);
return new goog.date.DateRange(start, end);
};
/**
* Returns the seven-day range that ends the day before the first day of
* the week (see {@link goog.i18n.DateTimeSymbols.FIRSTDAYOFWEEK}) that
* contains today.
* @param {goog.date.Date=} opt_today The date to consider today.
* Defaults to today.
* @return {goog.date.DateRange} The range that starts seven days before the
* Monday on or before today and ends the Sunday on or before yesterday.
*/
goog.date.DateRange.lastWeek = function(opt_today) {
var thisWeek = goog.date.DateRange.thisWeek(opt_today);
var start = goog.date.DateRange.offsetInDays_(thisWeek.getStartDate(), -7);
var end = goog.date.DateRange.offsetInDays_(thisWeek.getEndDate(), -7);
return new goog.date.DateRange(start, end);
};
/**
* Returns the range that starts seven days before the Monday on or before
* today and ends the Friday before today.
* @param {goog.date.Date=} opt_today The date to consider today.
* Defaults to today.
* @return {goog.date.DateRange} The range that starts seven days before the
* Monday on or before today and ends the Friday before today.
*/
goog.date.DateRange.lastBusinessWeek = function(opt_today) {
// TODO(user): should be i18nized.
var today = goog.date.DateRange.cloneOrCreate_(opt_today);
var start = goog.date.DateRange.offsetInDays_(today,
- 7 - today.getIsoWeekday());
var end = goog.date.DateRange.offsetInDays_(start, 4);
return new goog.date.DateRange(start, end);
};
/**
* Returns the range that includes all days between January 1, 1900 and
* December 31, 9999.
* @param {goog.date.Date=} opt_today The date to consider today.
* Defaults to today.
* @return {goog.date.DateRange} The range that includes all days between
* January 1, 1900 and December 31, 9999.
*/
goog.date.DateRange.allTime = function(opt_today) {
return new goog.date.DateRange(
goog.date.DateRange.MINIMUM_DATE,
goog.date.DateRange.MAXIMUM_DATE);
};
/**
* Standard date range keys. Equivalent to the enum IDs in
* DateRange.java http://go/datarange.java
*
* @enum {string}
*/
goog.date.DateRange.StandardDateRangeKeys = {
YESTERDAY: 'yesterday',
TODAY: 'today',
LAST_7_DAYS: 'last7days',
THIS_MONTH: 'thismonth',
LAST_MONTH: 'lastmonth',
THIS_WEEK: 'thisweek',
LAST_WEEK: 'lastweek',
LAST_BUSINESS_WEEK: 'lastbusinessweek',
ALL_TIME: 'alltime'
};
/**
* @param {string} dateRangeKey A standard date range key.
* @param {goog.date.Date=} opt_today The date to consider today.
* Defaults to today.
* @return {goog.date.DateRange} The date range that corresponds to that key.
* @throws {Error} If no standard date range with that key exists.
*/
goog.date.DateRange.standardDateRange = function(dateRangeKey, opt_today) {
switch (dateRangeKey) {
case goog.date.DateRange.StandardDateRangeKeys.YESTERDAY:
return goog.date.DateRange.yesterday(opt_today);
case goog.date.DateRange.StandardDateRangeKeys.TODAY:
return goog.date.DateRange.today(opt_today);
case goog.date.DateRange.StandardDateRangeKeys.LAST_7_DAYS:
return goog.date.DateRange.last7Days(opt_today);
case goog.date.DateRange.StandardDateRangeKeys.THIS_MONTH:
return goog.date.DateRange.thisMonth(opt_today);
case goog.date.DateRange.StandardDateRangeKeys.LAST_MONTH:
return goog.date.DateRange.lastMonth(opt_today);
case goog.date.DateRange.StandardDateRangeKeys.THIS_WEEK:
return goog.date.DateRange.thisWeek(opt_today);
case goog.date.DateRange.StandardDateRangeKeys.LAST_WEEK:
return goog.date.DateRange.lastWeek(opt_today);
case goog.date.DateRange.StandardDateRangeKeys.LAST_BUSINESS_WEEK:
return goog.date.DateRange.lastBusinessWeek(opt_today);
case goog.date.DateRange.StandardDateRangeKeys.ALL_TIME:
return goog.date.DateRange.allTime(opt_today);
default:
throw Error('no such date range key: ' + dateRangeKey);
}
};
/**
* Clones or creates new.
* @param {goog.date.Date=} opt_today The date to consider today.
* Defaults to today.
* @return {!goog.date.Date} cloned or new.
* @private
*/
goog.date.DateRange.cloneOrCreate_ = function(opt_today) {
return opt_today ? opt_today.clone() : new goog.date.Date();
};
/**
* Creates an iterator over the dates in a {@link goog.date.DateRange}.
* @constructor
* @extends {goog.iter.Iterator}
* @param {goog.date.DateRange} dateRange The date range to iterate.
*/
goog.date.DateRange.Iterator = function(dateRange) {
/**
* The next date.
* @type {goog.date.Date}
* @private
*/
this.nextDate_ = dateRange.getStartDate().clone();
/**
* The end date, expressed as an integer: YYYYMMDD.
* @type {number}
* @private
*/
this.endDate_ = Number(dateRange.getEndDate().toIsoString());
};
goog.inherits(goog.date.DateRange.Iterator, goog.iter.Iterator);
/** @override */
goog.date.DateRange.Iterator.prototype.next = function() {
if (Number(this.nextDate_.toIsoString()) > this.endDate_) {
throw goog.iter.StopIteration;
}
var rv = this.nextDate_.clone();
this.nextDate_.add(new goog.date.Interval(goog.date.Interval.DAYS, 1));
return rv;
};

View File

@@ -0,0 +1,465 @@
// Copyright 2009 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 Functions for formatting relative dates. Such as "3 days ago"
* "3 hours ago", "14 minutes ago", "12 days ago", "Today", "Yesterday".
*
*/
goog.provide('goog.date.relative');
goog.require('goog.i18n.DateTimeFormat');
/**
* Number of milliseconds in a minute.
* @type {number}
* @private
*/
goog.date.relative.MINUTE_MS_ = 60000;
/**
* Number of milliseconds in a day.
* @type {number}
* @private
*/
goog.date.relative.DAY_MS_ = 86400000;
/**
* Enumeration used to identify time units internally.
* @enum {number}
* @private
*/
goog.date.relative.Unit_ = {
MINUTES: 0,
HOURS: 1,
DAYS: 2
};
/**
* Full date formatter.
* @type {goog.i18n.DateTimeFormat}
* @private
*/
goog.date.relative.fullDateFormatter_;
/**
* Short time formatter.
* @type {goog.i18n.DateTimeFormat}
* @private
*/
goog.date.relative.shortTimeFormatter_;
/**
* Month-date formatter.
* @type {goog.i18n.DateTimeFormat}
* @private
*/
goog.date.relative.monthDateFormatter_;
/**
* Returns a date in month format, e.g. Mar 15.
* @param {Date} date The date object.
* @return {string} The formatted string.
* @private
*/
goog.date.relative.formatMonth_ = function(date) {
if (!goog.date.relative.monthDateFormatter_) {
goog.date.relative.monthDateFormatter_ =
new goog.i18n.DateTimeFormat('MMM dd');
}
return goog.date.relative.monthDateFormatter_.format(date);
};
/**
* Returns a date in short-time format, e.g. 2:50 PM.
* @param {Date|goog.date.DateTime} date The date object.
* @return {string} The formatted string.
* @private
*/
goog.date.relative.formatShortTime_ = function(date) {
if (!goog.date.relative.shortTimeFormatter_) {
goog.date.relative.shortTimeFormatter_ = new goog.i18n.DateTimeFormat(
goog.i18n.DateTimeFormat.Format.SHORT_TIME);
}
return goog.date.relative.shortTimeFormatter_.format(date);
};
/**
* Returns a date in full date format, e.g. Tuesday, March 24, 2009.
* @param {Date|goog.date.DateTime} date The date object.
* @return {string} The formatted string.
* @private
*/
goog.date.relative.formatFullDate_ = function(date) {
if (!goog.date.relative.fullDateFormatter_) {
goog.date.relative.fullDateFormatter_ = new goog.i18n.DateTimeFormat(
goog.i18n.DateTimeFormat.Format.FULL_DATE);
}
return goog.date.relative.fullDateFormatter_.format(date);
};
/**
* Accepts a timestamp in milliseconds and outputs a relative time in the form
* of "1 hour ago", "1 day ago", "in 1 hour", "in 2 days" etc. If the date
* delta is over 2 weeks, then the output string will be empty.
* @param {number} dateMs Date in milliseconds.
* @return {string} The formatted date.
*/
goog.date.relative.format = function(dateMs) {
var now = goog.now();
var delta = Math.floor((now - dateMs) / goog.date.relative.MINUTE_MS_);
var future = false;
if (delta < 0) {
future = true;
delta *= -1;
}
if (delta < 60) { // Minutes.
return goog.date.relative.getMessage_(
delta, future, goog.date.relative.Unit_.MINUTES);
} else {
delta = Math.floor(delta / 60);
if (delta < 24) { // Hours.
return goog.date.relative.getMessage_(
delta, future, goog.date.relative.Unit_.HOURS);
} else {
// We can be more than 24 hours apart but still only 1 day apart, so we
// compare the closest time from today against the target time to find
// the number of days in the delta.
var midnight = new Date(goog.now());
midnight.setHours(0);
midnight.setMinutes(0);
midnight.setSeconds(0);
midnight.setMilliseconds(0);
// Convert to days ago.
delta = Math.ceil(
(midnight.getTime() - dateMs) / goog.date.relative.DAY_MS_);
if (future) {
delta *= -1;
}
// Uses days for less than 2-weeks.
if (delta < 14) {
return goog.date.relative.getMessage_(
delta, future, goog.date.relative.Unit_.DAYS);
} else {
// For messages older than 2 weeks do not show anything. The client
// should decide the date format to show.
return '';
}
}
}
};
/**
* Accepts a timestamp in milliseconds and outputs a relative time in the form
* of "1 hour ago", "1 day ago". All future times will be returned as 0 minutes
* ago.
*
* This is provided for compatibility with users of the previous incarnation of
* the above {@see #format} method who relied on it protecting against
* future dates.
*
* @param {number} dateMs Date in milliseconds.
* @return {string} The formatted date.
*/
goog.date.relative.formatPast = function(dateMs) {
var now = goog.now();
if (now < dateMs) {
dateMs = now;
}
return goog.date.relative.format(dateMs);
};
/**
* Accepts a timestamp in milliseconds and outputs a relative day. i.e. "Today",
* "Yesterday", "Tomorrow", or "Sept 15".
*
* @param {number} dateMs Date in milliseconds.
* @param {function(!Date):string=} opt_formatter Formatter for the date.
* Defaults to form 'MMM dd'.
* @return {string} The formatted date.
*/
goog.date.relative.formatDay = function(dateMs, opt_formatter) {
var today = new Date(goog.now());
today.setHours(0);
today.setMinutes(0);
today.setSeconds(0);
today.setMilliseconds(0);
var yesterday = new Date(today.getTime() - goog.date.relative.DAY_MS_);
var tomorrow = new Date(today.getTime() + goog.date.relative.DAY_MS_);
var dayAfterTomorrow = new Date(today.getTime() +
2 * goog.date.relative.DAY_MS_);
var message;
if (dateMs >= tomorrow.getTime() && dateMs < dayAfterTomorrow.getTime()) {
/** @desc Tomorrow. */
var MSG_TOMORROW = goog.getMsg('Tomorrow');
message = MSG_TOMORROW;
} else if (dateMs >= today.getTime() && dateMs < tomorrow.getTime()) {
/** @desc Today. */
var MSG_TODAY = goog.getMsg('Today');
message = MSG_TODAY;
} else if (dateMs >= yesterday.getTime() && dateMs < today.getTime()) {
/** @desc Yesterday. */
var MSG_YESTERDAY = goog.getMsg('Yesterday');
message = MSG_YESTERDAY;
} else {
// If we don't have a special relative term for this date, then return the
// short date format (or a custom-formatted date).
var formatFunction = opt_formatter || goog.date.relative.formatMonth_;
message = formatFunction(new Date(dateMs));
}
return message;
};
/**
* Formats a date, adding the relative date in parenthesis. If the date is less
* than 24 hours then the time will be printed, otherwise the full-date will be
* used. Examples:
* 2:20 PM (1 minute ago)
* Monday, February 27, 2009 (4 days ago)
* Tuesday, March 20, 2005 // Too long ago for a relative date.
*
* @param {Date|goog.date.DateTime} date A date object.
* @param {string=} opt_shortTimeMsg An optional short time message can be
* provided if available, so that it's not recalculated in this function.
* @param {string=} opt_fullDateMsg An optional date message can be
* provided if available, so that it's not recalculated in this function.
* @return {string} The date string in the above form.
*/
goog.date.relative.getDateString = function(
date, opt_shortTimeMsg, opt_fullDateMsg) {
return goog.date.relative.getDateString_(
date, goog.date.relative.format, opt_shortTimeMsg, opt_fullDateMsg);
};
/**
* Formats a date, adding the relative date in parenthesis. Functions the same
* as #getDateString but ensures that the date is always seen to be in the past.
* If the date is in the future, it will be shown as 0 minutes ago.
*
* This is provided for compatibility with users of the previous incarnation of
* the above {@see #getDateString} method who relied on it protecting against
* future dates.
*
* @param {Date|goog.date.DateTime} date A date object.
* @param {string=} opt_shortTimeMsg An optional short time message can be
* provided if available, so that it's not recalculated in this function.
* @param {string=} opt_fullDateMsg An optional date message can be
* provided if available, so that it's not recalculated in this function.
* @return {string} The date string in the above form.
*/
goog.date.relative.getPastDateString = function(
date, opt_shortTimeMsg, opt_fullDateMsg) {
return goog.date.relative.getDateString_(
date, goog.date.relative.formatPast, opt_shortTimeMsg, opt_fullDateMsg);
};
/**
* Formats a date, adding the relative date in parenthesis. If the date is less
* than 24 hours then the time will be printed, otherwise the full-date will be
* used. Examples:
* 2:20 PM (1 minute ago)
* Monday, February 27, 2009 (4 days ago)
* Tuesday, March 20, 2005 // Too long ago for a relative date.
*
* @param {Date|goog.date.DateTime} date A date object.
* @param {function(number) : string} relativeFormatter Function to use when
* formatting the relative date.
* @param {string=} opt_shortTimeMsg An optional short time message can be
* provided if available, so that it's not recalculated in this function.
* @param {string=} opt_fullDateMsg An optional date message can be
* provided if available, so that it's not recalculated in this function.
* @return {string} The date string in the above form.
* @private
*/
goog.date.relative.getDateString_ = function(
date, relativeFormatter, opt_shortTimeMsg, opt_fullDateMsg) {
var dateMs = date.getTime();
var relativeDate = relativeFormatter(dateMs);
if (relativeDate) {
relativeDate = ' (' + relativeDate + ')';
}
var delta = Math.floor((goog.now() - dateMs) / goog.date.relative.MINUTE_MS_);
if (delta < 60 * 24) {
// TODO(user): this call raises an exception if date is a goog.date.Date.
return (opt_shortTimeMsg || goog.date.relative.formatShortTime_(date)) +
relativeDate;
} else {
return (opt_fullDateMsg || goog.date.relative.formatFullDate_(date)) +
relativeDate;
}
};
/*
* TODO(user):
*
* I think that this whole relative formatting should move to DateTimeFormat,
* make sure it treats plurals properly (now it does not), an so on.
* But we would have to wait for the next version of CLDR, which is cleaning
* the data for relative dates (even ICU has incomplete support for this).
*
* It also looks like this is not an object, and it does not save
* DateTimeSymbols at the time of creation, but uses the current value,
* whatever that is. So if one changes the global goog.i18n.DateTimeSymbols
* in between calls, then we get different results.
*/
/**
* Gets a localized relative date string for a given delta and unit.
* @param {number} delta Number of minutes/hours/days.
* @param {boolean} future Whether the delta is in the future.
* @param {goog.date.relative.Unit_} unit The units the delta is in.
* @return {string} The message.
* @private
*/
goog.date.relative.getMessage_ = function(delta, future, unit) {
// Convert to localized (native) digits
var localizedDelta =
goog.i18n.DateTimeFormat.prototype.localizeNumbers('' + delta);
if (!future && unit == goog.date.relative.Unit_.MINUTES) {
/**
* @desc Relative date indicating how many minutes ago something happened
* (singular).
*/
var MSG_MINUTES_AGO_SINGULAR =
goog.getMsg('{$num} minute ago', {'num' : localizedDelta});
/**
* @desc Relative date indicating how many minutes ago something happened
* (plural).
*/
var MSG_MINUTES_AGO_PLURAL =
goog.getMsg('{$num} minutes ago', {'num' : localizedDelta});
return delta == 1 ? MSG_MINUTES_AGO_SINGULAR : MSG_MINUTES_AGO_PLURAL;
} else if (future && unit == goog.date.relative.Unit_.MINUTES) {
/**
* @desc Relative date indicating in how many minutes something happens
* (singular).
*/
var MSG_IN_MINUTES_SINGULAR =
goog.getMsg('in {$num} minute', {'num' : localizedDelta});
/**
* @desc Relative date indicating in how many minutes something happens
* (plural).
*/
var MSG_IN_MINUTES_PLURAL =
goog.getMsg('in {$num} minutes', {'num' : localizedDelta});
return delta == 1 ? MSG_IN_MINUTES_SINGULAR : MSG_IN_MINUTES_PLURAL;
} else if (!future && unit == goog.date.relative.Unit_.HOURS) {
/**
* @desc Relative date indicating how many hours ago something happened
* (singular).
*/
var MSG_HOURS_AGO_SINGULAR =
goog.getMsg('{$num} hour ago', {'num' : localizedDelta});
/**
* @desc Relative date indicating how many hours ago something happened
* (plural).
*/
var MSG_HOURS_AGO_PLURAL =
goog.getMsg('{$num} hours ago', {'num' : localizedDelta});
return delta == 1 ? MSG_HOURS_AGO_SINGULAR : MSG_HOURS_AGO_PLURAL;
} else if (future && unit == goog.date.relative.Unit_.HOURS) {
/**
* @desc Relative date indicating in how many hours something happens
* (singular).
*/
var MSG_IN_HOURS_SINGULAR =
goog.getMsg('in {$num} hour', {'num' : localizedDelta});
/**
* @desc Relative date indicating in how many hours something happens
* (plural).
*/
var MSG_IN_HOURS_PLURAL =
goog.getMsg('in {$num} hours', {'num' : localizedDelta});
return delta == 1 ? MSG_IN_HOURS_SINGULAR : MSG_IN_HOURS_PLURAL;
} else if (!future && unit == goog.date.relative.Unit_.DAYS) {
/**
* @desc Relative date indicating how many days ago something happened
* (singular).
*/
var MSG_DAYS_AGO_SINGULAR =
goog.getMsg('{$num} day ago', {'num' : localizedDelta});
/**
* @desc Relative date indicating how many days ago something happened
* (plural).
*/
var MSG_DAYS_AGO_PLURAL =
goog.getMsg('{$num} days ago', {'num' : localizedDelta});
return delta == 1 ? MSG_DAYS_AGO_SINGULAR : MSG_DAYS_AGO_PLURAL;
} else if (future && unit == goog.date.relative.Unit_.DAYS) {
/**
* @desc Relative date indicating in how many days something happens
* (singular).
*/
var MSG_IN_DAYS_SINGULAR =
goog.getMsg('in {$num} day', {'num' : localizedDelta});
/**
* @desc Relative date indicating in how many days something happens
* (plural).
*/
var MSG_IN_DAYS_PLURAL =
goog.getMsg('in {$num} days', {'num' : localizedDelta});
return delta == 1 ? MSG_IN_DAYS_SINGULAR : MSG_IN_DAYS_PLURAL;
} else {
return '';
}
};

View File

@@ -0,0 +1,179 @@
// Copyright 2009 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 Locale independent date/time class.
*
*/
goog.provide('goog.date.UtcDateTime');
goog.require('goog.date');
goog.require('goog.date.Date');
goog.require('goog.date.DateTime');
goog.require('goog.date.Interval');
/**
* Class representing a date/time in GMT+0 time zone, without daylight saving.
* Defaults to current date and time if none is specified. The get... and the
* getUTC... methods are equivalent.
*
* @param {number|Object=} opt_year Four digit UTC year or a date-like object.
* If not set, the created object will contain the date determined by
* goog.now().
* @param {number=} opt_month UTC month, 0 = Jan, 11 = Dec.
* @param {number=} opt_date UTC date of month, 1 - 31.
* @param {number=} opt_hours UTC hours, 0 - 23.
* @param {number=} opt_minutes UTC minutes, 0 - 59.
* @param {number=} opt_seconds UTC seconds, 0 - 59.
* @param {number=} opt_milliseconds UTC milliseconds, 0 - 999.
* @constructor
* @extends {goog.date.DateTime}
*/
goog.date.UtcDateTime = function(opt_year, opt_month, opt_date, opt_hours,
opt_minutes, opt_seconds, opt_milliseconds) {
var timestamp;
if (goog.isNumber(opt_year)) {
timestamp = Date.UTC(opt_year, opt_month || 0, opt_date || 1,
opt_hours || 0, opt_minutes || 0, opt_seconds || 0,
opt_milliseconds || 0);
} else {
timestamp = opt_year ? opt_year.getTime() : goog.now();
}
this.date_ = new Date(timestamp);
};
goog.inherits(goog.date.UtcDateTime, goog.date.DateTime);
/**
* Creates a DateTime from a UTC datetime string expressed in ISO 8601 format.
*
* @param {string} formatted A date or datetime expressed in ISO 8601 format.
* @return {goog.date.UtcDateTime} Parsed date or null if parse fails.
*/
goog.date.UtcDateTime.fromIsoString = function(formatted) {
var ret = new goog.date.UtcDateTime(2000);
return goog.date.setIso8601DateTime(ret, formatted) ? ret : null;
};
/**
* Clones the UtcDateTime object.
*
* @return {!goog.date.UtcDateTime} A clone of the datetime object.
* @override
*/
goog.date.UtcDateTime.prototype.clone = function() {
var date = new goog.date.UtcDateTime(this.date_);
date.setFirstDayOfWeek(this.getFirstDayOfWeek());
date.setFirstWeekCutOffDay(this.getFirstWeekCutOffDay());
return date;
};
/** @override */
goog.date.UtcDateTime.prototype.add = function(interval) {
if (interval.years || interval.months) {
var yearsMonths = new goog.date.Interval(interval.years, interval.months);
goog.date.Date.prototype.add.call(this, yearsMonths);
}
var daysAndTimeMillis = 1000 * (
interval.seconds + 60 * (
interval.minutes + 60 * (
interval.hours + 24 * interval.days)));
this.date_ = new Date(this.date_.getTime() + daysAndTimeMillis);
};
/** @override */
goog.date.UtcDateTime.prototype.getTimezoneOffset = function() {
return 0;
};
/** @override */
goog.date.UtcDateTime.prototype.getFullYear =
goog.date.DateTime.prototype.getUTCFullYear;
/** @override */
goog.date.UtcDateTime.prototype.getMonth =
goog.date.DateTime.prototype.getUTCMonth;
/** @override */
goog.date.UtcDateTime.prototype.getDate =
goog.date.DateTime.prototype.getUTCDate;
/** @override */
goog.date.UtcDateTime.prototype.getHours =
goog.date.DateTime.prototype.getUTCHours;
/** @override */
goog.date.UtcDateTime.prototype.getMinutes =
goog.date.DateTime.prototype.getUTCMinutes;
/** @override */
goog.date.UtcDateTime.prototype.getSeconds =
goog.date.DateTime.prototype.getUTCSeconds;
/** @override */
goog.date.UtcDateTime.prototype.getMilliseconds =
goog.date.DateTime.prototype.getUTCMilliseconds;
/** @override */
goog.date.UtcDateTime.prototype.getDay =
goog.date.DateTime.prototype.getUTCDay;
/** @override */
goog.date.UtcDateTime.prototype.setFullYear =
goog.date.DateTime.prototype.setUTCFullYear;
/** @override */
goog.date.UtcDateTime.prototype.setMonth =
goog.date.DateTime.prototype.setUTCMonth;
/** @override */
goog.date.UtcDateTime.prototype.setDate =
goog.date.DateTime.prototype.setUTCDate;
/** @override */
goog.date.UtcDateTime.prototype.setHours =
goog.date.DateTime.prototype.setUTCHours;
/** @override */
goog.date.UtcDateTime.prototype.setMinutes =
goog.date.DateTime.prototype.setUTCMinutes;
/** @override */
goog.date.UtcDateTime.prototype.setSeconds =
goog.date.DateTime.prototype.setUTCSeconds;
/** @override */
goog.date.UtcDateTime.prototype.setMilliseconds =
goog.date.DateTime.prototype.setUTCMilliseconds;

View File

@@ -0,0 +1,214 @@
// Copyright 2012 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 Wrapper for a IndexedDB cursor.
*
*/
goog.provide('goog.db.Cursor');
goog.require('goog.async.Deferred');
goog.require('goog.db.Error');
goog.require('goog.debug');
goog.require('goog.events.EventTarget');
/**
* Creates a new IDBCursor wrapper object. Should not be created directly,
* access cursor through object store.
* @see goog.db.ObjectStore#openCursor
*
* @constructor
* @extends {goog.events.EventTarget}
*/
goog.db.Cursor = function() {
goog.base(this);
};
goog.inherits(goog.db.Cursor, goog.events.EventTarget);
/**
* Underlying IndexedDB cursor object.
*
* @type {IDBCursor}
* @private
*/
goog.db.Cursor.prototype.cursor_ = null;
/**
* Advances the cursor to the next position along its direction. When new data
* is available, the NEW_DATA event will be fired. If the cursor has reached the
* end of the range it will fire the COMPLETE event. If opt_key is specified it
* will advance to the key it matches in its direction.
*
* This wraps the native #continue method on the underlying object.
*
* @param {IDBKeyType=} opt_key The optional key to advance to.
*/
goog.db.Cursor.prototype.next = function(opt_key) {
if (opt_key) {
this.cursor_['continue'](opt_key);
} else {
this.cursor_['continue']();
}
};
/**
* Updates the value at the current position of the cursor in the object store.
* If the cursor points to a value that has just been deleted, a new value is
* created.
*
* @param {*} value The value to be stored.
* @return {!goog.async.Deferred} The resulting deferred request.
*/
goog.db.Cursor.prototype.update = function(value) {
var msg = 'updating via cursor with value ';
var d = new goog.async.Deferred();
var request;
try {
request = this.cursor_.update(value);
} catch (err) {
msg += goog.debug.deepExpose(value);
d.errback(goog.db.Error.fromException(err, msg));
return d;
}
request.onsuccess = function(ev) {
d.callback();
};
request.onerror = function(ev) {
msg += goog.debug.deepExpose(value);
d.errback(goog.db.Error.fromRequest(ev.target, msg));
};
return d;
};
/**
* Deletes the value at the cursor's position, without changing the cursor's
* position. Once the value is deleted, the cursor's value is set to null.
*
* @return {!goog.async.Deferred} The resulting deferred request.
*/
goog.db.Cursor.prototype.remove = function() {
var msg = 'deleting via cursor';
var d = new goog.async.Deferred();
var request;
try {
request = this.cursor_['delete']();
} catch (err) {
d.errback(goog.db.Error.fromException(err, msg));
return d;
}
request.onsuccess = function(ev) {
d.callback();
};
request.onerror = function(ev) {
d.errback(goog.db.Error.fromRequest(ev.target, msg));
};
return d;
};
/**
* @return {*} The value for the value at the cursor's position. Undefined
* if no current value, or null if value has just been deleted.
*/
goog.db.Cursor.prototype.getValue = function() {
return this.cursor_['value'];
};
/**
* @return {IDBKeyType} The key for the value at the cursor's position. If
* the cursor is outside its range, this is undefined.
*/
goog.db.Cursor.prototype.getKey = function() {
return this.cursor_.key;
};
/**
* Opens a value cursor from IDBObjectStore or IDBIndex over the specified key
* range. Returns a cursor object which is able to iterate over the given range.
* @param {!(IDBObjectStore|IDBIndex)} source Data source to open cursor.
* @param {!goog.db.KeyRange=} opt_range The key range. If undefined iterates
* over the whole data source.
* @param {!goog.db.Cursor.Direction=} opt_direction The direction. If undefined
* moves in a forward direction with duplicates.
* @return {!goog.db.Cursor} The cursor.
* @throws {goog.db.Error} If there was a problem opening the cursor.
*/
goog.db.Cursor.openCursor = function(source, opt_range, opt_direction) {
var cursor = new goog.db.Cursor();
var request;
try {
var range = opt_range ? opt_range.range() : null;
if (opt_direction) {
request = source.openCursor(range, opt_direction);
} else {
request = source.openCursor(range);
}
} catch (ex) {
cursor.dispose();
throw goog.db.Error.fromException(ex, source.name);
}
request.onsuccess = function(e) {
cursor.cursor_ = e.target.result || null;
if (cursor.cursor_) {
cursor.dispatchEvent(goog.db.Cursor.EventType.NEW_DATA);
} else {
cursor.dispatchEvent(goog.db.Cursor.EventType.COMPLETE);
}
};
request.onerror = function(e) {
cursor.dispatchEvent(goog.db.Cursor.EventType.ERROR);
};
return cursor;
};
/**
* Possible cursor directions.
* @see http://www.w3.org/TR/IndexedDB/#idl-def-IDBCursor
*
* @enum {string}
*/
goog.db.Cursor.Direction = {
NEXT: 'next',
NEXT_NO_DUPLICATE: 'nextunique',
PREV: 'prev',
PREV_NO_DUPLICATE: 'prevunique'
};
/**
* Event types that the cursor can dispatch. COMPLETE events are dispatched when
* a cursor is depleted of values, a NEW_DATA event if there is new data
* available, and ERROR if an error occurred.
*
* @enum {string}
*/
goog.db.Cursor.EventType = {
COMPLETE: 'c',
ERROR: 'e',
NEW_DATA: 'n'
};

View File

@@ -0,0 +1,182 @@
// Copyright 2011 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 Wrappers for the HTML5 IndexedDB. The wrappers export nearly
* the same interface as the standard API, but return goog.async.Deferred
* objects instead of request objects and use Closure events. The wrapper works
* and has been tested on Chrome version 22+. It may work on older Chrome
* versions, but they aren't explicitly supported.
*
* Example usage:
*
* <code>
* goog.db.openDatabase('mydb', 1, function(ev, db, tx) {
* db.createObjectStore('mystore');
* }).addCallback(function(db) {
* var putTx = db.createTransaction(
* [],
* goog.db.Transaction.TransactionMode.READ_WRITE);
* var store = putTx.objectStore('mystore');
* store.put('value', 'key');
* goog.listen(putTx, goog.db.Transaction.EventTypes.COMPLETE, function() {
* var getTx = db.createTransaction([]);
* var request = getTx.objectStore('mystore').get('key');
* request.addCallback(function(result) {
* ...
* });
* });
* </code>
*
*/
goog.provide('goog.db');
goog.require('goog.async.Deferred');
goog.require('goog.db.Error');
goog.require('goog.db.IndexedDb');
goog.require('goog.db.Transaction');
/**
* The IndexedDB factory object.
*
* @type {IDBFactory}
* @private
*/
goog.db.indexedDb_ = goog.global.indexedDB || goog.global.mozIndexedDB ||
goog.global.webkitIndexedDB || goog.global.moz_indexedDB;
/**
* A callback that's called if a blocked event is received. When a database is
* supposed to be deleted or upgraded (i.e. versionchange), and there are open
* connections to this database, a block event will be fired to prevent the
* operations from going through until all such open connections are closed.
* This callback can be used to notify users that they should close other tabs
* that have open connections, or to close the connections manually. Databases
* can also listen for the {@link goog.db.IndexedDb.EventType.VERSION_CHANGE}
* event to automatically close themselves when they're blocking such
* operations.
*
* This is passed a VersionChangeEvent that has the version of the database
* before it was deleted, and "null" as the new version.
*
* @typedef {function(!goog.db.IndexedDb.VersionChangeEvent)}
*/
goog.db.BlockedCallback;
/**
* A callback that's called when opening a database whose internal version is
* lower than the version passed to {@link goog.db.openDatabase}.
*
* This callback is passed three arguments: a VersionChangeEvent with both the
* old version and the new version of the database; the database that's being
* opened, for which you can create and delete object stores; and the version
* change transaction, with which you can abort the version change.
*
* Note that the transaction is not active, which means that it can't be used to
* make changes to the database. However, since there is a transaction running,
* you can't create another one via {@link goog.db.IndexedDb.createTransaction}.
* This means that it's not possible to manipulate the database other than
* creating or removing object stores in this callback.
*
* @typedef {function(!goog.db.IndexedDb.VersionChangeEvent,
* !goog.db.IndexedDb,
* !goog.db.Transaction)}
*/
goog.db.UpgradeNeededCallback;
/**
* Opens a database connection and wraps it.
*
* @param {string} name The name of the database to open.
* @param {number=} opt_version The expected version of the database. If this is
* larger than the actual version, opt_onUpgradeNeeded will be called
* (possibly after opt_onBlocked; see {@link goog.db.BlockedCallback}). If
* this is passed, opt_onUpgradeNeeded must be passed as well.
* @param {goog.db.UpgradeNeededCallback=} opt_onUpgradeNeeded Called if
* opt_version is greater than the old version of the database. If
* opt_version is passed, this must be passed as well.
* @param {goog.db.BlockedCallback=} opt_onBlocked Called if there are active
* connections to the database.
* @return {!goog.async.Deferred} The deferred database object.
*/
goog.db.openDatabase = function(name, opt_version, opt_onUpgradeNeeded,
opt_onBlocked) {
goog.asserts.assert(
goog.isDef(opt_version) == goog.isDef(opt_onUpgradeNeeded),
'opt_version must be passed to goog.db.openDatabase if and only if ' +
'opt_onUpgradeNeeded is also passed');
var d = new goog.async.Deferred();
var openRequest = opt_version ?
goog.db.indexedDb_.open(name, opt_version) :
goog.db.indexedDb_.open(name);
openRequest.onsuccess = function(ev) {
var db = new goog.db.IndexedDb(ev.target.result);
d.callback(db);
};
openRequest.onerror = function(ev) {
var msg = 'opening database ' + name;
d.errback(goog.db.Error.fromRequest(ev.target, msg));
};
openRequest.onupgradeneeded = function(ev) {
if (!opt_onUpgradeNeeded) return;
var db = new goog.db.IndexedDb(ev.target.result);
opt_onUpgradeNeeded(
new goog.db.IndexedDb.VersionChangeEvent(ev.oldVersion, ev.newVersion),
db,
new goog.db.Transaction(ev.target.transaction, db));
};
openRequest.onblocked = function(ev) {
if (opt_onBlocked) {
opt_onBlocked(new goog.db.IndexedDb.VersionChangeEvent(
ev.oldVersion, ev.newVersion));
}
};
return d;
};
/**
* Deletes a database once all open connections have been closed.
*
* @param {string} name The name of the database to delete.
* @param {goog.db.BlockedCallback=} opt_onBlocked Called if there are active
* connections to the database.
* @return {goog.async.Deferred} A deferred object that will fire once the
* database is deleted.
*/
goog.db.deleteDatabase = function(name, opt_onBlocked) {
var d = new goog.async.Deferred();
var deleteRequest = goog.db.indexedDb_.deleteDatabase(name);
deleteRequest.onsuccess = function(ev) {
d.callback();
};
deleteRequest.onerror = function(ev) {
var msg = 'deleting database ' + name;
d.errback(goog.db.Error.fromRequest(ev.target, msg));
};
deleteRequest.onblocked = function(ev) {
if (opt_onBlocked) {
opt_onBlocked(new goog.db.IndexedDb.VersionChangeEvent(
ev.oldVersion, ev.newVersion));
}
};
return d;
};

View File

@@ -0,0 +1,359 @@
// Copyright 2011 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 Error classes for the IndexedDB wrapper.
*
*/
goog.provide('goog.db.Error');
goog.provide('goog.db.Error.ErrorCode');
goog.provide('goog.db.Error.ErrorName');
goog.provide('goog.db.Error.VersionChangeBlockedError');
goog.require('goog.debug.Error');
/**
* A database error. Since the stack trace can be unhelpful in an asynchronous
* context, the error provides a message about where it was produced.
*
* @param {number|!DOMError} error The DOMError instance returned by the
* browser for Chrome22+, or an error code for previous versions.
* @param {string} context A description of where the error occured.
* @param {string=} opt_message Additional message.
* @constructor
* @extends {goog.debug.Error}
*/
goog.db.Error = function(error, context, opt_message) {
var errorCode = null;
var internalError = null;
if (goog.isNumber(error)) {
errorCode = error;
internalError = {name: goog.db.Error.getName(errorCode)};
} else {
internalError = error;
errorCode = goog.db.Error.getCode(error.name);
}
/**
* The code for this error.
*
* @type {number}
*/
this.code = errorCode;
/**
* The DOMException as returned by the browser.
*
* @type {!DOMError}
* @private
*/
this.error_ = /** @type {!DOMError} */ (internalError);
var msg = 'Error ' + context + ': ' + this.getName();
if (opt_message) {
msg += ', ' + opt_message;
}
goog.base(this, msg);
};
goog.inherits(goog.db.Error, goog.debug.Error);
/**
* @return {string} The name of the error.
*/
goog.db.Error.prototype.getName = function() {
return this.error_.name;
};
/**
* A specific kind of database error. If a Version Change is unable to proceed
* due to other open database connections, it will block and this error will be
* thrown.
*
* @constructor
* @extends {goog.debug.Error}
*/
goog.db.Error.VersionChangeBlockedError = function() {
goog.base(this, 'Version change blocked');
};
goog.inherits(goog.db.Error.VersionChangeBlockedError, goog.debug.Error);
/**
* Synthetic error codes for database errors, for use when IndexedDB
* support is not available. This numbering differs in practice
* from the browser implementations, but it is not meant to be reliable:
* this object merely ensures that goog.db.Error is loadable on platforms
* that do not support IndexedDB.
*
* @enum {number}
* @private
*/
goog.db.Error.DatabaseErrorCode_ = {
UNKNOWN_ERR: 1,
NON_TRANSIENT_ERR: 2,
NOT_FOUND_ERR: 3,
CONSTRAINT_ERR: 4,
DATA_ERR: 5,
NOT_ALLOWED_ERR: 6,
TRANSACTION_INACTIVE_ERR: 7,
ABORT_ERR: 8,
READ_ONLY_ERR: 9,
TRANSIENT_ERR: 11,
TIMEOUT_ERR: 10,
QUOTA_ERR: 11,
INVALID_ACCESS_ERR: 12,
INVALID_STATE_ERR: 13
};
/**
* Error codes for database errors.
* @see http://www.w3.org/TR/IndexedDB/#idl-def-IDBDatabaseException
*
* @enum {number}
*/
goog.db.Error.ErrorCode = {
UNKNOWN_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).UNKNOWN_ERR,
NON_TRANSIENT_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).NON_TRANSIENT_ERR,
NOT_FOUND_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).NOT_FOUND_ERR,
CONSTRAINT_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).CONSTRAINT_ERR,
DATA_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).DATA_ERR,
NOT_ALLOWED_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).NOT_ALLOWED_ERR,
TRANSACTION_INACTIVE_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).TRANSACTION_INACTIVE_ERR,
ABORT_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).ABORT_ERR,
READ_ONLY_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).READ_ONLY_ERR,
TIMEOUT_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).TIMEOUT_ERR,
QUOTA_ERR: (goog.global.IDBDatabaseException ||
goog.global.webkitIDBDatabaseException ||
goog.db.Error.DatabaseErrorCode_).QUOTA_ERR,
INVALID_ACCESS_ERR: (goog.global.DOMException ||
goog.db.Error.DatabaseErrorCode_).INVALID_ACCESS_ERR,
INVALID_STATE_ERR: (goog.global.DOMException ||
goog.db.Error.DatabaseErrorCode_).INVALID_STATE_ERR
};
/**
* Translates an error code into a more useful message.
*
* @param {number} code Error code.
* @return {string} A debug message.
*/
goog.db.Error.getMessage = function(code) {
switch (code) {
case goog.db.Error.ErrorCode.UNKNOWN_ERR:
return 'Unknown error';
case goog.db.Error.ErrorCode.NON_TRANSIENT_ERR:
return 'Invalid operation';
case goog.db.Error.ErrorCode.NOT_FOUND_ERR:
return 'Required database object not found';
case goog.db.Error.ErrorCode.CONSTRAINT_ERR:
return 'Constraint unsatisfied';
case goog.db.Error.ErrorCode.DATA_ERR:
return 'Invalid data';
case goog.db.Error.ErrorCode.NOT_ALLOWED_ERR:
return 'Operation disallowed';
case goog.db.Error.ErrorCode.TRANSACTION_INACTIVE_ERR:
return 'Transaction not active';
case goog.db.Error.ErrorCode.ABORT_ERR:
return 'Request aborted';
case goog.db.Error.ErrorCode.READ_ONLY_ERR:
return 'Modifying operation not allowed in a read-only transaction';
case goog.db.Error.ErrorCode.TIMEOUT_ERR:
return 'Transaction timed out';
case goog.db.Error.ErrorCode.QUOTA_ERR:
return 'Database storage space quota exceeded';
case goog.db.Error.ErrorCode.INVALID_ACCESS_ERR:
return 'Invalid operation';
case goog.db.Error.ErrorCode.INVALID_STATE_ERR:
return 'Invalid state';
default:
return 'Unrecognized exception with code ' + code;
}
};
/**
* Names of all possible errors as returned from the browser.
* @see http://www.w3.org/TR/IndexedDB/#exceptions
* @enum {string}
*/
goog.db.Error.ErrorName = {
ABORT_ERR: 'AbortError',
CONSTRAINT_ERR: 'ConstraintError',
DATA_CLONE_ERR: 'DataCloneError',
DATA_ERR: 'DataError',
INVALID_ACCESS_ERR: 'InvalidAccessError',
INVALID_STATE_ERR: 'InvalidStateError',
NOT_FOUND_ERR: 'NotFoundError',
QUOTA_EXCEEDED_ERR: 'QuotaExceededError',
READ_ONLY_ERR: 'ReadOnlyError',
SYNTAX_ERROR: 'SyntaxError',
TIMEOUT_ERR: 'TimeoutError',
TRANSACTION_INACTIVE_ERR: 'TransactionInactiveError',
UNKNOWN_ERR: 'UnknownError',
VERSION_ERR: 'VersionError'
};
/**
* Translates an error name to an error code. This is purely kept for backwards
* compatibility with Chrome21.
*
* @param {string} name The name of the erorr.
* @return {number} The error code corresponding to the error.
*/
goog.db.Error.getCode = function(name) {
switch (name) {
case goog.db.Error.ErrorName.UNKNOWN_ERR:
return goog.db.Error.ErrorCode.UNKNOWN_ERR;
case goog.db.Error.ErrorName.NOT_FOUND_ERR:
return goog.db.Error.ErrorCode.NOT_FOUND_ERR;
case goog.db.Error.ErrorName.CONSTRAINT_ERR:
return goog.db.Error.ErrorCode.CONSTRAINT_ERR;
case goog.db.Error.ErrorName.DATA_ERR:
return goog.db.Error.ErrorCode.DATA_ERR;
case goog.db.Error.ErrorName.TRANSACTION_INACTIVE_ERR:
return goog.db.Error.ErrorCode.TRANSACTION_INACTIVE_ERR;
case goog.db.Error.ErrorName.ABORT_ERR:
return goog.db.Error.ErrorCode.ABORT_ERR;
case goog.db.Error.ErrorName.READ_ONLY_ERR:
return goog.db.Error.ErrorCode.READ_ONLY_ERR;
case goog.db.Error.ErrorName.TIMEOUT_ERR:
return goog.db.Error.ErrorCode.TIMEOUT_ERR;
case goog.db.Error.ErrorName.QUOTA_EXCEEDED_ERR:
return goog.db.Error.ErrorCode.QUOTA_ERR;
case goog.db.Error.ErrorName.INVALID_ACCESS_ERR:
return goog.db.Error.ErrorCode.INVALID_ACCESS_ERR;
case goog.db.Error.ErrorName.INVALID_STATE_ERR:
return goog.db.Error.ErrorCode.INVALID_STATE_ERR;
default:
return goog.db.Error.ErrorCode.UNKNOWN_ERR;
}
};
/**
* Converts an error code used by the old spec, to an error name used by the
* latest spec.
* @see http://www.w3.org/TR/IndexedDB/#exceptions
*
* @param {!goog.db.Error.ErrorCode|number} code The error code to convert.
* @return {!goog.db.Error.ErrorName} The corresponding name of the error.
*/
goog.db.Error.getName = function(code) {
switch (code) {
case goog.db.Error.ErrorCode.UNKNOWN_ERR:
return goog.db.Error.ErrorName.UNKNOWN_ERR;
case goog.db.Error.ErrorCode.NOT_FOUND_ERR:
return goog.db.Error.ErrorName.NOT_FOUND_ERR;
case goog.db.Error.ErrorCode.CONSTRAINT_ERR:
return goog.db.Error.ErrorName.CONSTRAINT_ERR;
case goog.db.Error.ErrorCode.DATA_ERR:
return goog.db.Error.ErrorName.DATA_ERR;
case goog.db.Error.ErrorCode.TRANSACTION_INACTIVE_ERR:
return goog.db.Error.ErrorName.TRANSACTION_INACTIVE_ERR;
case goog.db.Error.ErrorCode.ABORT_ERR:
return goog.db.Error.ErrorName.ABORT_ERR;
case goog.db.Error.ErrorCode.READ_ONLY_ERR:
return goog.db.Error.ErrorName.READ_ONLY_ERR;
case goog.db.Error.ErrorCode.TIMEOUT_ERR:
return goog.db.Error.ErrorName.TIMEOUT_ERR;
case goog.db.Error.ErrorCode.QUOTA_ERR:
return goog.db.Error.ErrorName.QUOTA_EXCEEDED_ERR;
case goog.db.Error.ErrorCode.INVALID_ACCESS_ERR:
return goog.db.Error.ErrorName.INVALID_ACCESS_ERR;
case goog.db.Error.ErrorCode.INVALID_STATE_ERR:
return goog.db.Error.ErrorName.INVALID_STATE_ERR;
default:
return goog.db.Error.ErrorName.UNKNOWN_ERR;
}
};
/**
* Constructs an goog.db.Error instance from an IDBRequest. This abstraction is
* necessary to provide backwards compatibility with Chrome21.
*
* @param {!IDBRequest} request The request that failed.
* @param {string} message The error message to add to err if it's wrapped.
* @return {!goog.db.Error} The error that caused the failure.
*/
goog.db.Error.fromRequest = function(request, message) {
if ('error' in request) {
// Chrome 21 and before.
return new goog.db.Error(request.error, message);
} else if ('name' in request) {
// Chrome 22+.
var errorName = goog.db.Error.getName(request.errorCode);
return new goog.db.Error(
/**@type {!DOMError} */ ({name: errorName}), message);
} else {
return new goog.db.Error(/** @type {!DOMError} */ (
{name: goog.db.Error.ErrorName.UNKNOWN_ERR}), message);
}
};
/**
* Constructs an goog.db.Error instance from an DOMException. This abstraction
* is necessary to provide backwards compatibility with Chrome21.
*
* @param {!IDBDatabaseException} ex The exception that was thrown.
* @param {string} message The error message to add to err if it's wrapped.
* @return {!goog.db.Error} The error that caused the failure.
* @suppress {invalidCasts} The cast from IDBDatabaseException to DOMError
* is invalid and will not compile.
*/
goog.db.Error.fromException = function(ex, message) {
if ('name' in ex) {
// Chrome 21 and before.
return new goog.db.Error(/** @type {!DOMError} */ (ex), message);
} else if ('code' in ex) {
// Chrome 22+.
var errorName = goog.db.Error.getName(ex.code);
return new goog.db.Error(
/** @type {!DOMError} */ ({name: errorName}), message);
} else {
return new goog.db.Error(/** @type {!DOMError} */ (
{name: goog.db.Error.ErrorName.UNKNOWN_ERR}), message);
}
};

View File

@@ -0,0 +1,245 @@
// Copyright 2011 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 Wrapper for an IndexedDB index.
*
*/
goog.provide('goog.db.Index');
goog.require('goog.async.Deferred');
goog.require('goog.db.Cursor');
goog.require('goog.db.Error');
goog.require('goog.debug');
/**
* Creates an IDBIndex wrapper object. Indexes are associated with object
* stores and provide methods for looking up objects based on their non-key
* properties. Should not be created directly, access through the object store
* it belongs to.
* @see goog.db.ObjectStore#getIndex
*
* @param {!IDBIndex} index Underlying IDBIndex object.
* @constructor
*/
goog.db.Index = function(index) {
/**
* Underlying IndexedDB index object.
*
* @type {!IDBIndex}
* @private
*/
this.index_ = index;
};
/**
* @return {string} Name of the index.
*/
goog.db.Index.prototype.getName = function() {
return this.index_.name;
};
/**
* @return {string} Key path of the index.
*/
goog.db.Index.prototype.getKeyPath = function() {
return this.index_.keyPath;
};
/**
* @return {boolean} True if the index enforces that there is only one object
* for each unique value it indexes on.
*/
goog.db.Index.prototype.isUnique = function() {
return this.index_.unique;
};
/**
* Helper function for get and getKey.
*
* @param {string} fn Function name to call on the index to get the request.
* @param {string} msg Message to give to the error.
* @param {IDBKeyType} key The key to look up in the index.
* @return {!goog.async.Deferred} The resulting deferred object.
* @private
*/
goog.db.Index.prototype.get_ = function(fn, msg, key) {
var d = new goog.async.Deferred();
var request;
try {
request = this.index_[fn](key);
} catch (err) {
msg += ' with key ' + goog.debug.deepExpose(key);
d.errback(goog.db.Error.fromException(err, msg));
return d;
}
request.onsuccess = function(ev) {
d.callback(ev.target.result);
};
request.onerror = function(ev) {
msg += ' with key ' + goog.debug.deepExpose(key);
d.errback(goog.db.Error.fromRequest(ev.target, msg));
};
return d;
};
/**
* Fetches a single object from the object store. Even if there are multiple
* objects that match the given key, this method will get only one of them.
*
* @param {IDBKeyType} key Key to look up in the index.
* @return {!goog.async.Deferred} The deferred object for the given record.
*/
goog.db.Index.prototype.get = function(key) {
return this.get_('get', 'getting from index ' + this.getName(), key);
};
/**
* Looks up a single object from the object store and gives back the key that
* it's listed under in the object store. Even if there are multiple records
* that match the given key, this method returns the first.
*
* @param {IDBKeyType} key Key to look up in the index.
* @return {!goog.async.Deferred} The deferred key for the record that matches
* the key.
*/
goog.db.Index.prototype.getKey = function(key) {
return this.get_('getKey', 'getting key from index ' + this.getName(), key);
};
/**
* Helper function for getAll and getAllKeys.
*
* @param {string} fn Function name to call on the index to get the request.
* @param {string} msg Message to give to the error.
* @param {IDBKeyType=} opt_key Key to look up in the index.
* @return {!goog.async.Deferred} The resulting deferred array of objects.
* @private
*/
goog.db.Index.prototype.getAll_ = function(fn, msg, opt_key) {
// This is the most common use of IDBKeyRange. If more specific uses of
// cursors are needed then a full wrapper should be created.
var IDBKeyRange = goog.global.IDBKeyRange || goog.global.webkitIDBKeyRange;
var d = new goog.async.Deferred();
var request;
try {
if (opt_key) {
request = this.index_[fn](IDBKeyRange.only(opt_key));
} else {
request = this.index_[fn]();
}
} catch (err) {
if (opt_key) {
msg += ' for key ' + goog.debug.deepExpose(opt_key);
}
d.errback(goog.db.Error.fromException(err, msg));
return d;
}
var result = [];
request.onsuccess = function(ev) {
var cursor = ev.target.result;
if (cursor) {
result.push(cursor.value);
cursor['continue']();
} else {
d.callback(result);
}
};
request.onerror = function(ev) {
if (opt_key) {
msg += ' for key ' + goog.debug.deepExpose(opt_key);
}
d.errback(goog.db.Error.fromRequest(ev.target, msg));
};
return d;
};
/**
* Gets all indexed objects. If the key is provided, gets all indexed objects
* that match the key instead.
*
* @param {IDBKeyType=} opt_key Key to look up in the index.
* @return {!goog.async.Deferred} A deferred array of objects that match the
* key.
*/
goog.db.Index.prototype.getAll = function(opt_key) {
return this.getAll_(
'openCursor',
'getting all from index ' + this.getName(),
opt_key);
};
/**
* Gets the keys to look up all the indexed objects. If the key is provided,
* gets all records for objects that match the key instead.
*
* @param {IDBKeyType=} opt_key Key to look up in the index.
* @return {!goog.async.Deferred} A deferred array of keys for objects that
* match the key.
*/
goog.db.Index.prototype.getAllKeys = function(opt_key) {
return this.getAll_(
'openKeyCursor',
'getting all keys from index ' + this.getName(),
opt_key);
};
/**
* Opens a cursor over the specified key range. Returns a cursor object which is
* able to iterate over the given range.
*
* Example usage:
*
* <code>
* var cursor = index.openCursor(goog.db.Range.bound('a', 'c'));
*
* var key = goog.events.listen(
* cursor, goog.db.Cursor.EventType.NEW_DATA,
* function() {
* // Do something with data.
* cursor.next();
* });
*
* goog.events.listenOnce(
* cursor, goog.db.Cursor.EventType.COMPLETE,
* function() {
* // Clean up listener, and perform a finishing operation on the data.
* goog.events.unlistenByKey(key);
* });
* </code>
*
* @param {!goog.db.KeyRange=} opt_range The key range. If undefined iterates
* over the whole object store.
* @param {!goog.db.Cursor.Direction=} opt_direction The direction. If undefined
* moves in a forward direction with duplicates.
* @return {!goog.db.Cursor} The cursor.
* @throws {goog.db.Error} If there was a problem opening the cursor.
*/
goog.db.Index.prototype.openCursor = function(opt_range, opt_direction) {
return goog.db.Cursor.openCursor(this.index_, opt_range, opt_direction);
};

View File

@@ -0,0 +1,337 @@
// Copyright 2011 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 Wrapper for an IndexedDB database.
*
*/
goog.provide('goog.db.IndexedDb');
goog.require('goog.async.Deferred');
goog.require('goog.db.Error');
goog.require('goog.db.Error.VersionChangeBlockedError');
goog.require('goog.db.ObjectStore');
goog.require('goog.db.Transaction');
goog.require('goog.db.Transaction.TransactionMode');
goog.require('goog.events.Event');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventTarget');
/**
* Creates an IDBDatabase wrapper object. The database object has methods for
* setting the version to change the structure of the database and for creating
* transactions to get or modify the stored records. Should not be created
* directly, call {@link goog.db.openDatabase} to set up the connection.
*
* @param {!IDBDatabase} db Underlying IndexedDB database object.
* @constructor
* @extends {goog.events.EventTarget}
*/
goog.db.IndexedDb = function(db) {
goog.base(this);
/**
* Underlying IndexedDB database object.
*
* @type {!IDBDatabase}
* @private
*/
this.db_ = db;
/**
* Internal event handler that listens to IDBDatabase events.
* @type {!goog.events.EventHandler}
* @private
*/
this.eventHandler_ = new goog.events.EventHandler(this);
this.eventHandler_.listen(
this.db_,
goog.db.IndexedDb.EventType.ABORT,
goog.bind(
this.dispatchEvent,
this,
goog.db.IndexedDb.EventType.ABORT));
this.eventHandler_.listen(
this.db_,
goog.db.IndexedDb.EventType.ERROR,
this.dispatchError_);
this.eventHandler_.listen(
this.db_,
goog.db.IndexedDb.EventType.VERSION_CHANGE,
this.dispatchVersionChange_);
};
goog.inherits(goog.db.IndexedDb, goog.events.EventTarget);
/**
* True iff the database connection is open.
*
* @type {boolean}
* @private
*/
goog.db.IndexedDb.prototype.open_ = true;
/**
* Dispatches a wrapped error event based on the given event.
*
* @param {Event} ev The error event given to the underlying IDBDatabase.
* @private
*/
goog.db.IndexedDb.prototype.dispatchError_ = function(ev) {
this.dispatchEvent({
type: goog.db.IndexedDb.EventType.ERROR,
errorCode: /** @type {IDBRequest} */ (ev.target).errorCode
});
};
/**
* Dispatches a wrapped version change event based on the given event.
*
* @param {Event} ev The version change event given to the underlying
* IDBDatabase.
* @private
*/
goog.db.IndexedDb.prototype.dispatchVersionChange_ = function(ev) {
this.dispatchEvent(new goog.db.IndexedDb.VersionChangeEvent(
ev.oldVersion, ev.newVersion));
};
/**
* Closes the database connection. Metadata queries can still be made after this
* method is called, but otherwise this wrapper should not be used further.
*/
goog.db.IndexedDb.prototype.close = function() {
if (this.open_) {
this.db_.close();
this.open_ = false;
}
};
/**
* @return {boolean} Whether a connection is open and the database can be used.
*/
goog.db.IndexedDb.prototype.isOpen = function() {
return this.open_;
};
/**
* @return {string} The name of this database.
*/
goog.db.IndexedDb.prototype.getName = function() {
return this.db_.name;
};
/**
* @return {string} The current database version.
*/
goog.db.IndexedDb.prototype.getVersion = function() {
return this.db_.version;
};
/**
* @return {DOMStringList} List of object stores in this database.
*/
goog.db.IndexedDb.prototype.getObjectStoreNames = function() {
return this.db_.objectStoreNames;
};
/**
* Creates an object store in this database. Can only be called inside a
* {@link goog.db.UpgradeNeededCallback} or the callback for the Deferred
* returned from #setVersion.
*
* @param {string} name Name for the new object store.
* @param {Object=} opt_params Options object. The available options are:
* keyPath, which is a string and determines what object attribute
* to use as the key when storing objects in this object store; and
* autoIncrement, which is a boolean, which defaults to false and determines
* whether the object store should automatically generate keys for stored
* objects. If keyPath is not provided and autoIncrement is false, then all
* insert operations must provide a key as a parameter.
* @return {goog.db.ObjectStore} The newly created object store.
* @throws {goog.db.Error} If there's a problem creating the object store.
*/
goog.db.IndexedDb.prototype.createObjectStore = function(name, opt_params) {
try {
return new goog.db.ObjectStore(this.db_.createObjectStore(
name, opt_params));
} catch (ex) {
throw goog.db.Error.fromException(ex, 'creating object store ' + name);
}
};
/**
* Deletes an object store. Can only be called inside a
* {@link goog.db.UpgradeNeededCallback} or the callback for the Deferred
* returned from #setVersion.
*
* @param {string} name Name of the object store to delete.
* @throws {goog.db.Error} If there's a problem deleting the object store.
*/
goog.db.IndexedDb.prototype.deleteObjectStore = function(name) {
try {
this.db_.deleteObjectStore(name);
} catch (ex) {
throw goog.db.Error.fromException(ex, 'deleting object store ' + name);
}
};
/**
* Updates the version of the database and returns a Deferred transaction.
* The database's structure can be changed inside this Deferred's callback, but
* nowhere else. This means adding or deleting object stores, and adding or
* deleting indexes. The version change will not succeed unless there are no
* other connections active for this database anywhere. A new database
* connection should be opened after the version change is finished to pick
* up changes.
*
* This is deprecated, and only supported on Chrome prior to version 25. New
* applications should use the version parameter to {@link goog.db.openDatabase}
* instead.
*
* @param {string} version The new version of the database.
* @return {!goog.async.Deferred} The deferred transaction for changing the
* version.
*/
goog.db.IndexedDb.prototype.setVersion = function(version) {
var self = this;
var d = new goog.async.Deferred();
var request = this.db_.setVersion(version);
request.onsuccess = function(ev) {
// the transaction is in the result field (the transaction field is null
// for version change requests)
d.callback(new goog.db.Transaction(ev.target.result, self));
};
request.onerror = function(ev) {
// If a version change is blocked, onerror and onblocked may both fire.
// Check d.hasFired() to avoid an AlreadyCalledError.
if (!d.hasFired()) {
d.errback(goog.db.Error.fromRequest(ev.target, 'setting version'));
}
};
request.onblocked = function(ev) {
// If a version change is blocked, onerror and onblocked may both fire.
// Check d.hasFired() to avoid an AlreadyCalledError.
if (!d.hasFired()) {
d.errback(new goog.db.Error.VersionChangeBlockedError());
}
};
return d;
};
/**
* Creates a new transaction.
*
* @param {!Array.<string>} storeNames A list of strings that contains the
* transaction's scope, the object stores that this transaction can operate
* on.
* @param {goog.db.Transaction.TransactionMode=} opt_mode The mode of the
* transaction. If not present, the default is READ_ONLY. For VERSION_CHANGE
* transactions call {@link goog.db.IndexedDB#setVersion} instead.
* @return {!goog.db.Transaction} The wrapper for the newly created transaction.
* @throws {goog.db.Error} If there's a problem creating the transaction.
*/
goog.db.IndexedDb.prototype.createTransaction = function(storeNames, opt_mode) {
try {
// IndexedDB on Chrome 22+ requires that opt_mode not be passed rather than
// be explicitly passed as undefined.
var transaction = opt_mode ?
this.db_.transaction(storeNames, opt_mode) :
this.db_.transaction(storeNames);
return new goog.db.Transaction(transaction, this);
} catch (ex) {
throw goog.db.Error.fromException(ex, 'creating transaction');
}
};
/** @override */
goog.db.IndexedDb.prototype.disposeInternal = function() {
goog.base(this, 'disposeInternal');
this.eventHandler_.dispose();
};
/**
* Event types fired by a database.
*
* @enum {string} The event types for the web socket.
*/
goog.db.IndexedDb.EventType = {
/**
* Fired when a transaction is aborted and the event bubbles to its database.
*/
ABORT: 'abort',
/**
* Fired when a transaction has an error.
*/
ERROR: 'error',
/**
* Fired when someone (possibly in another window) is attempting to modify the
* structure of the database. Since a change can only be made when there are
* no active database connections, this usually means that the database should
* be closed so that the other client can make its changes.
*/
VERSION_CHANGE: 'versionchange'
};
/**
* Event representing a (possibly attempted) change in the database structure.
*
* At time of writing, no Chrome versions support oldVersion or newVersion. See
* http://crbug.com/153122.
*
* @param {number} oldVersion The previous version of the database.
* @param {number} newVersion The version the database is being or has been
* updated to.
* @constructor
* @extends {goog.events.Event}
*/
goog.db.IndexedDb.VersionChangeEvent = function(oldVersion, newVersion) {
goog.base(this, goog.db.IndexedDb.EventType.VERSION_CHANGE);
/**
* The previous version of the database.
* @type {number}
*/
this.oldVersion = oldVersion;
/**
* The version the database is being or has been updated to.
* @type {number}
*/
this.newVersion = newVersion;
};
goog.inherits(goog.db.IndexedDb.VersionChangeEvent, goog.events.Event);

View File

@@ -0,0 +1,117 @@
// Copyright 2012 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 Wrapper for a IndexedDB key range.
*
*/
goog.provide('goog.db.KeyRange');
/**
* Creates a new IDBKeyRange wrapper object. Should not be created directly,
* instead use one of the static factory methods. For example:
* @see goog.db.KeyRange.bound
* @see goog.db.KeyRange.lowerBound
*
* @param {!IDBKeyRange} range Underlying IDBKeyRange object.
* @constructor
*/
goog.db.KeyRange = function(range) {
/**
* Underlying IDBKeyRange object.
*
* @type {!IDBKeyRange}
* @private
*/
this.range_ = range;
};
/**
* The IDBKeyRange.
* @type {!Object}
* @private
*/
goog.db.KeyRange.IDB_KEY_RANGE_ = goog.global.IDBKeyRange ||
goog.global.webkitIDBKeyRange;
/**
* Creates a new key range for a single value.
*
* @param {IDBKeyType} key The single value in the range.
* @return {!goog.db.KeyRange} The key range.
*/
goog.db.KeyRange.only = function(key) {
return new goog.db.KeyRange(goog.db.KeyRange.IDB_KEY_RANGE_.only(key));
};
/**
* Creates a key range with upper and lower bounds.
*
* @param {IDBKeyType} lower The value of the lower bound.
* @param {IDBKeyType} upper The value of the upper bound.
* @param {boolean=} opt_lowerOpen If true, the range excludes the lower bound
* value.
* @param {boolean=} opt_upperOpen If true, the range excludes the upper bound
* value.
* @return {!goog.db.KeyRange} The key range.
*/
goog.db.KeyRange.bound = function(lower, upper, opt_lowerOpen, opt_upperOpen) {
return new goog.db.KeyRange(goog.db.KeyRange.IDB_KEY_RANGE_.bound(
lower, upper, opt_lowerOpen, opt_upperOpen));
};
/**
* Creates a key range with a lower bound only, finishes at the last record.
*
* @param {IDBKeyType} lower The value of the lower bound.
* @param {boolean=} opt_lowerOpen If true, the range excludes the lower bound
* value.
* @return {!goog.db.KeyRange} The key range.
*/
goog.db.KeyRange.lowerBound = function(lower, opt_lowerOpen) {
return new goog.db.KeyRange(goog.db.KeyRange.IDB_KEY_RANGE_.lowerBound(
lower, opt_lowerOpen));
};
/**
* Creates a key range with a upper bound only, starts at the first record.
*
* @param {IDBKeyType} upper The value of the upper bound.
* @param {boolean=} opt_upperOpen If true, the range excludes the upper bound
* value.
* @return {!goog.db.KeyRange} The key range.
*/
goog.db.KeyRange.upperBound = function(upper, opt_upperOpen) {
return new goog.db.KeyRange(goog.db.KeyRange.IDB_KEY_RANGE_.upperBound(
upper, opt_upperOpen));
};
/**
* Returns underlying key range object. This is used in ObjectStore's openCursor
* and count methods.
* @return {!IDBKeyRange}
*/
goog.db.KeyRange.prototype.range = function() {
return this.range_;
};

View File

@@ -0,0 +1,399 @@
// Copyright 2011 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 Wrapper for an IndexedDB object store.
*
*/
goog.provide('goog.db.ObjectStore');
goog.require('goog.async.Deferred');
goog.require('goog.db.Cursor');
goog.require('goog.db.Error');
goog.require('goog.db.Index');
goog.require('goog.debug');
goog.require('goog.events');
/**
* Creates an IDBObjectStore wrapper object. Object stores have methods for
* storing and retrieving records, and are accessed through a transaction
* object. They also have methods for creating indexes associated with the
* object store. They can only be created when setting the version of the
* database. Should not be created directly, access object stores through
* transactions.
* @see goog.db.IndexedDb#setVersion
* @see goog.db.Transaction#objectStore
*
* @param {!IDBObjectStore} store The backing IndexedDb object.
* @constructor
*
* TODO(user): revisit msg in exception and errors in this class. In newer
* Chrome (v22+) the error/request come with a DOM error string that is
* already very descriptive.
*/
goog.db.ObjectStore = function(store) {
/**
* Underlying IndexedDB object store object.
*
* @type {!IDBObjectStore}
* @private
*/
this.store_ = store;
};
/**
* @return {string} The name of the object store.
*/
goog.db.ObjectStore.prototype.getName = function() {
return this.store_.name;
};
/**
* Helper function for put and add.
*
* @param {string} fn Function name to call on the object store.
* @param {string} msg Message to give to the error.
* @param {*} value Value to insert into the object store.
* @param {IDBKeyType=} opt_key The key to use.
* @return {!goog.async.Deferred} The resulting deferred request.
* @private
*/
goog.db.ObjectStore.prototype.insert_ = function(fn, msg, value, opt_key) {
// TODO(user): refactor wrapping an IndexedDB request in a Deferred by
// creating a higher-level abstraction for it (mostly affects here and
// goog.db.Index)
var d = new goog.async.Deferred();
var request;
try {
// put or add with (value, undefined) throws an error, so we need to check
// for undefined ourselves
if (opt_key) {
request = this.store_[fn](value, opt_key);
} else {
request = this.store_[fn](value);
}
} catch (ex) {
msg += goog.debug.deepExpose(value);
if (opt_key) {
msg += ', with key ' + goog.debug.deepExpose(opt_key);
}
d.errback(goog.db.Error.fromException(ex, msg));
return d;
}
request.onsuccess = function(ev) {
d.callback();
};
var self = this;
request.onerror = function(ev) {
msg += goog.debug.deepExpose(value);
if (opt_key) {
msg += ', with key ' + goog.debug.deepExpose(opt_key);
}
d.errback(goog.db.Error.fromRequest(ev.target, msg));
};
return d;
};
/**
* Adds an object to the object store. Replaces existing objects with the
* same key.
*
* @param {*} value The value to put.
* @param {IDBKeyType=} opt_key The key to use. Cannot be used if the
* keyPath was specified for the object store. If the keyPath was not
* specified but autoIncrement was not enabled, it must be used.
* @return {!goog.async.Deferred} The deferred put request.
*/
goog.db.ObjectStore.prototype.put = function(value, opt_key) {
return this.insert_(
'put',
'putting into ' + this.getName() + ' with value',
value,
opt_key);
};
/**
* Adds an object to the object store. Requires that there is no object with
* the same key already present.
*
* @param {*} value The value to add.
* @param {IDBKeyType=} opt_key The key to use. Cannot be used if the
* keyPath was specified for the object store. If the keyPath was not
* specified but autoIncrement was not enabled, it must be used.
* @return {!goog.async.Deferred} The deferred add request.
*/
goog.db.ObjectStore.prototype.add = function(value, opt_key) {
return this.insert_(
'add',
'adding into ' + this.getName() + ' with value ',
value,
opt_key);
};
/**
* Removes an object from the store. No-op if there is no object present with
* the given key.
*
* @param {IDBKeyType} key The key to remove objects under.
* @return {!goog.async.Deferred} The deferred remove request.
*/
goog.db.ObjectStore.prototype.remove = function(key) {
var d = new goog.async.Deferred();
var request;
try {
request = this.store_['delete'](key);
} catch (err) {
var msg = 'removing from ' + this.getName() + ' with key ' +
goog.debug.deepExpose(key);
d.errback(goog.db.Error.fromException(err, msg));
return d;
}
request.onsuccess = function(ev) {
d.callback();
};
var self = this;
request.onerror = function(ev) {
var msg = 'removing from ' + self.getName() + ' with key ' +
goog.debug.deepExpose(key);
d.errback(goog.db.Error.fromRequest(ev.target, msg));
};
return d;
};
/**
* Gets an object from the store. If no object is present with that key
* the result is {@code undefined}.
*
* @param {IDBKeyType} key The key to look up.
* @return {!goog.async.Deferred} The deferred get request.
*/
goog.db.ObjectStore.prototype.get = function(key) {
var d = new goog.async.Deferred();
var request;
try {
request = this.store_.get(key);
} catch (err) {
var msg = 'getting from ' + this.getName() + ' with key ' +
goog.debug.deepExpose(key);
d.errback(goog.db.Error.fromException(err, msg));
return d;
}
request.onsuccess = function(ev) {
d.callback(ev.target.result);
};
var self = this;
request.onerror = function(ev) {
var msg = 'getting from ' + self.getName() + ' with key ' +
goog.debug.deepExpose(key);
d.errback(goog.db.Error.fromRequest(ev.target, msg));
};
return d;
};
/**
* Gets all objects from the store and returns them as an array.
*
* @param {!goog.db.KeyRange=} opt_range The key range. If undefined iterates
* over the whole object store.
* @param {!goog.db.Cursor.Direction=} opt_direction The direction. If undefined
* moves in a forward direction with duplicates.
* @return {!goog.async.Deferred} The deferred getAll request.
*/
goog.db.ObjectStore.prototype.getAll = function(opt_range, opt_direction) {
var d = new goog.async.Deferred();
var cursor;
try {
cursor = this.openCursor(opt_range, opt_direction);
} catch (err) {
d.errback(err);
return d;
}
var result = [];
var key = goog.events.listen(
cursor, goog.db.Cursor.EventType.NEW_DATA, function() {
result.push(cursor.getValue());
cursor.next();
});
goog.events.listenOnce(cursor, [
goog.db.Cursor.EventType.ERROR,
goog.db.Cursor.EventType.COMPLETE
], function(evt) {
cursor.dispose();
if (evt.type == goog.db.Cursor.EventType.COMPLETE) {
d.callback(result);
} else {
d.errback();
}
});
return d;
};
/**
* Opens a cursor over the specified key range. Returns a cursor object which is
* able to iterate over the given range.
*
* Example usage:
*
* <code>
* var cursor = objectStore.openCursor(goog.db.Range.bound('a', 'c'));
*
* var key = goog.events.listen(
* cursor, goog.db.Cursor.EventType.NEW_DATA, function() {
* // Do something with data.
* cursor.next();
* });
*
* goog.events.listenOnce(
* cursor, goog.db.Cursor.EventType.COMPLETE, function() {
* // Clean up listener, and perform a finishing operation on the data.
* goog.events.unlistenByKey(key);
* });
* </code>
*
* @param {!goog.db.KeyRange=} opt_range The key range. If undefined iterates
* over the whole object store.
* @param {!goog.db.Cursor.Direction=} opt_direction The direction. If undefined
* moves in a forward direction with duplicates.
* @return {!goog.db.Cursor} The cursor.
* @throws {goog.db.Error} If there was a problem opening the cursor.
*/
goog.db.ObjectStore.prototype.openCursor = function(opt_range, opt_direction) {
return goog.db.Cursor.openCursor(this.store_, opt_range, opt_direction);
};
/**
* Deletes all objects from the store.
*
* @return {!goog.async.Deferred} The deferred clear request.
*/
goog.db.ObjectStore.prototype.clear = function() {
var msg = 'clearing store ' + this.getName();
var d = new goog.async.Deferred();
var request;
try {
request = this.store_.clear();
} catch (err) {
d.errback(goog.db.Error.fromException(err, msg));
return d;
}
request.onsuccess = function(ev) {
d.callback();
};
request.onerror = function(ev) {
d.errback(goog.db.Error.fromRequest(ev.target, msg));
};
return d;
};
/**
* Creates an index in this object store. Can only be called inside the callback
* for the Deferred returned from goog.db.IndexedDb#setVersion.
*
* @param {string} name Name of the index to create.
* @param {string} keyPath Attribute to index on.
* @param {!Object=} opt_parameters Optional parameters object. The only
* available option is unique, which defaults to false. If unique is true,
* the index will enforce that there is only ever one object in the object
* store for each unique value it indexes on.
* @return {goog.db.Index} The newly created, wrapped index.
* @throws {goog.db.Error} In case of an error creating the index.
*/
goog.db.ObjectStore.prototype.createIndex = function(
name, keyPath, opt_parameters) {
try {
return new goog.db.Index(this.store_.createIndex(
name, keyPath, opt_parameters));
} catch (ex) {
var msg = 'creating new index ' + name + ' with key path ' + keyPath;
throw goog.db.Error.fromException(ex, msg);
}
};
/**
* Gets an index.
*
* @param {string} name Name of the index to fetch.
* @return {goog.db.Index} The requested wrapped index.
* @throws {goog.db.Error} In case of an error getting the index.
*/
goog.db.ObjectStore.prototype.getIndex = function(name) {
try {
return new goog.db.Index(this.store_.index(name));
} catch (ex) {
var msg = 'getting index ' + name;
throw goog.db.Error.fromException(ex, msg);
}
};
/**
* Deletes an index from the object store. Can only be called inside the
* callback for the Deferred returned from goog.db.IndexedDb#setVersion.
*
* @param {string} name Name of the index to delete.
* @throws {goog.db.Error} In case of an error deleting the index.
*/
goog.db.ObjectStore.prototype.deleteIndex = function(name) {
try {
this.store_.deleteIndex(name);
} catch (ex) {
var msg = 'deleting index ' + name;
throw goog.db.Error.fromException(ex, msg);
}
};
/**
* Gets number of records within a key range.
*
* @param {!goog.db.KeyRange=} opt_range The key range. If undefined, this will
* count all records in the object store.
* @return {!goog.async.Deferred} The deferred number of records.
*/
goog.db.ObjectStore.prototype.count = function(opt_range) {
var request;
var d = new goog.async.Deferred();
try {
var range = opt_range ? opt_range.range() : null;
request = this.store_.count(range);
} catch (ex) {
d.errback(goog.db.Error.fromException(ex, this.getName()));
}
request.onsuccess = function(ev) {
d.callback(ev.target.result);
};
request.onerror = function(ev) {
d.errback(goog.db.Error.fromRequest(ev.target, this.getName()));
};
return d;
};

View File

@@ -0,0 +1,218 @@
// Copyright 2011 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 Wrapper for an IndexedDB transaction.
*
*/
goog.provide('goog.db.Transaction');
goog.provide('goog.db.Transaction.TransactionMode');
goog.require('goog.async.Deferred');
goog.require('goog.db.Error');
goog.require('goog.db.ObjectStore');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventTarget');
/**
* Creates a new transaction. Transactions contain methods for accessing object
* stores and are created from the database object. Should not be created
* directly, open a database and call createTransaction on it.
* @see goog.db.IndexedDb#createTransaction
*
* @param {!IDBTransaction} tx IndexedDB transaction to back this wrapper.
* @param {!goog.db.IndexedDb} db The database that this transaction modifies.
* @constructor
* @extends {goog.events.EventTarget}
*/
goog.db.Transaction = function(tx, db) {
goog.base(this);
/**
* Underlying IndexedDB transaction object.
*
* @type {!IDBTransaction}
* @private
*/
this.tx_ = tx;
/**
* The database that this transaction modifies.
*
* @type {!goog.db.IndexedDb}
* @private
*/
this.db_ = db;
/**
* Event handler for this transaction.
*
* @type {!goog.events.EventHandler}
* @private
*/
this.eventHandler_ = new goog.events.EventHandler(this);
// TODO(user): remove these casts once the externs file is updated to
// correctly reflect that IDBTransaction extends EventTarget
this.eventHandler_.listen(
/** @type {EventTarget} */ (this.tx_),
'complete',
goog.bind(
this.dispatchEvent,
this,
goog.db.Transaction.EventTypes.COMPLETE));
this.eventHandler_.listen(
/** @type {EventTarget} */ (this.tx_),
'abort',
goog.bind(
this.dispatchEvent,
this,
goog.db.Transaction.EventTypes.ABORT));
this.eventHandler_.listen(
/** @type {EventTarget} */ (this.tx_),
'error',
this.dispatchError_);
};
goog.inherits(goog.db.Transaction, goog.events.EventTarget);
/**
* Dispatches an error event based on the given event, wrapping the error
* if necessary.
*
* @param {Event} ev The error event given to the underlying IDBTransaction.
* @private
*/
goog.db.Transaction.prototype.dispatchError_ = function(ev) {
if (ev.target instanceof goog.db.Error) {
this.dispatchEvent({
type: goog.db.Transaction.EventTypes.ERROR,
target: ev.target
});
} else {
this.dispatchEvent({
type: goog.db.Transaction.EventTypes.ERROR,
target: goog.db.Error.fromRequest(
/** @type {!IDBRequest} */ (ev.target), 'in transaction')
});
}
};
/**
* Event types the Transaction can dispatch. COMPLETE events are dispatched
* when the transaction is committed. If a transaction is aborted it dispatches
* both an ABORT event and an ERROR event with the ABORT_ERR code. Error events
* are dispatched on any error.
*
* @enum {string}
*/
goog.db.Transaction.EventTypes = {
COMPLETE: 'complete',
ABORT: 'abort',
ERROR: 'error'
};
/**
* @return {goog.db.Transaction.TransactionMode} The transaction's mode.
*/
goog.db.Transaction.prototype.getMode = function() {
return /** @type {goog.db.Transaction.TransactionMode} */ (this.tx_.mode);
};
/**
* @return {!goog.db.IndexedDb} The database that this transaction modifies.
*/
goog.db.Transaction.prototype.getDatabase = function() {
return this.db_;
};
/**
* Opens an object store to do operations on in this transaction. The requested
* object store must be one that is in this transaction's scope.
* @see goog.db.IndexedDb#createTransaction
*
* @param {string} name The name of the requested object store.
* @return {!goog.db.ObjectStore} The wrapped object store.
* @throws {goog.db.Error} In case of error getting the object store.
*/
goog.db.Transaction.prototype.objectStore = function(name) {
try {
return new goog.db.ObjectStore(this.tx_.objectStore(name));
} catch (ex) {
throw goog.db.Error.fromException(ex, 'getting object store ' + name);
}
};
/**
* @return {!goog.async.Deferred} A deferred that will fire once the
* transaction is complete. It fires the errback chain if an error occurs
* in the transaction, or if it is aborted.
*/
goog.db.Transaction.prototype.wait = function() {
var d = new goog.async.Deferred();
goog.events.listenOnce(
this, goog.db.Transaction.EventTypes.COMPLETE, goog.bind(d.callback, d));
goog.events.listenOnce(
this, goog.db.Transaction.EventTypes.ABORT, function() {
d.errback(new goog.db.Error(goog.db.Error.ErrorCode.ABORT_ERR,
'waiting for transaction to complete'));
});
goog.events.listenOnce(
this, goog.db.Transaction.EventTypes.ERROR, function(e) {
d.errback(e.target);
});
var db = this.getDatabase();
return d.addCallback(function() {
return db;
});
};
/**
* Aborts this transaction. No pending operations will be applied to the
* database. Dispatches an ABORT event.
*/
goog.db.Transaction.prototype.abort = function() {
this.tx_.abort();
};
/** @override */
goog.db.Transaction.prototype.disposeInternal = function() {
goog.base(this, 'disposeInternal');
this.eventHandler_.dispose();
};
/**
* The three possible transaction modes.
* @see http://www.w3.org/TR/IndexedDB/#idl-def-IDBTransaction
*
* @enum {string}
*/
goog.db.Transaction.TransactionMode = {
READ_ONLY: 'readonly',
READ_WRITE: 'readwrite',
VERSION_CHANGE: 'versionchange'
};

View File

@@ -0,0 +1,207 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Simple logger that logs to the window console if available.
*
* Has an autoInstall option which can be put into initialization code, which
* will start logging if "Debug=true" is in document.location.href
*
*/
goog.provide('goog.debug.Console');
goog.require('goog.debug.LogManager');
goog.require('goog.debug.Logger.Level');
goog.require('goog.debug.TextFormatter');
/**
* Create and install a log handler that logs to window.console if available
* @constructor
*/
goog.debug.Console = function() {
this.publishHandler_ = goog.bind(this.addLogRecord, this);
/**
* Formatter for formatted output.
* @type {!goog.debug.TextFormatter}
* @private
*/
this.formatter_ = new goog.debug.TextFormatter();
this.formatter_.showAbsoluteTime = false;
this.formatter_.showExceptionText = false;
this.isCapturing_ = false;
this.logBuffer_ = '';
/**
* Loggers that we shouldn't output.
* @type {!Object.<boolean>}
* @private
*/
this.filteredLoggers_ = {};
};
/**
* Returns the text formatter used by this console
* @return {!goog.debug.TextFormatter} The text formatter.
*/
goog.debug.Console.prototype.getFormatter = function() {
return this.formatter_;
};
/**
* Sets whether we are currently capturing logger output.
* @param {boolean} capturing Whether to capture logger output.
*/
goog.debug.Console.prototype.setCapturing = function(capturing) {
if (capturing == this.isCapturing_) {
return;
}
// attach or detach handler from the root logger
var rootLogger = goog.debug.LogManager.getRoot();
if (capturing) {
rootLogger.addHandler(this.publishHandler_);
} else {
rootLogger.removeHandler(this.publishHandler_);
this.logBuffer = '';
}
this.isCapturing_ = capturing;
};
/**
* Adds a log record.
* @param {goog.debug.LogRecord} logRecord The log entry.
*/
goog.debug.Console.prototype.addLogRecord = function(logRecord) {
// Check to see if the log record is filtered or not.
if (this.filteredLoggers_[logRecord.getLoggerName()]) {
return;
}
var record = this.formatter_.formatRecord(logRecord);
var console = goog.debug.Console.console_;
if (console) {
switch (logRecord.getLevel()) {
case goog.debug.Logger.Level.SHOUT:
goog.debug.Console.logToConsole_(console, 'info', record);
break;
case goog.debug.Logger.Level.SEVERE:
goog.debug.Console.logToConsole_(console, 'error', record);
break;
case goog.debug.Logger.Level.WARNING:
goog.debug.Console.logToConsole_(console, 'warn', record);
break;
default:
goog.debug.Console.logToConsole_(console, 'debug', record);
break;
}
} else if (window.opera) {
// window.opera.postError is considered an undefined property reference
// by JSCompiler, so it has to be referenced using array notation instead.
window.opera['postError'](record);
} else {
this.logBuffer_ += record;
}
};
/**
* Adds a logger name to be filtered.
* @param {string} loggerName the logger name to add.
*/
goog.debug.Console.prototype.addFilter = function(loggerName) {
this.filteredLoggers_[loggerName] = true;
};
/**
* Removes a logger name to be filtered.
* @param {string} loggerName the logger name to remove.
*/
goog.debug.Console.prototype.removeFilter = function(loggerName) {
delete this.filteredLoggers_[loggerName];
};
/**
* Global console logger instance
* @type {goog.debug.Console}
*/
goog.debug.Console.instance = null;
/**
* The console to which to log. This is a property so it can be mocked out in
* this unit test for goog.debug.Console.
* @type {Object}
* @private
*/
goog.debug.Console.console_ = window.console;
/**
* Sets the console to which to log.
* @param {!Object} console The console to which to log.
*/
goog.debug.Console.setConsole = function(console) {
goog.debug.Console.console_ = console;
};
/**
* Install the console and start capturing if "Debug=true" is in the page URL
*/
goog.debug.Console.autoInstall = function() {
if (!goog.debug.Console.instance) {
goog.debug.Console.instance = new goog.debug.Console();
}
if (window.location.href.indexOf('Debug=true') != -1) {
goog.debug.Console.instance.setCapturing(true);
}
};
/**
* Show an alert with all of the captured debug information.
* Information is only captured if console is not available
*/
goog.debug.Console.show = function() {
alert(goog.debug.Console.instance.logBuffer_);
};
/**
* Logs the record to the console using the given function. If the function is
* not available on the console object, the log function is used instead.
* @param {!Object} console The console object.
* @param {string} fnName The name of the function to use.
* @param {string} record The record to log.
* @private
*/
goog.debug.Console.logToConsole_ = function(console, fnName, record) {
if (console[fnName]) {
console[fnName](record);
} else {
console.log(record);
}
};

View File

@@ -0,0 +1,501 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Logging and debugging utilities.
*
* @see ../demos/debug.html
*/
goog.provide('goog.debug');
goog.require('goog.array');
goog.require('goog.string');
goog.require('goog.structs.Set');
goog.require('goog.userAgent');
/** @define {boolean} Whether logging should be enabled. */
goog.define('goog.debug.LOGGING_ENABLED', goog.DEBUG);
/**
* Catches onerror events fired by windows and similar objects.
* @param {function(Object)} logFunc The function to call with the error
* information.
* @param {boolean=} opt_cancel Whether to stop the error from reaching the
* browser.
* @param {Object=} opt_target Object that fires onerror events.
*/
goog.debug.catchErrors = function(logFunc, opt_cancel, opt_target) {
var target = opt_target || goog.global;
var oldErrorHandler = target.onerror;
var retVal = !!opt_cancel;
// Chrome interprets onerror return value backwards (http://crbug.com/92062)
// until it was fixed in webkit revision r94061 (Webkit 535.3). This
// workaround still needs to be skipped in Safari after the webkit change
// gets pushed out in Safari.
// See https://bugs.webkit.org/show_bug.cgi?id=67119
if (goog.userAgent.WEBKIT &&
!goog.userAgent.isVersionOrHigher('535.3')) {
retVal = !retVal;
}
target.onerror = function(message, url, line) {
if (oldErrorHandler) {
oldErrorHandler(message, url, line);
}
logFunc({
message: message,
fileName: url,
line: line
});
return retVal;
};
};
/**
* Creates a string representing an object and all its properties.
* @param {Object|null|undefined} obj Object to expose.
* @param {boolean=} opt_showFn Show the functions as well as the properties,
* default is false.
* @return {string} The string representation of {@code obj}.
*/
goog.debug.expose = function(obj, opt_showFn) {
if (typeof obj == 'undefined') {
return 'undefined';
}
if (obj == null) {
return 'NULL';
}
var str = [];
for (var x in obj) {
if (!opt_showFn && goog.isFunction(obj[x])) {
continue;
}
var s = x + ' = ';
/** @preserveTry */
try {
s += obj[x];
} catch (e) {
s += '*** ' + e + ' ***';
}
str.push(s);
}
return str.join('\n');
};
/**
* Creates a string representing a given primitive or object, and for an
* object, all its properties and nested objects. WARNING: If an object is
* given, it and all its nested objects will be modified. To detect reference
* cycles, this method identifies objects using goog.getUid() which mutates the
* object.
* @param {*} obj Object to expose.
* @param {boolean=} opt_showFn Also show properties that are functions (by
* default, functions are omitted).
* @return {string} A string representation of {@code obj}.
*/
goog.debug.deepExpose = function(obj, opt_showFn) {
var previous = new goog.structs.Set();
var str = [];
var helper = function(obj, space) {
var nestspace = space + ' ';
var indentMultiline = function(str) {
return str.replace(/\n/g, '\n' + space);
};
/** @preserveTry */
try {
if (!goog.isDef(obj)) {
str.push('undefined');
} else if (goog.isNull(obj)) {
str.push('NULL');
} else if (goog.isString(obj)) {
str.push('"' + indentMultiline(obj) + '"');
} else if (goog.isFunction(obj)) {
str.push(indentMultiline(String(obj)));
} else if (goog.isObject(obj)) {
if (previous.contains(obj)) {
// TODO(user): This is a bug; it falsely detects non-loops as loops
// when the reference tree contains two references to the same object.
str.push('*** reference loop detected ***');
} else {
previous.add(obj);
str.push('{');
for (var x in obj) {
if (!opt_showFn && goog.isFunction(obj[x])) {
continue;
}
str.push('\n');
str.push(nestspace);
str.push(x + ' = ');
helper(obj[x], nestspace);
}
str.push('\n' + space + '}');
}
} else {
str.push(obj);
}
} catch (e) {
str.push('*** ' + e + ' ***');
}
};
helper(obj, '');
return str.join('');
};
/**
* Recursively outputs a nested array as a string.
* @param {Array} arr The array.
* @return {string} String representing nested array.
*/
goog.debug.exposeArray = function(arr) {
var str = [];
for (var i = 0; i < arr.length; i++) {
if (goog.isArray(arr[i])) {
str.push(goog.debug.exposeArray(arr[i]));
} else {
str.push(arr[i]);
}
}
return '[ ' + str.join(', ') + ' ]';
};
/**
* Exposes an exception that has been caught by a try...catch and outputs the
* error with a stack trace.
* @param {Object} err Error object or string.
* @param {Function=} opt_fn Optional function to start stack trace from.
* @return {string} Details of exception.
*/
goog.debug.exposeException = function(err, opt_fn) {
/** @preserveTry */
try {
var e = goog.debug.normalizeErrorObject(err);
// Create the error message
var error = 'Message: ' + goog.string.htmlEscape(e.message) +
'\nUrl: <a href="view-source:' + e.fileName + '" target="_new">' +
e.fileName + '</a>\nLine: ' + e.lineNumber + '\n\nBrowser stack:\n' +
goog.string.htmlEscape(e.stack + '-> ') +
'[end]\n\nJS stack traversal:\n' + goog.string.htmlEscape(
goog.debug.getStacktrace(opt_fn) + '-> ');
return error;
} catch (e2) {
return 'Exception trying to expose exception! You win, we lose. ' + e2;
}
};
/**
* Normalizes the error/exception object between browsers.
* @param {Object} err Raw error object.
* @return {Object} Normalized error object.
*/
goog.debug.normalizeErrorObject = function(err) {
var href = goog.getObjectByName('window.location.href');
if (goog.isString(err)) {
return {
'message': err,
'name': 'Unknown error',
'lineNumber': 'Not available',
'fileName': href,
'stack': 'Not available'
};
}
var lineNumber, fileName;
var threwError = false;
try {
lineNumber = err.lineNumber || err.line || 'Not available';
} catch (e) {
// Firefox 2 sometimes throws an error when accessing 'lineNumber':
// Message: Permission denied to get property UnnamedClass.lineNumber
lineNumber = 'Not available';
threwError = true;
}
try {
fileName = err.fileName || err.filename || err.sourceURL ||
// $googDebugFname may be set before a call to eval to set the filename
// that the eval is supposed to present.
goog.global['$googDebugFname'] || href;
} catch (e) {
// Firefox 2 may also throw an error when accessing 'filename'.
fileName = 'Not available';
threwError = true;
}
// The IE Error object contains only the name and the message.
// The Safari Error object uses the line and sourceURL fields.
if (threwError || !err.lineNumber || !err.fileName || !err.stack ||
!err.message || !err.name) {
return {
'message': err.message || 'Not available',
'name': err.name || 'UnknownError',
'lineNumber': lineNumber,
'fileName': fileName,
'stack': err.stack || 'Not available'
};
}
// Standards error object
return err;
};
/**
* Converts an object to an Error if it's a String,
* adds a stacktrace if there isn't one,
* and optionally adds an extra message.
* @param {Error|string} err the original thrown object or string.
* @param {string=} opt_message optional additional message to add to the
* error.
* @return {Error} If err is a string, it is used to create a new Error,
* which is enhanced and returned. Otherwise err itself is enhanced
* and returned.
*/
goog.debug.enhanceError = function(err, opt_message) {
var error = typeof err == 'string' ? Error(err) : err;
if (!error.stack) {
error.stack = goog.debug.getStacktrace(arguments.callee.caller);
}
if (opt_message) {
// find the first unoccupied 'messageX' property
var x = 0;
while (error['message' + x]) {
++x;
}
error['message' + x] = String(opt_message);
}
return error;
};
/**
* Gets the current stack trace. Simple and iterative - doesn't worry about
* catching circular references or getting the args.
* @param {number=} opt_depth Optional maximum depth to trace back to.
* @return {string} A string with the function names of all functions in the
* stack, separated by \n.
*/
goog.debug.getStacktraceSimple = function(opt_depth) {
var sb = [];
var fn = arguments.callee.caller;
var depth = 0;
while (fn && (!opt_depth || depth < opt_depth)) {
sb.push(goog.debug.getFunctionName(fn));
sb.push('()\n');
/** @preserveTry */
try {
fn = fn.caller;
} catch (e) {
sb.push('[exception trying to get caller]\n');
break;
}
depth++;
if (depth >= goog.debug.MAX_STACK_DEPTH) {
sb.push('[...long stack...]');
break;
}
}
if (opt_depth && depth >= opt_depth) {
sb.push('[...reached max depth limit...]');
} else {
sb.push('[end]');
}
return sb.join('');
};
/**
* Max length of stack to try and output
* @type {number}
*/
goog.debug.MAX_STACK_DEPTH = 50;
/**
* Gets the current stack trace, either starting from the caller or starting
* from a specified function that's currently on the call stack.
* @param {Function=} opt_fn Optional function to start getting the trace from.
* If not provided, defaults to the function that called this.
* @return {string} Stack trace.
*/
goog.debug.getStacktrace = function(opt_fn) {
return goog.debug.getStacktraceHelper_(opt_fn || arguments.callee.caller, []);
};
/**
* Private helper for getStacktrace().
* @param {Function} fn Function to start getting the trace from.
* @param {Array} visited List of functions visited so far.
* @return {string} Stack trace starting from function fn.
* @private
*/
goog.debug.getStacktraceHelper_ = function(fn, visited) {
var sb = [];
// Circular reference, certain functions like bind seem to cause a recursive
// loop so we need to catch circular references
if (goog.array.contains(visited, fn)) {
sb.push('[...circular reference...]');
// Traverse the call stack until function not found or max depth is reached
} else if (fn && visited.length < goog.debug.MAX_STACK_DEPTH) {
sb.push(goog.debug.getFunctionName(fn) + '(');
var args = fn.arguments;
for (var i = 0; i < args.length; i++) {
if (i > 0) {
sb.push(', ');
}
var argDesc;
var arg = args[i];
switch (typeof arg) {
case 'object':
argDesc = arg ? 'object' : 'null';
break;
case 'string':
argDesc = arg;
break;
case 'number':
argDesc = String(arg);
break;
case 'boolean':
argDesc = arg ? 'true' : 'false';
break;
case 'function':
argDesc = goog.debug.getFunctionName(arg);
argDesc = argDesc ? argDesc : '[fn]';
break;
case 'undefined':
default:
argDesc = typeof arg;
break;
}
if (argDesc.length > 40) {
argDesc = argDesc.substr(0, 40) + '...';
}
sb.push(argDesc);
}
visited.push(fn);
sb.push(')\n');
/** @preserveTry */
try {
sb.push(goog.debug.getStacktraceHelper_(fn.caller, visited));
} catch (e) {
sb.push('[exception trying to get caller]\n');
}
} else if (fn) {
sb.push('[...long stack...]');
} else {
sb.push('[end]');
}
return sb.join('');
};
/**
* Set a custom function name resolver.
* @param {function(Function): string} resolver Resolves functions to their
* names.
*/
goog.debug.setFunctionResolver = function(resolver) {
goog.debug.fnNameResolver_ = resolver;
};
/**
* Gets a function name
* @param {Function} fn Function to get name of.
* @return {string} Function's name.
*/
goog.debug.getFunctionName = function(fn) {
if (goog.debug.fnNameCache_[fn]) {
return goog.debug.fnNameCache_[fn];
}
if (goog.debug.fnNameResolver_) {
var name = goog.debug.fnNameResolver_(fn);
if (name) {
goog.debug.fnNameCache_[fn] = name;
return name;
}
}
// Heuristically determine function name based on code.
var functionSource = String(fn);
if (!goog.debug.fnNameCache_[functionSource]) {
var matches = /function ([^\(]+)/.exec(functionSource);
if (matches) {
var method = matches[1];
goog.debug.fnNameCache_[functionSource] = method;
} else {
goog.debug.fnNameCache_[functionSource] = '[Anonymous]';
}
}
return goog.debug.fnNameCache_[functionSource];
};
/**
* Makes whitespace visible by replacing it with printable characters.
* This is useful in finding diffrences between the expected and the actual
* output strings of a testcase.
* @param {string} string whose whitespace needs to be made visible.
* @return {string} string whose whitespace is made visible.
*/
goog.debug.makeWhitespaceVisible = function(string) {
return string.replace(/ /g, '[_]')
.replace(/\f/g, '[f]')
.replace(/\n/g, '[n]\n')
.replace(/\r/g, '[r]')
.replace(/\t/g, '[t]');
};
/**
* Hash map for storing function names that have already been looked up.
* @type {Object}
* @private
*/
goog.debug.fnNameCache_ = {};
/**
* Resolves functions to their names. Resolved function names will be cached.
* @type {function(Function):string}
* @private
*/
goog.debug.fnNameResolver_;

View File

@@ -0,0 +1,615 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition of the DebugWindow class. Please minimize
* dependencies this file has on other closure classes as any dependency it
* takes won't be able to use the logging infrastructure.
*
*/
goog.provide('goog.debug.DebugWindow');
goog.require('goog.debug.HtmlFormatter');
goog.require('goog.debug.LogManager');
goog.require('goog.debug.Logger');
goog.require('goog.structs.CircularBuffer');
goog.require('goog.userAgent');
/**
* Provides a debug DebugWindow that is bound to the goog.debug.Logger.
* It handles log messages and writes them to the DebugWindow. This doesn't
* provide a lot of functionality that the old Gmail logging infrastructure
* provided like saving debug logs for exporting to the server. Now that we
* have an event-based logging infrastructure, we can encapsulate that
* functionality in a separate class.
*
* @constructor
* @param {string=} opt_identifier Identifier for this logging class.
* @param {string=} opt_prefix Prefix prepended to messages.
*/
goog.debug.DebugWindow = function(opt_identifier, opt_prefix) {
/**
* Identifier for this logging class
* @protected {string}
*/
this.identifier = opt_identifier || '';
/**
* Array used to buffer log output
* @protected {!Array}
*/
this.outputBuffer = [];
/**
* Optional prefix to be prepended to error strings
* @private {string}
*/
this.prefix_ = opt_prefix || '';
/**
* Buffer for saving the last 1000 messages
* @private {!goog.structs.CircularBuffer}
*/
this.savedMessages_ =
new goog.structs.CircularBuffer(goog.debug.DebugWindow.MAX_SAVED);
/**
* Save the publish handler so it can be removed
* @private {!Function}
*/
this.publishHandler_ = goog.bind(this.addLogRecord, this);
/**
* Formatter for formatted output
* @private {goog.debug.Formatter}
*/
this.formatter_ = new goog.debug.HtmlFormatter(this.prefix_);
/**
* Loggers that we shouldn't output
* @private {!Object}
*/
this.filteredLoggers_ = {};
// enable by default
this.setCapturing(true);
/**
* Whether we are currently enabled. When the DebugWindow is enabled, it tries
* to keep its window open. When it's disabled, it can still be capturing log
* output if, but it won't try to write them to the DebugWindow window until
* it's enabled.
* @private {boolean}
*/
this.enabled_ = goog.debug.DebugWindow.isEnabled(this.identifier);
// timer to save the DebugWindow's window position in a cookie
goog.global.setInterval(goog.bind(this.saveWindowPositionSize_, this), 7500);
};
/**
* Max number of messages to be saved
* @type {number}
*/
goog.debug.DebugWindow.MAX_SAVED = 500;
/**
* How long to keep the cookies for in milliseconds
* @type {number}
*/
goog.debug.DebugWindow.COOKIE_TIME = 30 * 24 * 60 * 60 * 1000; // 30-days
/**
* HTML string printed when the debug window opens
* @type {string}
* @protected
*/
goog.debug.DebugWindow.prototype.welcomeMessage = 'LOGGING';
/**
* Whether to force enable the window on a severe log.
* @type {boolean}
* @private
*/
goog.debug.DebugWindow.prototype.enableOnSevere_ = false;
/**
* Reference to debug window
* @type {Window}
* @protected
*/
goog.debug.DebugWindow.prototype.win = null;
/**
* In the process of opening the window
* @type {boolean}
* @private
*/
goog.debug.DebugWindow.prototype.winOpening_ = false;
/**
* Whether we are currently capturing logger output.
*
* @type {boolean}
* @private
*/
goog.debug.DebugWindow.prototype.isCapturing_ = false;
/**
* Whether we already showed an alert that the DebugWindow was blocked.
* @type {boolean}
* @private
*/
goog.debug.DebugWindow.showedBlockedAlert_ = false;
/**
* Reference to timeout used to buffer the output stream.
* @type {?number}
* @private
*/
goog.debug.DebugWindow.prototype.bufferTimeout_ = null;
/**
* Timestamp for the last time the log was written to.
* @protected {number}
*/
goog.debug.DebugWindow.prototype.lastCall = goog.now();
/**
* Sets the welcome message shown when the window is first opened or reset.
*
* @param {string} msg An HTML string.
*/
goog.debug.DebugWindow.prototype.setWelcomeMessage = function(msg) {
this.welcomeMessage = msg;
};
/**
* Initializes the debug window.
*/
goog.debug.DebugWindow.prototype.init = function() {
if (this.enabled_) {
this.openWindow_();
}
};
/**
* Whether the DebugWindow is enabled. When the DebugWindow is enabled, it
* tries to keep its window open and logs all messages to the window. When the
* DebugWindow is disabled, it stops logging messages to its window.
*
* @return {boolean} Whether the DebugWindow is enabled.
*/
goog.debug.DebugWindow.prototype.isEnabled = function() {
return this.enabled_;
};
/**
* Sets whether the DebugWindow is enabled. When the DebugWindow is enabled, it
* tries to keep its window open and log all messages to the window. When the
* DebugWindow is disabled, it stops logging messages to its window. The
* DebugWindow also saves this state to a cookie so that it's persisted across
* application refreshes.
* @param {boolean} enable Whether the DebugWindow is enabled.
*/
goog.debug.DebugWindow.prototype.setEnabled = function(enable) {
this.enabled_ = enable;
if (this.enabled_) {
this.openWindow_();
}
this.setCookie_('enabled', enable ? '1' : '0');
};
/**
* Sets whether the debug window should be force enabled when a severe log is
* encountered.
* @param {boolean} enableOnSevere Whether to enable on severe logs..
*/
goog.debug.DebugWindow.prototype.setForceEnableOnSevere =
function(enableOnSevere) {
this.enableOnSevere_ = enableOnSevere;
};
/**
* Whether we are currently capturing logger output.
* @return {boolean} whether we are currently capturing logger output.
*/
goog.debug.DebugWindow.prototype.isCapturing = function() {
return this.isCapturing_;
};
/**
* Sets whether we are currently capturing logger output.
* @param {boolean} capturing Whether to capture logger output.
*/
goog.debug.DebugWindow.prototype.setCapturing = function(capturing) {
if (capturing == this.isCapturing_) {
return;
}
this.isCapturing_ = capturing;
// attach or detach handler from the root logger
var rootLogger = goog.debug.LogManager.getRoot();
if (capturing) {
rootLogger.addHandler(this.publishHandler_);
} else {
rootLogger.removeHandler(this.publishHandler_);
}
};
/**
* Gets the formatter for outputting to the debug window. The default formatter
* is an instance of goog.debug.HtmlFormatter
* @return {goog.debug.Formatter} The formatter in use.
*/
goog.debug.DebugWindow.prototype.getFormatter = function() {
return this.formatter_;
};
/**
* Sets the formatter for outputting to the debug window.
* @param {goog.debug.Formatter} formatter The formatter to use.
*/
goog.debug.DebugWindow.prototype.setFormatter = function(formatter) {
this.formatter_ = formatter;
};
/**
* Adds a separator to the debug window.
*/
goog.debug.DebugWindow.prototype.addSeparator = function() {
this.write_('<hr>');
};
/**
* @return {boolean} Whether there is an active window.
*/
goog.debug.DebugWindow.prototype.hasActiveWindow = function() {
return !!this.win && !this.win.closed;
};
/**
* Clears the contents of the debug window
* @protected
*/
goog.debug.DebugWindow.prototype.clear = function() {
this.savedMessages_.clear();
if (this.hasActiveWindow()) {
this.writeInitialDocument();
}
};
/**
* Adds a log record.
* @param {goog.debug.LogRecord} logRecord the LogRecord.
*/
goog.debug.DebugWindow.prototype.addLogRecord = function(logRecord) {
if (this.filteredLoggers_[logRecord.getLoggerName()]) {
return;
}
var html = this.formatter_.formatRecord(logRecord);
this.write_(html);
if (this.enableOnSevere_ &&
logRecord.getLevel().value >= goog.debug.Logger.Level.SEVERE.value) {
this.setEnabled(true);
}
};
/**
* Writes a message to the log, possibly opening up the window if it's enabled,
* or saving it if it's disabled.
* @param {string} html The HTML to write.
* @private
*/
goog.debug.DebugWindow.prototype.write_ = function(html) {
// If the logger is enabled, open window and write html message to log
// otherwise save it
if (this.enabled_) {
this.openWindow_();
this.savedMessages_.add(html);
this.writeToLog_(html);
} else {
this.savedMessages_.add(html);
}
};
/**
* Write to the buffer. If a message hasn't been sent for more than 750ms just
* write, otherwise delay for a minimum of 250ms.
* @param {string} html HTML to post to the log.
* @private
*/
goog.debug.DebugWindow.prototype.writeToLog_ = function(html) {
this.outputBuffer.push(html);
goog.global.clearTimeout(this.bufferTimeout_);
if (goog.now() - this.lastCall > 750) {
this.writeBufferToLog();
} else {
this.bufferTimeout_ =
goog.global.setTimeout(goog.bind(this.writeBufferToLog, this), 250);
}
};
/**
* Write to the log and maybe scroll into view.
* @protected
*/
goog.debug.DebugWindow.prototype.writeBufferToLog = function() {
this.lastCall = goog.now();
if (this.hasActiveWindow()) {
var body = this.win.document.body;
var scroll = body &&
body.scrollHeight - (body.scrollTop + body.clientHeight) <= 100;
this.win.document.write(this.outputBuffer.join(''));
this.outputBuffer.length = 0;
if (scroll) {
this.win.scrollTo(0, 1000000);
}
}
};
/**
* Writes all saved messages to the DebugWindow.
* @protected
*/
goog.debug.DebugWindow.prototype.writeSavedMessages = function() {
var messages = this.savedMessages_.getValues();
for (var i = 0; i < messages.length; i++) {
this.writeToLog_(messages[i]);
}
};
/**
* Opens the debug window if it is not already referenced
* @private
*/
goog.debug.DebugWindow.prototype.openWindow_ = function() {
if (this.hasActiveWindow() || this.winOpening_) {
return;
}
var winpos = this.getCookie_('dbg', '0,0,800,500').split(',');
var x = Number(winpos[0]);
var y = Number(winpos[1]);
var w = Number(winpos[2]);
var h = Number(winpos[3]);
this.winOpening_ = true;
this.win = window.open('', this.getWindowName_(), 'width=' + w +
',height=' + h + ',toolbar=no,resizable=yes,' +
'scrollbars=yes,left=' + x + ',top=' + y +
',status=no,screenx=' + x + ',screeny=' + y);
if (!this.win) {
if (!this.showedBlockedAlert_) {
// only show this once
alert('Logger popup was blocked');
this.showedBlockedAlert_ = true;
}
}
this.winOpening_ = false;
if (this.win) {
this.writeInitialDocument();
}
};
/**
* Gets a valid window name for the debug window. Replaces invalid characters in
* IE.
* @return {string} Valid window name.
* @private
*/
goog.debug.DebugWindow.prototype.getWindowName_ = function() {
return goog.userAgent.IE ?
this.identifier.replace(/[\s\-\.\,]/g, '_') : this.identifier;
};
/**
* @return {string} The style rule text, for inclusion in the initial HTML.
*/
goog.debug.DebugWindow.prototype.getStyleRules = function() {
return '*{font:normal 14px monospace;}' +
'.dbg-sev{color:#F00}' +
'.dbg-w{color:#E92}' +
'.dbg-sh{background-color:#fd4;font-weight:bold;color:#000}' +
'.dbg-i{color:#666}' +
'.dbg-f{color:#999}' +
'.dbg-ev{color:#0A0}' +
'.dbg-m{color:#990}';
};
/**
* Writes the initial HTML of the debug window.
* @protected
*/
goog.debug.DebugWindow.prototype.writeInitialDocument = function() {
if (!this.hasActiveWindow()) {
return;
}
this.win.document.open();
var html = '<style>' + this.getStyleRules() + '</style>' +
'<hr><div class="dbg-ev" style="text-align:center">' +
this.welcomeMessage + '<br><small>Logger: ' +
this.identifier + '</small></div><hr>';
this.writeToLog_(html);
this.writeSavedMessages();
};
/**
* Save persistent data (using cookies) for 1 month (cookie specific to this
* logger object).
* @param {string} key Data name.
* @param {string} value Data value.
* @private
*/
goog.debug.DebugWindow.prototype.setCookie_ = function(key, value) {
var fullKey = goog.debug.DebugWindow.getCookieKey_(this.identifier, key);
document.cookie = fullKey + '=' + encodeURIComponent(value) +
';path=/;expires=' +
(new Date(goog.now() + goog.debug.DebugWindow.COOKIE_TIME)).toUTCString();
};
/**
* Retrieve data (using cookies).
* @param {string} key Data name.
* @param {string=} opt_default Optional default value if cookie doesn't exist.
* @return {string} Cookie value.
* @private
*/
goog.debug.DebugWindow.prototype.getCookie_ = function(key, opt_default) {
return goog.debug.DebugWindow.getCookieValue_(
this.identifier, key, opt_default);
};
/**
* Creates a valid cookie key name which is scoped to the given identifier.
* Substitutes all occurences of invalid cookie name characters (whitespace,
* ';', and '=') with '_', which is a valid and readable alternative.
* @see goog.net.Cookies#isValidName
* @see <a href="http://tools.ietf.org/html/rfc2109">RFC 2109</a>
* @param {string} identifier Identifier for logging class.
* @param {string} key Data name.
* @return {string} Cookie key name.
* @private
*/
goog.debug.DebugWindow.getCookieKey_ = function(identifier, key) {
var fullKey = key + identifier;
return fullKey.replace(/[;=\s]/g, '_');
};
/**
* Retrieve data (using cookies).
* @param {string} identifier Identifier for logging class.
* @param {string} key Data name.
* @param {string=} opt_default Optional default value if cookie doesn't exist.
* @return {string} Cookie value.
* @private
*/
goog.debug.DebugWindow.getCookieValue_ = function(
identifier, key, opt_default) {
var fullKey = goog.debug.DebugWindow.getCookieKey_(identifier, key);
var cookie = String(document.cookie);
var start = cookie.indexOf(fullKey + '=');
if (start != -1) {
var end = cookie.indexOf(';', start);
return decodeURIComponent(cookie.substring(start + fullKey.length + 1,
end == -1 ? cookie.length : end));
} else {
return opt_default || '';
}
};
/**
* @param {string} identifier Identifier for logging class.
* @return {boolean} Whether the DebugWindow is enabled.
*/
goog.debug.DebugWindow.isEnabled = function(identifier) {
return goog.debug.DebugWindow.getCookieValue_(identifier, 'enabled') == '1';
};
/**
* Saves the window position size to a cookie
* @private
*/
goog.debug.DebugWindow.prototype.saveWindowPositionSize_ = function() {
if (!this.hasActiveWindow()) {
return;
}
var x = this.win.screenX || this.win.screenLeft || 0;
var y = this.win.screenY || this.win.screenTop || 0;
var w = this.win.outerWidth || 800;
var h = this.win.outerHeight || 500;
this.setCookie_('dbg', x + ',' + y + ',' + w + ',' + h);
};
/**
* Adds a logger name to be filtered.
* @param {string} loggerName the logger name to add.
*/
goog.debug.DebugWindow.prototype.addFilter = function(loggerName) {
this.filteredLoggers_[loggerName] = 1;
};
/**
* Removes a logger name to be filtered.
* @param {string} loggerName the logger name to remove.
*/
goog.debug.DebugWindow.prototype.removeFilter = function(loggerName) {
delete this.filteredLoggers_[loggerName];
};
/**
* Modify the size of the circular buffer. Allows the log to retain more
* information while the window is closed.
* @param {number} size New size of the circular buffer.
*/
goog.debug.DebugWindow.prototype.resetBufferWithNewSize = function(size) {
if (size > 0 && size < 50000) {
this.clear();
this.savedMessages_ = new goog.structs.CircularBuffer(size);
}
};

View File

@@ -0,0 +1,444 @@
// Copyright 2008 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 Runtime development CSS Compiler emulation, via javascript.
* This class provides an approximation to CSSCompiler's functionality by
* hacking the live CSSOM.
* This code is designed to be inserted in the DOM immediately after the last
* style block in HEAD when in development mode, i.e. you are not using a
* running instance of a CSS Compiler to pass your CSS through.
*/
goog.provide('goog.debug.DevCss');
goog.provide('goog.debug.DevCss.UserAgent');
goog.require('goog.cssom');
goog.require('goog.dom.classes');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.string');
goog.require('goog.userAgent');
/**
* A class for solving development CSS issues/emulating the CSS Compiler.
* @param {goog.debug.DevCss.UserAgent=} opt_userAgent The user agent, if not
* passed in, will be determined using goog.userAgent.
* @param {number|string=} opt_userAgentVersion The user agent's version.
* If not passed in, will be determined using goog.userAgent.
* @throws {Error} When userAgent detection fails.
* @constructor
*/
goog.debug.DevCss = function(opt_userAgent, opt_userAgentVersion) {
if (!opt_userAgent) {
// Walks through the known goog.userAgents.
if (goog.userAgent.IE) {
opt_userAgent = goog.debug.DevCss.UserAgent.IE;
} else if (goog.userAgent.GECKO) {
opt_userAgent = goog.debug.DevCss.UserAgent.GECKO;
} else if (goog.userAgent.WEBKIT) {
opt_userAgent = goog.debug.DevCss.UserAgent.WEBKIT;
} else if (goog.userAgent.MOBILE) {
opt_userAgent = goog.debug.DevCss.UserAgent.MOBILE;
} else if (goog.userAgent.OPERA) {
opt_userAgent = goog.debug.DevCss.UserAgent.OPERA;
}
}
switch (opt_userAgent) {
case goog.debug.DevCss.UserAgent.OPERA:
case goog.debug.DevCss.UserAgent.IE:
case goog.debug.DevCss.UserAgent.GECKO:
case goog.debug.DevCss.UserAgent.FIREFOX:
case goog.debug.DevCss.UserAgent.WEBKIT:
case goog.debug.DevCss.UserAgent.SAFARI:
case goog.debug.DevCss.UserAgent.MOBILE:
break;
default:
throw Error('Could not determine the user agent from known UserAgents');
}
/**
* One of goog.debug.DevCss.UserAgent.
* @type {string}
* @private
*/
this.userAgent_ = opt_userAgent;
/**
* @type {number|string}
* @private
*/
this.userAgentVersion_ = opt_userAgentVersion || goog.userAgent.VERSION;
this.generateUserAgentTokens_();
/**
* @type {boolean}
* @private
*/
this.isIe6OrLess_ = this.userAgent_ == goog.debug.DevCss.UserAgent.IE &&
goog.string.compareVersions('7', this.userAgentVersion_) > 0;
if (this.isIe6OrLess_) {
/**
* @type {Array.<{classNames,combinedClassName,els}>}
* @private
*/
this.ie6CombinedMatches_ = [];
}
};
/**
* Rewrites the CSSOM as needed to activate any useragent-specific selectors.
* @param {boolean=} opt_enableIe6ReadyHandler If true(the default), and the
* userAgent is ie6, we set a document "ready" event handler to walk the DOM
* and make combined selector className changes. Having this parameter also
* aids unit testing.
*/
goog.debug.DevCss.prototype.activateBrowserSpecificCssRules = function(
opt_enableIe6ReadyHandler) {
var enableIe6EventHandler = goog.isDef(opt_enableIe6ReadyHandler) ?
opt_enableIe6ReadyHandler : true;
var cssRules = goog.cssom.getAllCssStyleRules();
for (var i = 0, cssRule; cssRule = cssRules[i]; i++) {
this.replaceBrowserSpecificClassNames_(cssRule);
}
// Since we may have manipulated the rules above, we'll have to do a
// complete sweep again if we're in IE6. Luckily performance doesn't
// matter for this tool.
if (this.isIe6OrLess_) {
cssRules = goog.cssom.getAllCssStyleRules();
for (var i = 0, cssRule; cssRule = cssRules[i]; i++) {
this.replaceIe6CombinedSelectors_(cssRule);
}
}
// Add an event listener for document ready to rewrite any necessary
// combined classnames in IE6.
if (this.isIe6OrLess_ && enableIe6EventHandler) {
goog.events.listen(document, goog.events.EventType.LOAD, goog.bind(
this.addIe6CombinedClassNames_, this));
}
};
/**
* @type {Object}
* @private
*/
goog.debug.DevCss.prototype.userAgentTokens_ = {};
/**
* A list of possible user agent strings.
* @enum {string}
*/
goog.debug.DevCss.UserAgent = {
OPERA: 'OPERA',
IE: 'IE',
GECKO: 'GECKO',
FIREFOX: 'GECKO',
WEBKIT: 'WEBKIT',
SAFARI: 'WEBKIT',
MOBILE: 'MOBILE'
};
/**
* A list of strings that may be used for matching in CSS files/development.
* @enum {string}
* @private
*/
goog.debug.DevCss.CssToken_ = {
USERAGENT: 'USERAGENT',
SEPARATOR: '-',
LESS_THAN: 'LT',
GREATER_THAN: 'GT',
LESS_THAN_OR_EQUAL: 'LTE',
GREATER_THAN_OR_EQUAL: 'GTE',
IE6_SELECTOR_TEXT: 'goog-ie6-selector',
IE6_COMBINED_GLUE: '_'
};
/**
* Generates user agent token match strings with comparison and version bits.
* For example:
* userAgentTokens_.ANY will be like 'GECKO'
* userAgentTokens_.LESS_THAN will be like 'GECKO-LT3' etc...
* @private
*/
goog.debug.DevCss.prototype.generateUserAgentTokens_ = function() {
this.userAgentTokens_.ANY = goog.debug.DevCss.CssToken_.USERAGENT +
goog.debug.DevCss.CssToken_.SEPARATOR + this.userAgent_;
this.userAgentTokens_.EQUALS = this.userAgentTokens_.ANY +
goog.debug.DevCss.CssToken_.SEPARATOR;
this.userAgentTokens_.LESS_THAN = this.userAgentTokens_.ANY +
goog.debug.DevCss.CssToken_.SEPARATOR +
goog.debug.DevCss.CssToken_.LESS_THAN;
this.userAgentTokens_.LESS_THAN_OR_EQUAL = this.userAgentTokens_.ANY +
goog.debug.DevCss.CssToken_.SEPARATOR +
goog.debug.DevCss.CssToken_.LESS_THAN_OR_EQUAL;
this.userAgentTokens_.GREATER_THAN = this.userAgentTokens_.ANY +
goog.debug.DevCss.CssToken_.SEPARATOR +
goog.debug.DevCss.CssToken_.GREATER_THAN;
this.userAgentTokens_.GREATER_THAN_OR_EQUAL = this.userAgentTokens_.ANY +
goog.debug.DevCss.CssToken_.SEPARATOR +
goog.debug.DevCss.CssToken_.GREATER_THAN_OR_EQUAL;
};
/**
* Gets the version number bit from a selector matching userAgentToken.
* @param {string} selectorText The selector text of a CSS rule.
* @param {string} userAgentToken Includes the LTE/GTE bit to see if it matches.
* @return {string|undefined} The version number.
* @private
*/
goog.debug.DevCss.prototype.getVersionNumberFromSelectorText_ = function(
selectorText, userAgentToken) {
var regex = new RegExp(userAgentToken + '([\\d\\.]+)');
var matches = regex.exec(selectorText);
if (matches && matches.length == 2) {
return matches[1];
}
};
/**
* Extracts a rule version from the selector text, and if it finds one, calls
* compareVersions against it and the passed in token string to provide the
* value needed to determine if we have a match or not.
* @param {CSSRule} cssRule The rule to test against.
* @param {string} token The match token to test against the rule.
* @return {Array|undefined} A tuple with the result of the compareVersions call
* and the matched ruleVersion.
* @private
*/
goog.debug.DevCss.prototype.getRuleVersionAndCompare_ = function(cssRule,
token) {
if (!cssRule.selectorText.match(token)) {
return;
}
var ruleVersion = this.getVersionNumberFromSelectorText_(
cssRule.selectorText, token);
if (!ruleVersion) {
return;
}
var comparison = goog.string.compareVersions(this.userAgentVersion_,
ruleVersion);
return [comparison, ruleVersion];
};
/**
* Replaces a CSS selector if we have matches based on our useragent/version.
* Example: With a selector like ".USERAGENT-IE-LTE6 .class { prop: value }" if
* we are running IE6 we'll end up with ".class { prop: value }", thereby
* "activating" the selector.
* @param {CSSRule} cssRule The cssRule to potentially replace.
* @private
*/
goog.debug.DevCss.prototype.replaceBrowserSpecificClassNames_ = function(
cssRule) {
// If we don't match the browser token, we can stop now.
if (!cssRule.selectorText.match(this.userAgentTokens_.ANY)) {
return;
}
// We know it will begin as a classname.
var additionalRegexString;
// Tests "Less than or equals".
var compared = this.getRuleVersionAndCompare_(cssRule,
this.userAgentTokens_.LESS_THAN_OR_EQUAL);
if (compared && compared.length) {
if (compared[0] > 0) {
return;
}
additionalRegexString = this.userAgentTokens_.LESS_THAN_OR_EQUAL +
compared[1];
}
// Tests "Less than".
compared = this.getRuleVersionAndCompare_(cssRule,
this.userAgentTokens_.LESS_THAN);
if (compared && compared.length) {
if (compared[0] > -1) {
return;
}
additionalRegexString = this.userAgentTokens_.LESS_THAN + compared[1];
}
// Tests "Greater than or equals".
compared = this.getRuleVersionAndCompare_(cssRule,
this.userAgentTokens_.GREATER_THAN_OR_EQUAL);
if (compared && compared.length) {
if (compared[0] < 0) {
return;
}
additionalRegexString = this.userAgentTokens_.GREATER_THAN_OR_EQUAL +
compared[1];
}
// Tests "Greater than".
compared = this.getRuleVersionAndCompare_(cssRule,
this.userAgentTokens_.GREATER_THAN);
if (compared && compared.length) {
if (compared[0] < 1) {
return;
}
additionalRegexString = this.userAgentTokens_.GREATER_THAN + compared[1];
}
// Tests "Equals".
compared = this.getRuleVersionAndCompare_(cssRule,
this.userAgentTokens_.EQUALS);
if (compared && compared.length) {
if (compared[0] != 0) {
return;
}
additionalRegexString = this.userAgentTokens_.EQUALS + compared[1];
}
// If we got to here without generating the additionalRegexString, then
// we did not match any of our comparison token strings, and we want a
// general browser token replacement.
if (!additionalRegexString) {
additionalRegexString = this.userAgentTokens_.ANY;
}
// We need to match at least a single whitespace character to know that
// we are matching the entire useragent string token.
var regexString = '\\.' + additionalRegexString + '\\s+';
var re = new RegExp(regexString, 'g');
var currentCssText = goog.cssom.getCssTextFromCssRule(cssRule);
// Replacing the token with '' activates the selector for this useragent.
var newCssText = currentCssText.replace(re, '');
if (newCssText != currentCssText) {
goog.cssom.replaceCssRule(cssRule, newCssText);
}
};
/**
* Replaces IE6 combined selector rules with a workable development alternative.
* IE6 actually parses .class1.class2 {} to simply .class2 {} which is nasty.
* To fully support combined selectors in IE6 this function needs to be paired
* with a call to replace the relevant DOM elements classNames as well.
* @see {this.addIe6CombinedClassNames_}
* @param {CSSRule} cssRule The rule to potentially fix.
* @private
*/
goog.debug.DevCss.prototype.replaceIe6CombinedSelectors_ = function(cssRule) {
// This match only ever works in IE because other UA's won't have our
// IE6_SELECTOR_TEXT in the cssText property.
if (cssRule.style.cssText &&
cssRule.style.cssText.match(
goog.debug.DevCss.CssToken_.IE6_SELECTOR_TEXT)) {
var cssText = goog.cssom.getCssTextFromCssRule(cssRule);
var combinedSelectorText = this.getIe6CombinedSelectorText_(cssText);
if (combinedSelectorText) {
var newCssText = combinedSelectorText + '{' + cssRule.style.cssText + '}';
goog.cssom.replaceCssRule(cssRule, newCssText);
}
}
};
/**
* Gets the appropriate new combined selector text for IE6.
* Also adds an entry onto ie6CombinedMatches_ with relevant info for the
* likely following call to walk the DOM and rewrite the class attribute.
* Example: With a selector like
* ".class2 { -goog-ie6-selector: .class1.class2; prop: value }".
* this function will return:
* ".class1_class2 { prop: value }".
* @param {string} cssText The CSS selector text and css rule text combined.
* @return {?string} The rewritten css rule text.
* @private
*/
goog.debug.DevCss.prototype.getIe6CombinedSelectorText_ = function(cssText) {
var regex = new RegExp(goog.debug.DevCss.CssToken_.IE6_SELECTOR_TEXT +
'\\s*:\\s*\\"([^\\"]+)\\"', 'gi');
var matches = regex.exec(cssText);
if (matches) {
var combinedSelectorText = matches[1];
// To aid in later fixing the DOM, we need to split up the possible
// selector groups by commas.
var groupedSelectors = combinedSelectorText.split(/\s*\,\s*/);
for (var i = 0, selector; selector = groupedSelectors[i]; i++) {
// Strips off the leading ".".
var combinedClassName = selector.substr(1);
var classNames = combinedClassName.split(
goog.debug.DevCss.CssToken_.IE6_COMBINED_GLUE);
var entry = {
classNames: classNames,
combinedClassName: combinedClassName,
els: []
};
this.ie6CombinedMatches_.push(entry);
}
return combinedSelectorText;
}
return null;
};
/**
* Adds combined selectors with underscores to make them "work" in IE6.
* @see {this.replaceIe6CombinedSelectors_}
* @private
*/
goog.debug.DevCss.prototype.addIe6CombinedClassNames_ = function() {
if (!this.ie6CombinedMatches_.length) {
return;
}
var allEls = document.getElementsByTagName('*');
var matches = [];
// Match nodes for all classNames.
for (var i = 0, classNameEntry; classNameEntry =
this.ie6CombinedMatches_[i]; i++) {
for (var j = 0, el; el = allEls[j]; j++) {
var classNamesLength = classNameEntry.classNames.length;
for (var k = 0, className; className = classNameEntry.classNames[k];
k++) {
if (!goog.dom.classes.has(el, className)) {
break;
}
if (k == classNamesLength - 1) {
classNameEntry.els.push(el);
}
}
}
// Walks over our matching nodes and fixes them.
if (classNameEntry.els.length) {
for (var j = 0, el; el = classNameEntry.els[j]; j++) {
if (!goog.dom.classes.has(el, classNameEntry.combinedClassName)) {
goog.dom.classes.add(el, classNameEntry.combinedClassName);
}
}
}
}
};

View File

@@ -0,0 +1,26 @@
// Copyright 2008 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 Development CSS Compiler runtime execution.
*/
goog.provide('goog.debug.devCssRunner');
goog.require('goog.debug.DevCss');
(function() {
var devCssInstance = new goog.debug.DevCss();
devCssInstance.activateBrowserSpecificCssRules();
})();

View File

@@ -0,0 +1,141 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Simple logger that logs a Div Element.
*
*/
goog.provide('goog.debug.DivConsole');
goog.require('goog.debug.HtmlFormatter');
goog.require('goog.debug.LogManager');
goog.require('goog.style');
/**
* A class for visualising logger calls in a div element.
* @param {Element} element The element to append to.
* @constructor
*/
goog.debug.DivConsole = function(element) {
this.publishHandler_ = goog.bind(this.addLogRecord, this);
this.formatter_ = new goog.debug.HtmlFormatter();
this.formatter_.showAbsoluteTime = false;
this.isCapturing_ = false;
this.element_ = element;
this.elementOwnerDocument_ =
this.element_.ownerDocument || this.element_.document;
this.installStyles();
};
/**
* Installs styles for the log messages and its div
*/
goog.debug.DivConsole.prototype.installStyles = function() {
goog.style.installStyles(
'.dbg-sev{color:#F00}' +
'.dbg-w{color:#C40}' +
'.dbg-sh{font-weight:bold;color:#000}' +
'.dbg-i{color:#444}' +
'.dbg-f{color:#999}' +
'.dbg-ev{color:#0A0}' +
'.dbg-m{color:#990}' +
'.logmsg{border-bottom:1px solid #CCC;padding:2px}' +
'.logsep{background-color: #8C8;}' +
'.logdiv{border:1px solid #CCC;background-color:#FCFCFC;' +
'font:medium monospace}',
this.element_);
this.element_.className += ' logdiv';
};
/**
* Sets whether we are currently capturing logger output.
* @param {boolean} capturing Whether to capture logger output.
*/
goog.debug.DivConsole.prototype.setCapturing = function(capturing) {
if (capturing == this.isCapturing_) {
return;
}
// attach or detach handler from the root logger
var rootLogger = goog.debug.LogManager.getRoot();
if (capturing) {
rootLogger.addHandler(this.publishHandler_);
} else {
rootLogger.removeHandler(this.publishHandler_);
this.logBuffer = '';
}
this.isCapturing_ = capturing;
};
/**
* Adds a log record.
* @param {goog.debug.LogRecord} logRecord The log entry.
*/
goog.debug.DivConsole.prototype.addLogRecord = function(logRecord) {
var scroll = this.element_.scrollHeight - this.element_.scrollTop -
this.element_.clientHeight <= 100;
var div = this.elementOwnerDocument_.createElement('div');
div.className = 'logmsg';
div.innerHTML = this.formatter_.formatRecord(logRecord);
this.element_.appendChild(div);
if (scroll) {
this.element_.scrollTop = this.element_.scrollHeight;
}
};
/**
* Gets the formatter for outputting to the console. The default formatter
* is an instance of goog.debug.HtmlFormatter
* @return {goog.debug.Formatter} The formatter in use.
*/
goog.debug.DivConsole.prototype.getFormatter = function() {
return this.formatter_;
};
/**
* Sets the formatter for outputting to the console.
* @param {goog.debug.Formatter} formatter The formatter to use.
*/
goog.debug.DivConsole.prototype.setFormatter = function(formatter) {
this.formatter_ = formatter;
};
/**
* Adds a separator to the debug window.
*/
goog.debug.DivConsole.prototype.addSeparator = function() {
var div = this.elementOwnerDocument_.createElement('div');
div.className = 'logmsg logsep';
this.element_.appendChild(div);
};
/**
* Clears the console.
*/
goog.debug.DivConsole.prototype.clear = function() {
this.element_.innerHTML = '';
};

View File

@@ -0,0 +1,158 @@
// 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 global registry for entry points into a program,
* so that they can be instrumented. Each module should register their
* entry points with this registry. Designed to be compiled out
* if no instrumentation is requested.
*
* Entry points may be registered before or after a call to
* goog.debug.entryPointRegistry.monitorAll. If an entry point is registered
* later, the existing monitor will instrument the new entry point.
*
* @author nicksantos@google.com (Nick Santos)
*/
goog.provide('goog.debug.EntryPointMonitor');
goog.provide('goog.debug.entryPointRegistry');
goog.require('goog.asserts');
/**
* @interface
*/
goog.debug.EntryPointMonitor = function() {};
/**
* Instruments a function.
*
* @param {!Function} fn A function to instrument.
* @return {!Function} The instrumented function.
*/
goog.debug.EntryPointMonitor.prototype.wrap;
/**
* Try to remove an instrumentation wrapper created by this monitor.
* If the function passed to unwrap is not a wrapper created by this
* monitor, then we will do nothing.
*
* Notice that some wrappers may not be unwrappable. For example, if other
* monitors have applied their own wrappers, then it will be impossible to
* unwrap them because their wrappers will have captured our wrapper.
*
* So it is important that entry points are unwrapped in the reverse
* order that they were wrapped.
*
* @param {!Function} fn A function to unwrap.
* @return {!Function} The unwrapped function, or {@code fn} if it was not
* a wrapped function created by this monitor.
*/
goog.debug.EntryPointMonitor.prototype.unwrap;
/**
* An array of entry point callbacks.
* @type {!Array.<function(!Function)>}
* @private
*/
goog.debug.entryPointRegistry.refList_ = [];
/**
* Monitors that should wrap all the entry points.
* @type {!Array.<!goog.debug.EntryPointMonitor>}
* @private
*/
goog.debug.entryPointRegistry.monitors_ = [];
/**
* Whether goog.debug.entryPointRegistry.monitorAll has ever been called.
* Checking this allows the compiler to optimize out the registrations.
* @type {boolean}
* @private
*/
goog.debug.entryPointRegistry.monitorsMayExist_ = false;
/**
* Register an entry point with this module.
*
* The entry point will be instrumented when a monitor is passed to
* goog.debug.entryPointRegistry.monitorAll. If this has already occurred, the
* entry point is instrumented immediately.
*
* @param {function(!Function)} callback A callback function which is called
* with a transforming function to instrument the entry point. The callback
* is responsible for wrapping the relevant entry point with the
* transforming function.
*/
goog.debug.entryPointRegistry.register = function(callback) {
// Don't use push(), so that this can be compiled out.
goog.debug.entryPointRegistry.refList_[
goog.debug.entryPointRegistry.refList_.length] = callback;
// If no one calls monitorAll, this can be compiled out.
if (goog.debug.entryPointRegistry.monitorsMayExist_) {
var monitors = goog.debug.entryPointRegistry.monitors_;
for (var i = 0; i < monitors.length; i++) {
callback(goog.bind(monitors[i].wrap, monitors[i]));
}
}
};
/**
* Configures a monitor to wrap all entry points.
*
* Entry points that have already been registered are immediately wrapped by
* the monitor. When an entry point is registered in the future, it will also
* be wrapped by the monitor when it is registered.
*
* @param {!goog.debug.EntryPointMonitor} monitor An entry point monitor.
*/
goog.debug.entryPointRegistry.monitorAll = function(monitor) {
goog.debug.entryPointRegistry.monitorsMayExist_ = true;
var transformer = goog.bind(monitor.wrap, monitor);
for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
goog.debug.entryPointRegistry.refList_[i](transformer);
}
goog.debug.entryPointRegistry.monitors_.push(monitor);
};
/**
* Try to unmonitor all the entry points that have already been registered. If
* an entry point is registered in the future, it will not be wrapped by the
* monitor when it is registered. Note that this may fail if the entry points
* have additional wrapping.
*
* @param {!goog.debug.EntryPointMonitor} monitor The last monitor to wrap
* the entry points.
* @throws {Error} If the monitor is not the most recently configured monitor.
*/
goog.debug.entryPointRegistry.unmonitorAllIfPossible = function(monitor) {
var monitors = goog.debug.entryPointRegistry.monitors_;
goog.asserts.assert(monitor == monitors[monitors.length - 1],
'Only the most recent monitor can be unwrapped.');
var transformer = goog.bind(monitor.unwrap, monitor);
for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
goog.debug.entryPointRegistry.refList_[i](transformer);
}
monitors.length--;
};

View File

@@ -0,0 +1,51 @@
// Copyright 2009 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 Provides a base class for custom Error objects such that the
* stack is correctly maintained.
*
* You should never need to throw goog.debug.Error(msg) directly, Error(msg) is
* sufficient.
*
*/
goog.provide('goog.debug.Error');
/**
* Base class for custom error objects.
* @param {*=} opt_msg The message associated with the error.
* @constructor
* @extends {Error}
*/
goog.debug.Error = function(opt_msg) {
// Ensure there is a stack trace.
if (Error.captureStackTrace) {
Error.captureStackTrace(this, goog.debug.Error);
} else {
this.stack = new Error().stack || '';
}
if (opt_msg) {
this.message = String(opt_msg);
}
};
goog.inherits(goog.debug.Error, Error);
/** @override */
goog.debug.Error.prototype.name = 'CustomError';

View File

@@ -0,0 +1,363 @@
// Copyright 2007 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 Error handling utilities.
*
*/
goog.provide('goog.debug.ErrorHandler');
goog.provide('goog.debug.ErrorHandler.ProtectedFunctionError');
goog.require('goog.asserts');
goog.require('goog.debug');
goog.require('goog.debug.EntryPointMonitor');
goog.require('goog.debug.Trace');
/**
* The ErrorHandler can be used to to wrap functions with a try/catch
* statement. If an exception is thrown, the given error handler function will
* be called.
*
* When this object is disposed, it will stop handling exceptions and tracing.
* It will also try to restore window.setTimeout and window.setInterval
* if it wrapped them. Notice that in the general case, it is not technically
* possible to remove the wrapper, because functions have no knowledge of
* what they have been assigned to. So the app is responsible for other
* forms of unwrapping.
*
* @param {Function} handler Handler for exceptions.
* @constructor
* @extends {goog.Disposable}
* @implements {goog.debug.EntryPointMonitor}
*/
goog.debug.ErrorHandler = function(handler) {
goog.base(this);
/**
* Handler for exceptions, which can do logging, reporting, etc.
* @type {Function}
* @private
*/
this.errorHandlerFn_ = handler;
/**
* Whether errors should be wrapped in
* goog.debug.ErrorHandler.ProtectedFunctionError before rethrowing.
* @type {boolean}
* @private
*/
this.wrapErrors_ = true; // TODO(user) Change default.
/**
* Whether to add a prefix to all error messages. The prefix is
* goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX. This option
* only has an effect if this.wrapErrors_ is set to false.
* @type {boolean}
* @private
*/
this.prefixErrorMessages_ = false;
};
goog.inherits(goog.debug.ErrorHandler, goog.Disposable);
/**
* Whether to add tracers when instrumenting entry points.
* @type {boolean}
* @private
*/
goog.debug.ErrorHandler.prototype.addTracersToProtectedFunctions_ = false;
/**
* Enable tracers when instrumenting entry points.
* @param {boolean} newVal See above.
*/
goog.debug.ErrorHandler.prototype.setAddTracersToProtectedFunctions =
function(newVal) {
this.addTracersToProtectedFunctions_ = newVal;
};
/** @override */
goog.debug.ErrorHandler.prototype.wrap = function(fn) {
return this.protectEntryPoint(goog.asserts.assertFunction(fn));
};
/** @override */
goog.debug.ErrorHandler.prototype.unwrap = function(fn) {
goog.asserts.assertFunction(fn);
return fn[this.getFunctionIndex_(false)] || fn;
};
/**
* Private helper function to return a span that can be clicked on to display
* an alert with the current stack trace. Newlines are replaced with a
* placeholder so that they will not be html-escaped.
* @param {string} stackTrace The stack trace to create a span for.
* @return {string} A span which can be clicked on to show the stack trace.
* @private
*/
goog.debug.ErrorHandler.prototype.getStackTraceHolder_ = function(stackTrace) {
var buffer = [];
buffer.push('##PE_STACK_START##');
buffer.push(stackTrace.replace(/(\r\n|\r|\n)/g, '##STACK_BR##'));
buffer.push('##PE_STACK_END##');
return buffer.join('');
};
/**
* Get the index for a function. Used for internal indexing.
* @param {boolean} wrapper True for the wrapper; false for the wrapped.
* @return {string} The index where we should store the function in its
* wrapper/wrapped function.
* @private
*/
goog.debug.ErrorHandler.prototype.getFunctionIndex_ = function(wrapper) {
return (wrapper ? '__wrapper_' : '__protected_') + goog.getUid(this) + '__';
};
/**
* Installs exception protection for an entry point function. When an exception
* is thrown from a protected function, a handler will be invoked to handle it.
*
* @param {Function} fn An entry point function to be protected.
* @return {!Function} A protected wrapper function that calls the entry point
* function.
*/
goog.debug.ErrorHandler.prototype.protectEntryPoint = function(fn) {
var protectedFnName = this.getFunctionIndex_(true);
if (!fn[protectedFnName]) {
var wrapper = fn[protectedFnName] = this.getProtectedFunction(fn);
wrapper[this.getFunctionIndex_(false)] = fn;
}
return fn[protectedFnName];
};
/**
* Helps {@link #protectEntryPoint} by actually creating the protected
* wrapper function, after {@link #protectEntryPoint} determines that one does
* not already exist for the given function. Can be overriden by subclasses
* that may want to implement different error handling, or add additional
* entry point hooks.
* @param {!Function} fn An entry point function to be protected.
* @return {!Function} protected wrapper function.
* @protected
*/
goog.debug.ErrorHandler.prototype.getProtectedFunction = function(fn) {
var that = this;
var tracers = this.addTracersToProtectedFunctions_;
if (tracers) {
var stackTrace = goog.debug.getStacktraceSimple(15);
}
var googDebugErrorHandlerProtectedFunction = function() {
if (that.isDisposed()) {
return fn.apply(this, arguments);
}
if (tracers) {
var tracer = goog.debug.Trace.startTracer('protectedEntryPoint: ' +
that.getStackTraceHolder_(stackTrace));
}
try {
return fn.apply(this, arguments);
} catch (e) {
that.errorHandlerFn_(e);
if (!that.wrapErrors_) {
// Add the prefix to the existing message.
if (that.prefixErrorMessages_) {
if (typeof e === 'object') {
e.message =
goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX +
e.message;
} else {
e = goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX +
e;
}
}
if (goog.DEBUG) {
// Work around for https://code.google.com/p/v8/issues/detail?id=2625
// and https://code.google.com/p/chromium/issues/detail?id=237059
// Custom errors and errors with custom stack traces show the wrong
// stack trace
// If it has a stack and Error.captureStackTrace is supported (only
// supported in V8 as of May 2013) log the stack to the console.
if (e && e.stack && Error.captureStackTrace &&
goog.global['console']) {
goog.global['console']['error'](e.message, e.stack);
}
}
// Re-throw original error. This is great for debugging as it makes
// browser JS dev consoles show the correct error and stack trace.
throw e;
}
// Re-throw it since this may be expected by the caller.
throw new goog.debug.ErrorHandler.ProtectedFunctionError(e);
} finally {
if (tracers) {
goog.debug.Trace.stopTracer(tracer);
}
}
};
googDebugErrorHandlerProtectedFunction[this.getFunctionIndex_(false)] = fn;
return googDebugErrorHandlerProtectedFunction;
};
// TODO(user): Allow these functions to take in the window to protect.
/**
* Installs exception protection for window.setTimeout to handle exceptions.
*/
goog.debug.ErrorHandler.prototype.protectWindowSetTimeout =
function() {
this.protectWindowFunctionsHelper_('setTimeout');
};
/**
* Install exception protection for window.setInterval to handle exceptions.
*/
goog.debug.ErrorHandler.prototype.protectWindowSetInterval =
function() {
this.protectWindowFunctionsHelper_('setInterval');
};
/**
* Install exception protection for window.requestAnimationFrame to handle
* exceptions.
*/
goog.debug.ErrorHandler.prototype.protectWindowRequestAnimationFrame =
function() {
var win = goog.getObjectByName('window');
var fnNames = [
'requestAnimationFrame',
'mozRequestAnimationFrame',
'webkitAnimationFrame',
'msRequestAnimationFrame'
];
for (var i = 0; i < fnNames.length; i++) {
var fnName = fnNames[i];
if (fnNames[i] in win) {
win[fnName] = this.protectEntryPoint(win[fnName]);
}
}
};
/**
* Helper function for protecting setTimeout/setInterval.
* @param {string} fnName The name of the function we're protecting. Must
* be setTimeout or setInterval.
* @private
*/
goog.debug.ErrorHandler.prototype.protectWindowFunctionsHelper_ =
function(fnName) {
var win = goog.getObjectByName('window');
var originalFn = win[fnName];
var that = this;
win[fnName] = function(fn, time) {
// Don't try to protect strings. In theory, we could try to globalEval
// the string, but this seems to lead to permission errors on IE6.
if (goog.isString(fn)) {
fn = goog.partial(goog.globalEval, fn);
}
fn = that.protectEntryPoint(fn);
// IE doesn't support .call for setInterval/setTimeout, but it
// also doesn't care what "this" is, so we can just call the
// original function directly
if (originalFn.call) {
return originalFn.call(this, fn, time);
} else {
return originalFn(fn, time);
}
};
win[fnName][this.getFunctionIndex_(false)] = originalFn;
};
/**
* Set whether to wrap errors that occur in protected functions in a
* goog.debug.ErrorHandler.ProtectedFunctionError.
* @param {boolean} wrapErrors Whether to wrap errors.
*/
goog.debug.ErrorHandler.prototype.setWrapErrors = function(wrapErrors) {
this.wrapErrors_ = wrapErrors;
};
/**
* Set whether to add a prefix to all error messages that occur in protected
* functions.
* @param {boolean} prefixErrorMessages Whether to add a prefix to error
* messages.
*/
goog.debug.ErrorHandler.prototype.setPrefixErrorMessages =
function(prefixErrorMessages) {
this.prefixErrorMessages_ = prefixErrorMessages;
};
/** @override */
goog.debug.ErrorHandler.prototype.disposeInternal = function() {
// Try to unwrap window.setTimeout and window.setInterval.
var win = goog.getObjectByName('window');
win.setTimeout = this.unwrap(win.setTimeout);
win.setInterval = this.unwrap(win.setInterval);
goog.base(this, 'disposeInternal');
};
/**
* Error thrown to the caller of a protected entry point if the entry point
* throws an error.
* @param {*} cause The error thrown by the entry point.
* @constructor
* @extends {goog.debug.Error}
*/
goog.debug.ErrorHandler.ProtectedFunctionError = function(cause) {
var message = goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX +
(cause && cause.message ? String(cause.message) : String(cause));
goog.base(this, message);
/**
* The error thrown by the entry point.
* @type {*}
*/
this.cause = cause;
var stack = cause && cause.stack;
if (stack && goog.isString(stack)) {
this.stack = /** @type {string} */ (stack);
}
};
goog.inherits(goog.debug.ErrorHandler.ProtectedFunctionError, goog.debug.Error);
/**
* Text to prefix the message with.
* @type {string}
*/
goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX =
'Error in protected function: ';

View File

@@ -0,0 +1,38 @@
// Copyright 2008 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 File which defines dummy object to work around undefined
* properties compiler warning for weak dependencies on
* {@link goog.debug.ErrorHandler#protectEntryPoint}.
*
*/
goog.provide('goog.debug.errorHandlerWeakDep');
/**
* Dummy object to work around undefined properties compiler warning.
* @type {Object}
*/
goog.debug.errorHandlerWeakDep = {
/**
* @param {Function} fn An entry point function to be protected.
* @param {boolean=} opt_tracers Whether to install tracers around the
* fn.
* @return {Function} A protected wrapper function that calls the
* entry point function.
*/
protectEntryPoint: function(fn, opt_tracers) { return fn; }
};

View File

@@ -0,0 +1,376 @@
// Copyright 2009 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 Definition of the ErrorReporter class, which creates an error
* handler that reports any errors raised to a URL.
*
*/
goog.provide('goog.debug.ErrorReporter');
goog.provide('goog.debug.ErrorReporter.ExceptionEvent');
goog.require('goog.asserts');
goog.require('goog.debug');
goog.require('goog.debug.ErrorHandler');
goog.require('goog.debug.entryPointRegistry');
goog.require('goog.events');
goog.require('goog.events.Event');
goog.require('goog.events.EventTarget');
goog.require('goog.log');
goog.require('goog.net.XhrIo');
goog.require('goog.object');
goog.require('goog.string');
goog.require('goog.uri.utils');
goog.require('goog.userAgent');
/**
* Constructs an error reporter. Internal Use Only. To install an error
* reporter see the {@see #install} method below.
*
* @param {string} handlerUrl The URL to which all errors will be reported.
* @param {function(!Error, !Object.<string, string>)=}
* opt_contextProvider When a report is to be sent to the server,
* this method will be called, and given an opportunity to modify the
* context object before submission to the server.
* @param {boolean=} opt_noAutoProtect Whether to automatically add handlers for
* onerror and to protect entry points. If apps have other error reporting
* facilities, it may make sense for them to set these up themselves and use
* the ErrorReporter just for transmission of reports.
* @constructor
* @extends {goog.events.EventTarget}
*/
goog.debug.ErrorReporter = function(
handlerUrl, opt_contextProvider, opt_noAutoProtect) {
goog.base(this);
/**
* Context provider, if one was provided.
* @type {?function(!Error, !Object.<string, string>)}
* @private
*/
this.contextProvider_ = opt_contextProvider || null;
/**
* The string prefix of any optional context parameters logged with the error.
* @private {string}
*/
this.contextPrefix_ = 'context.';
/**
* The number of bytes after which the ErrorReporter truncates the POST body.
* If null, the ErrorReporter won't truncate the body.
* @private {?number}
*/
this.truncationLimit_ = null;
/**
* XHR sender.
* @type {function(string, string, string, (Object|goog.structs.Map)=)}
* @private
*/
this.xhrSender_ = goog.debug.ErrorReporter.defaultXhrSender;
/**
* The URL at which all errors caught by this handler will be logged.
*
* @type {string}
* @private
*/
this.handlerUrl_ = handlerUrl;
if (!opt_noAutoProtect) {
this.setup_();
}
};
goog.inherits(goog.debug.ErrorReporter, goog.events.EventTarget);
/**
* Event broadcast when an exception is logged.
* @param {Error} error The exception that was was reported.
* @param {!Object.<string, string>} context The context values sent to the
* server alongside this error.
* @constructor
* @extends {goog.events.Event}
*/
goog.debug.ErrorReporter.ExceptionEvent = function(error, context) {
goog.events.Event.call(this, goog.debug.ErrorReporter.ExceptionEvent.TYPE);
/**
* The error that was reported.
* @type {Error}
*/
this.error = error;
/**
* Context values sent to the server alongside this report.
* @type {!Object.<string, string>}
*/
this.context = context;
};
goog.inherits(goog.debug.ErrorReporter.ExceptionEvent, goog.events.Event);
/**
* Event type for notifying of a logged exception.
* @type {string}
*/
goog.debug.ErrorReporter.ExceptionEvent.TYPE =
goog.events.getUniqueId('exception');
/**
* The internal error handler used to catch all errors.
*
* @type {goog.debug.ErrorHandler}
* @private
*/
goog.debug.ErrorReporter.prototype.errorHandler_ = null;
/**
* Extra headers for the error-reporting XHR.
* @type {Object|goog.structs.Map|undefined}
* @private
*/
goog.debug.ErrorReporter.prototype.extraHeaders_;
/**
* Logging object.
*
* @type {goog.log.Logger}
* @private
*/
goog.debug.ErrorReporter.logger_ =
goog.log.getLogger('goog.debug.ErrorReporter');
/**
* Installs an error reporter to catch all JavaScript errors raised.
*
* @param {string} loggingUrl The URL to which the errors caught will be
* reported.
* @param {function(!Error, !Object.<string, string>)=}
* opt_contextProvider When a report is to be sent to the server,
* this method will be called, and given an opportunity to modify the
* context object before submission to the server.
* @param {boolean=} opt_noAutoProtect Whether to automatically add handlers for
* onerror and to protect entry points. If apps have other error reporting
* facilities, it may make sense for them to set these up themselves and use
* the ErrorReporter just for transmission of reports.
* @return {goog.debug.ErrorReporter} The error reporter.
*/
goog.debug.ErrorReporter.install = function(
loggingUrl, opt_contextProvider, opt_noAutoProtect) {
var instance = new goog.debug.ErrorReporter(
loggingUrl, opt_contextProvider, opt_noAutoProtect);
return instance;
};
/**
* Default implemntation of XHR sender interface.
*
* @param {string} uri URI to make request to.
* @param {string} method Send method.
* @param {string} content Post data.
* @param {Object|goog.structs.Map=} opt_headers Map of headers to add to the
* request.
*/
goog.debug.ErrorReporter.defaultXhrSender = function(uri, method, content,
opt_headers) {
goog.net.XhrIo.send(uri, null, method, content, opt_headers);
};
/**
* Installs exception protection for an entry point function in addition
* to those that are protected by default.
* Has no effect in IE because window.onerror is used for reporting
* exceptions in that case.
*
* @param {Function} fn An entry point function to be protected.
* @return {Function} A protected wrapper function that calls the entry point
* function or null if the entry point could not be protected.
*/
goog.debug.ErrorReporter.prototype.protectAdditionalEntryPoint = function(fn) {
if (this.errorHandler_) {
return this.errorHandler_.protectEntryPoint(fn);
}
return null;
};
/**
* Add headers to the logging url.
* @param {Object|goog.structs.Map} loggingHeaders Extra headers to send
* to the logging URL.
*/
goog.debug.ErrorReporter.prototype.setLoggingHeaders =
function(loggingHeaders) {
this.extraHeaders_ = loggingHeaders;
};
/**
* Set the function used to send error reports to the server.
* @param {function(string, string, string, (Object|goog.structs.Map)=)}
* xhrSender If provided, this will be used to send a report to the
* server instead of the default method. The function will be given the URI,
* HTTP method request content, and (optionally) request headers to be
* added.
*/
goog.debug.ErrorReporter.prototype.setXhrSender = function(xhrSender) {
this.xhrSender_ = xhrSender;
};
/**
* Sets up the error reporter.
*
* @private
*/
goog.debug.ErrorReporter.prototype.setup_ = function() {
if (goog.userAgent.IE) {
// Use "onerror" because caught exceptions in IE don't provide line number.
goog.debug.catchErrors(
goog.bind(this.handleException, this), false, null);
} else {
// "onerror" doesn't work with FF2 or Chrome
this.errorHandler_ = new goog.debug.ErrorHandler(
goog.bind(this.handleException, this));
this.errorHandler_.protectWindowSetTimeout();
this.errorHandler_.protectWindowSetInterval();
goog.debug.entryPointRegistry.monitorAll(this.errorHandler_);
}
};
/**
* Handler for caught exceptions. Sends report to the LoggingServlet and
* notifies any listeners.
*
* @param {Object} e The exception.
* @param {!Object.<string, string>=} opt_context Context values to optionally
* include in the error report.
*/
goog.debug.ErrorReporter.prototype.handleException = function(e,
opt_context) {
var error = /** @type {!Error} */ (goog.debug.normalizeErrorObject(e));
// Construct the context, possibly from the one provided in the argument, and
// pass it to the context provider if there is one.
var context = opt_context ? goog.object.clone(opt_context) : {};
if (this.contextProvider_) {
try {
this.contextProvider_(error, context);
} catch (err) {
goog.log.error(goog.debug.ErrorReporter.logger_,
'Context provider threw an exception: ' + err.message);
}
}
// Truncate message to a reasonable length, since it will be sent in the URL.
var message = error.message.substring(0, 2000);
this.sendErrorReport(message, error.fileName, error.lineNumber, error.stack,
context);
try {
this.dispatchEvent(
new goog.debug.ErrorReporter.ExceptionEvent(error, context));
} catch (ex) {
// Swallow exception to avoid infinite recursion.
}
};
/**
* Sends an error report to the logging URL. This will not consult the context
* provider, the report will be sent exactly as specified.
*
* @param {string} message Error description.
* @param {string} fileName URL of the JavaScript file with the error.
* @param {number} line Line number of the error.
* @param {string=} opt_trace Call stack trace of the error.
* @param {!Object.<string, string>=} opt_context Context information to include
* in the request.
*/
goog.debug.ErrorReporter.prototype.sendErrorReport =
function(message, fileName, line, opt_trace, opt_context) {
try {
// Create the logging URL.
var requestUrl = goog.uri.utils.appendParams(this.handlerUrl_,
'script', fileName, 'error', message, 'line', line);
var queryMap = {};
queryMap['trace'] = opt_trace;
// Copy context into query data map
if (opt_context) {
for (var entry in opt_context) {
queryMap[this.contextPrefix_ + entry] = opt_context[entry];
}
}
// Copy query data map into request.
var queryData = goog.uri.utils.buildQueryDataFromMap(queryMap);
// Truncate if truncationLimit set.
if (goog.isNumber(this.truncationLimit_)) {
queryData = queryData.substring(0, this.truncationLimit_);
}
// Send the request with the contents of the error.
this.xhrSender_(requestUrl, 'POST', queryData, this.extraHeaders_);
} catch (e) {
var logMessage = goog.string.buildString(
'Error occurred in sending an error report.\n\n',
'script:', fileName, '\n',
'line:', line, '\n',
'error:', message, '\n',
'trace:', opt_trace);
goog.log.info(goog.debug.ErrorReporter.logger_, logMessage);
}
};
/**
* @param {string} prefix The prefix to appear prepended to all context
* variables in the error report body.
*/
goog.debug.ErrorReporter.prototype.setContextPrefix = function(prefix) {
this.contextPrefix_ = prefix;
};
/**
* @param {?number} limit Size in bytes to begin truncating POST body. Set to
* null to prevent truncation. The limit must be >= 0.
*/
goog.debug.ErrorReporter.prototype.setTruncationLimit = function(limit) {
goog.asserts.assert(!goog.isNumber(limit) || limit >= 0,
'Body limit must be valid number >= 0 or null');
this.truncationLimit_ = limit;
};
/** @override */
goog.debug.ErrorReporter.prototype.disposeInternal = function() {
goog.dispose(this.errorHandler_);
goog.base(this, 'disposeInternal');
};

View File

@@ -0,0 +1,362 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition of the FancyWindow class. Please minimize
* dependencies this file has on other closure classes as any dependency it
* takes won't be able to use the logging infrastructure.
*
* This is a pretty hacky implementation, aimed at making debugging of large
* applications more manageable.
*
* @see ../demos/debug.html
*/
goog.provide('goog.debug.FancyWindow');
goog.require('goog.debug.DebugWindow');
goog.require('goog.debug.LogManager');
goog.require('goog.debug.Logger');
goog.require('goog.dom.DomHelper');
goog.require('goog.object');
goog.require('goog.string');
goog.require('goog.userAgent');
/**
* Provides a Fancy extension to the DebugWindow class. Allows filtering based
* on loggers and levels.
*
* @param {string=} opt_identifier Idenitifier for this logging class.
* @param {string=} opt_prefix Prefix pre-pended to messages.
* @constructor
* @extends {goog.debug.DebugWindow}
*/
goog.debug.FancyWindow = function(opt_identifier, opt_prefix) {
this.readOptionsFromLocalStorage_();
goog.base(this, opt_identifier, opt_prefix);
};
goog.inherits(goog.debug.FancyWindow, goog.debug.DebugWindow);
/**
* Constant indicating if we are able to use localStorage to persist filters
* @type {boolean}
*/
goog.debug.FancyWindow.HAS_LOCAL_STORE = (function() {
/** @preserveTry */
try {
return !!window['localStorage'].getItem;
} catch (e) {}
return false;
})();
/**
* Constant defining the prefix to use when storing log levels
* @type {string}
*/
goog.debug.FancyWindow.LOCAL_STORE_PREFIX = 'fancywindow.sel.';
/** @override */
goog.debug.FancyWindow.prototype.writeBufferToLog = function() {
this.lastCall = goog.now();
if (this.hasActiveWindow()) {
var logel = this.dh_.getElement('log');
// Work out if scrolling is needed before we add the content
var scroll =
logel.scrollHeight - (logel.scrollTop + logel.offsetHeight) <= 100;
for (var i = 0; i < this.outputBuffer.length; i++) {
var div = this.dh_.createDom('div', 'logmsg');
div.innerHTML = this.outputBuffer[i];
logel.appendChild(div);
}
this.outputBuffer.length = 0;
this.resizeStuff_();
if (scroll) {
logel.scrollTop = logel.scrollHeight;
}
}
};
/** @override */
goog.debug.FancyWindow.prototype.writeInitialDocument = function() {
if (!this.hasActiveWindow()) {
return;
}
var doc = this.win.document;
doc.open();
doc.write(this.getHtml_());
doc.close();
(goog.userAgent.IE ? doc.body : this.win).onresize =
goog.bind(this.resizeStuff_, this);
// Create a dom helper for the logging window
this.dh_ = new goog.dom.DomHelper(doc);
// Don't use events system to reduce dependencies
this.dh_.getElement('openbutton').onclick =
goog.bind(this.openOptions_, this);
this.dh_.getElement('closebutton').onclick =
goog.bind(this.closeOptions_, this);
this.dh_.getElement('clearbutton').onclick =
goog.bind(this.clear, this);
this.dh_.getElement('exitbutton').onclick =
goog.bind(this.exit_, this);
this.writeSavedMessages();
};
/**
* Show the options menu.
* @return {boolean} false.
* @private
*/
goog.debug.FancyWindow.prototype.openOptions_ = function() {
var el = this.dh_.getElement('optionsarea');
el.innerHTML = '';
var loggers = goog.debug.FancyWindow.getLoggers_();
var dh = this.dh_;
for (var i = 0; i < loggers.length; i++) {
var logger = goog.debug.Logger.getLogger(loggers[i]);
var curlevel = logger.getLevel() ? logger.getLevel().name : 'INHERIT';
var div = dh.createDom('div', {},
this.getDropDown_('sel' + loggers[i], curlevel),
dh.createDom('span', {}, loggers[i] || '(root)'));
el.appendChild(div);
}
this.dh_.getElement('options').style.display = 'block';
return false;
};
/**
* Make a drop down for the log levels.
* @param {string} id Logger id.
* @param {string} selected What log level is currently selected.
* @return {Element} The newly created 'select' DOM element.
* @private
*/
goog.debug.FancyWindow.prototype.getDropDown_ = function(id, selected) {
var dh = this.dh_;
var sel = dh.createDom('select', {'id': id});
var levels = goog.debug.Logger.Level.PREDEFINED_LEVELS;
for (var i = 0; i < levels.length; i++) {
var level = levels[i];
var option = dh.createDom('option', {}, level.name);
if (selected == level.name) {
option.selected = true;
}
sel.appendChild(option);
}
sel.appendChild(dh.createDom('option',
{'selected': selected == 'INHERIT'}, 'INHERIT'));
return sel;
};
/**
* Close the options menu.
* @return {boolean} The value false.
* @private
*/
goog.debug.FancyWindow.prototype.closeOptions_ = function() {
this.dh_.getElement('options').style.display = 'none';
var loggers = goog.debug.FancyWindow.getLoggers_();
var dh = this.dh_;
for (var i = 0; i < loggers.length; i++) {
var logger = goog.debug.Logger.getLogger(loggers[i]);
var sel = dh.getElement('sel' + loggers[i]);
var level = sel.options[sel.selectedIndex].text;
if (level == 'INHERIT') {
logger.setLevel(null);
} else {
logger.setLevel(goog.debug.Logger.Level.getPredefinedLevel(level));
}
}
this.writeOptionsToLocalStorage_();
return false;
};
/**
* Resize the lof elements
* @private
*/
goog.debug.FancyWindow.prototype.resizeStuff_ = function() {
var dh = this.dh_;
var logel = dh.getElement('log');
var headel = dh.getElement('head');
logel.style.top = headel.offsetHeight + 'px';
logel.style.height = (dh.getDocument().body.offsetHeight -
headel.offsetHeight - (goog.userAgent.IE ? 4 : 0)) + 'px';
};
/**
* Handles the user clicking the exit button, disabled the debug window and
* closes the popup.
* @param {Event} e Event object.
* @private
*/
goog.debug.FancyWindow.prototype.exit_ = function(e) {
this.setEnabled(false);
if (this.win) {
this.win.close();
}
};
/** @override */
goog.debug.FancyWindow.prototype.getStyleRules = function() {
return goog.base(this, 'getStyleRules') +
'html,body{height:100%;width:100%;margin:0px;padding:0px;' +
'background-color:#FFF;overflow:hidden}' +
'*{}' +
'.logmsg{border-bottom:1px solid #CCC;padding:2px;font:90% monospace}' +
'#head{position:absolute;width:100%;font:x-small arial;' +
'border-bottom:2px solid #999;background-color:#EEE;}' +
'#head p{margin:0px 5px;}' +
'#log{position:absolute;width:100%;background-color:#FFF;}' +
'#options{position:absolute;right:0px;width:50%;height:100%;' +
'border-left:1px solid #999;background-color:#DDD;display:none;' +
'padding-left: 5px;font:normal small arial;overflow:auto;}' +
'#openbutton,#closebutton{text-decoration:underline;color:#00F;cursor:' +
'pointer;position:absolute;top:0px;right:5px;font:x-small arial;}' +
'#clearbutton{text-decoration:underline;color:#00F;cursor:' +
'pointer;position:absolute;top:0px;right:80px;font:x-small arial;}' +
'#exitbutton{text-decoration:underline;color:#00F;cursor:' +
'pointer;position:absolute;top:0px;right:50px;font:x-small arial;}' +
'select{font:x-small arial;margin-right:10px;}' +
'hr{border:0;height:5px;background-color:#8c8;color:#8c8;}';
};
/**
* Return the default HTML for the debug window
* @return {string} Html.
* @private
*/
goog.debug.FancyWindow.prototype.getHtml_ = function() {
return '' +
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"' +
'"http://www.w3.org/TR/html4/loose.dtd">' +
'<html><head><title>Logging: ' + this.identifier + '</title>' +
'<style>' + this.getStyleRules() + '</style>' +
'</head><body>' +
'<div id="log" style="overflow:auto"></div>' +
'<div id="head">' +
'<p><b>Logging: ' + this.identifier + '</b></p><p>' +
this.welcomeMessage + '</p>' +
'<span id="clearbutton">clear</span>' +
'<span id="exitbutton">exit</span>' +
'<span id="openbutton">options</span>' +
'</div>' +
'<div id="options">' +
'<big><b>Options:</b></big>' +
'<div id="optionsarea"></div>' +
'<span id="closebutton">save and close</span>' +
'</div>' +
'</body></html>';
};
/**
* Write logger levels to localStorage if possible.
* @private
*/
goog.debug.FancyWindow.prototype.writeOptionsToLocalStorage_ = function() {
if (!goog.debug.FancyWindow.HAS_LOCAL_STORE) {
return;
}
var loggers = goog.debug.FancyWindow.getLoggers_();
var storedKeys = goog.debug.FancyWindow.getStoredKeys_();
for (var i = 0; i < loggers.length; i++) {
var key = goog.debug.FancyWindow.LOCAL_STORE_PREFIX + loggers[i];
var level = goog.debug.Logger.getLogger(loggers[i]).getLevel();
if (key in storedKeys) {
if (!level) {
window.localStorage.removeItem(key);
} else if (window.localStorage.getItem(key) != level.name) {
window.localStorage.setItem(key, level.name);
}
} else if (level) {
window.localStorage.setItem(key, level.name);
}
}
};
/**
* Sync logger levels with any values stored in localStorage.
* @private
*/
goog.debug.FancyWindow.prototype.readOptionsFromLocalStorage_ = function() {
if (!goog.debug.FancyWindow.HAS_LOCAL_STORE) {
return;
}
var storedKeys = goog.debug.FancyWindow.getStoredKeys_();
for (var key in storedKeys) {
var loggerName = key.replace(goog.debug.FancyWindow.LOCAL_STORE_PREFIX, '');
var logger = goog.debug.Logger.getLogger(loggerName);
var curLevel = logger.getLevel();
var storedLevel = window.localStorage.getItem(key).toString();
if (!curLevel || curLevel.toString() != storedLevel) {
logger.setLevel(goog.debug.Logger.Level.getPredefinedLevel(storedLevel));
}
}
};
/**
* Helper function to create a list of locally stored keys. Used to avoid
* expensive localStorage.getItem() calls.
* @return {Object} List of keys.
* @private
*/
goog.debug.FancyWindow.getStoredKeys_ = function() {
var storedKeys = {};
for (var i = 0, len = window.localStorage.length; i < len; i++) {
var key = window.localStorage.key(i);
if (key != null && goog.string.startsWith(
key, goog.debug.FancyWindow.LOCAL_STORE_PREFIX)) {
storedKeys[key] = true;
}
}
return storedKeys;
};
/**
* Gets a sorted array of all the loggers registered
* @return {Array} Array of logger idents, e.g. goog.net.XhrIo.
* @private
*/
goog.debug.FancyWindow.getLoggers_ = function() {
var loggers = goog.object.getKeys(goog.debug.LogManager.getLoggers());
loggers.sort();
return loggers;
};

View File

@@ -0,0 +1,316 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition of various formatters for logging. Please minimize
* dependencies this file has on other closure classes as any dependency it
* takes won't be able to use the logging infrastructure.
*
*/
goog.provide('goog.debug.Formatter');
goog.provide('goog.debug.HtmlFormatter');
goog.provide('goog.debug.TextFormatter');
goog.require('goog.debug.RelativeTimeProvider');
goog.require('goog.string');
/**
* Base class for Formatters. A Formatter is used to format a LogRecord into
* something that can be displayed to the user.
*
* @param {string=} opt_prefix The prefix to place before text records.
* @constructor
*/
goog.debug.Formatter = function(opt_prefix) {
this.prefix_ = opt_prefix || '';
/**
* A provider that returns the relative start time.
* @type {goog.debug.RelativeTimeProvider}
* @private
*/
this.startTimeProvider_ =
goog.debug.RelativeTimeProvider.getDefaultInstance();
};
/**
* Whether to show absolute time in the DebugWindow.
* @type {boolean}
*/
goog.debug.Formatter.prototype.showAbsoluteTime = true;
/**
* Whether to show relative time in the DebugWindow.
* @type {boolean}
*/
goog.debug.Formatter.prototype.showRelativeTime = true;
/**
* Whether to show the logger name in the DebugWindow.
* @type {boolean}
*/
goog.debug.Formatter.prototype.showLoggerName = true;
/**
* Whether to show the logger exception text.
* @type {boolean}
*/
goog.debug.Formatter.prototype.showExceptionText = false;
/**
* Whether to show the severity level.
* @type {boolean}
*/
goog.debug.Formatter.prototype.showSeverityLevel = false;
/**
* Formats a record.
* @param {goog.debug.LogRecord} logRecord the logRecord to format.
* @return {string} The formatted string.
*/
goog.debug.Formatter.prototype.formatRecord = goog.abstractMethod;
/**
* Sets the start time provider. By default, this is the default instance
* but can be changed.
* @param {goog.debug.RelativeTimeProvider} provider The provider to use.
*/
goog.debug.Formatter.prototype.setStartTimeProvider = function(provider) {
this.startTimeProvider_ = provider;
};
/**
* Returns the start time provider. By default, this is the default instance
* but can be changed.
* @return {goog.debug.RelativeTimeProvider} The start time provider.
*/
goog.debug.Formatter.prototype.getStartTimeProvider = function() {
return this.startTimeProvider_;
};
/**
* Resets the start relative time.
*/
goog.debug.Formatter.prototype.resetRelativeTimeStart = function() {
this.startTimeProvider_.reset();
};
/**
* Returns a string for the time/date of the LogRecord.
* @param {goog.debug.LogRecord} logRecord The record to get a time stamp for.
* @return {string} A string representation of the time/date of the LogRecord.
* @private
*/
goog.debug.Formatter.getDateTimeStamp_ = function(logRecord) {
var time = new Date(logRecord.getMillis());
return goog.debug.Formatter.getTwoDigitString_((time.getFullYear() - 2000)) +
goog.debug.Formatter.getTwoDigitString_((time.getMonth() + 1)) +
goog.debug.Formatter.getTwoDigitString_(time.getDate()) + ' ' +
goog.debug.Formatter.getTwoDigitString_(time.getHours()) + ':' +
goog.debug.Formatter.getTwoDigitString_(time.getMinutes()) + ':' +
goog.debug.Formatter.getTwoDigitString_(time.getSeconds()) + '.' +
goog.debug.Formatter.getTwoDigitString_(
Math.floor(time.getMilliseconds() / 10));
};
/**
* Returns the number as a two-digit string, meaning it prepends a 0 if the
* number if less than 10.
* @param {number} n The number to format.
* @return {string} A two-digit string representation of {@code n}.
* @private
*/
goog.debug.Formatter.getTwoDigitString_ = function(n) {
if (n < 10) {
return '0' + n;
}
return String(n);
};
/**
* Returns a string for the number of seconds relative to the start time.
* Prepads with spaces so that anything less than 1000 seconds takes up the
* same number of characters for better formatting.
* @param {goog.debug.LogRecord} logRecord The log to compare time to.
* @param {number} relativeTimeStart The start time to compare to.
* @return {string} The number of seconds of the LogRecord relative to the
* start time.
* @private
*/
goog.debug.Formatter.getRelativeTime_ = function(logRecord,
relativeTimeStart) {
var ms = logRecord.getMillis() - relativeTimeStart;
var sec = ms / 1000;
var str = sec.toFixed(3);
var spacesToPrepend = 0;
if (sec < 1) {
spacesToPrepend = 2;
} else {
while (sec < 100) {
spacesToPrepend++;
sec *= 10;
}
}
while (spacesToPrepend-- > 0) {
str = ' ' + str;
}
return str;
};
/**
* Formatter that returns formatted html. See formatRecord for the classes
* it uses for various types of formatted output.
*
* @param {string=} opt_prefix The prefix to place before text records.
* @constructor
* @extends {goog.debug.Formatter}
*/
goog.debug.HtmlFormatter = function(opt_prefix) {
goog.debug.Formatter.call(this, opt_prefix);
};
goog.inherits(goog.debug.HtmlFormatter, goog.debug.Formatter);
/**
* Whether to show the logger exception text
* @type {boolean}
* @override
*/
goog.debug.HtmlFormatter.prototype.showExceptionText = true;
/**
* Formats a record
* @param {goog.debug.LogRecord} logRecord the logRecord to format.
* @return {string} The formatted string as html.
* @override
*/
goog.debug.HtmlFormatter.prototype.formatRecord = function(logRecord) {
var className;
switch (logRecord.getLevel().value) {
case goog.debug.Logger.Level.SHOUT.value:
className = 'dbg-sh';
break;
case goog.debug.Logger.Level.SEVERE.value:
className = 'dbg-sev';
break;
case goog.debug.Logger.Level.WARNING.value:
className = 'dbg-w';
break;
case goog.debug.Logger.Level.INFO.value:
className = 'dbg-i';
break;
case goog.debug.Logger.Level.FINE.value:
default:
className = 'dbg-f';
break;
}
// Build message html
var sb = [];
sb.push(this.prefix_, ' ');
if (this.showAbsoluteTime) {
sb.push('[', goog.debug.Formatter.getDateTimeStamp_(logRecord), '] ');
}
if (this.showRelativeTime) {
sb.push('[',
goog.string.whitespaceEscape(
goog.debug.Formatter.getRelativeTime_(logRecord,
this.startTimeProvider_.get())),
's] ');
}
if (this.showLoggerName) {
sb.push('[', goog.string.htmlEscape(logRecord.getLoggerName()), '] ');
}
if (this.showSeverityLevel) {
sb.push('[', goog.string.htmlEscape(logRecord.getLevel().name), '] ');
}
sb.push('<span class="', className, '">',
goog.string.newLineToBr(goog.string.whitespaceEscape(
goog.string.htmlEscape(logRecord.getMessage()))));
if (this.showExceptionText && logRecord.getException()) {
sb.push('<br>',
goog.string.newLineToBr(goog.string.whitespaceEscape(
logRecord.getExceptionText() || '')));
}
sb.push('</span><br>');
return sb.join('');
};
/**
* Formatter that returns formatted plain text
*
* @param {string=} opt_prefix The prefix to place before text records.
* @constructor
* @extends {goog.debug.Formatter}
*/
goog.debug.TextFormatter = function(opt_prefix) {
goog.debug.Formatter.call(this, opt_prefix);
};
goog.inherits(goog.debug.TextFormatter, goog.debug.Formatter);
/**
* Formats a record as text
* @param {goog.debug.LogRecord} logRecord the logRecord to format.
* @return {string} The formatted string.
* @override
*/
goog.debug.TextFormatter.prototype.formatRecord = function(logRecord) {
// Build message html
var sb = [];
sb.push(this.prefix_, ' ');
if (this.showAbsoluteTime) {
sb.push('[', goog.debug.Formatter.getDateTimeStamp_(logRecord), '] ');
}
if (this.showRelativeTime) {
sb.push('[', goog.debug.Formatter.getRelativeTime_(logRecord,
this.startTimeProvider_.get()), 's] ');
}
if (this.showLoggerName) {
sb.push('[', logRecord.getLoggerName(), '] ');
}
if (this.showSeverityLevel) {
sb.push('[', logRecord.getLevel().name, '] ');
}
sb.push(logRecord.getMessage(), '\n');
if (this.showExceptionText && logRecord.getException()) {
sb.push(logRecord.getExceptionText(), '\n');
}
return sb.join('');
};

View File

@@ -0,0 +1,162 @@
// Copyright 2011 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 Displays frames per second (FPS) for the current window.
* Only supported in browsers that support requestAnimationFrame.
* See: https://developer.mozilla.org/en/DOM/window.requestAnimationFrame.
*
* @see ../demos/fpsdisplay.html
*/
goog.provide('goog.debug.FpsDisplay');
goog.require('goog.asserts');
goog.require('goog.async.AnimationDelay');
goog.require('goog.ui.Component');
/**
* Displays frames per seconds that the window this component is
* rendered in is animating at.
*
* @param {goog.dom.DomHelper=} opt_domHelper An optional dom helper.
* @constructor
* @extends {goog.ui.Component}
*/
goog.debug.FpsDisplay = function(opt_domHelper) {
goog.base(this, opt_domHelper);
};
goog.inherits(goog.debug.FpsDisplay, goog.ui.Component);
/**
* CSS class for the FPS display.
*/
goog.debug.FpsDisplay.CSS = goog.getCssName('goog-fps-display');
/**
* The number of samples per FPS report.
*/
goog.debug.FpsDisplay.SAMPLES = 10;
/**
* The current animation.
* @type {goog.debug.FpsDisplay.FpsAnimation_}
* @private
*/
goog.debug.FpsDisplay.prototype.animation_ = null;
/** @override */
goog.debug.FpsDisplay.prototype.createDom = function() {
this.setElementInternal(
this.getDomHelper().createDom('div', goog.debug.FpsDisplay.CSS));
};
/** @override */
goog.debug.FpsDisplay.prototype.enterDocument = function() {
goog.base(this, 'enterDocument');
this.animation_ = new goog.debug.FpsDisplay.FpsAnimation_(this.getElement());
this.delay_ = new goog.async.AnimationDelay(
this.handleDelay_, this.getDomHelper().getWindow(), this);
this.delay_.start();
};
/**
* @param {number} now The current time.
* @private
*/
goog.debug.FpsDisplay.prototype.handleDelay_ = function(now) {
if (this.isInDocument()) {
this.animation_.onAnimationFrame(now);
this.delay_.start();
}
};
/** @override */
goog.debug.FpsDisplay.prototype.exitDocument = function() {
goog.base(this, 'exitDocument');
this.animation_ = null;
goog.dispose(this.delay_);
};
/**
* @return {number} The average frames per second.
*/
goog.debug.FpsDisplay.prototype.getFps = function() {
goog.asserts.assert(
this.isInDocument(), 'Render the FPS display before querying FPS');
return this.animation_.lastFps_;
};
/**
* @param {Element} elem An element to hold the FPS count.
* @constructor
* @private
*/
goog.debug.FpsDisplay.FpsAnimation_ = function(elem) {
/**
* An element to hold the current FPS rate.
* @type {Element}
* @private
*/
this.element_ = elem;
/**
* The number of frames observed so far.
* @type {number}
* @private
*/
this.frameNumber_ = 0;
};
/**
* The last time which we reported FPS at.
* @type {number}
* @private
*/
goog.debug.FpsDisplay.FpsAnimation_.prototype.lastTime_ = 0;
/**
* The last average FPS.
* @type {number}
* @private
*/
goog.debug.FpsDisplay.FpsAnimation_.prototype.lastFps_ = -1;
/**
* @param {number} now The current time.
*/
goog.debug.FpsDisplay.FpsAnimation_.prototype.onAnimationFrame = function(now) {
var SAMPLES = goog.debug.FpsDisplay.SAMPLES;
if (this.frameNumber_ % SAMPLES == 0) {
this.lastFps_ = Math.round((1000 * SAMPLES) / (now - this.lastTime_));
this.element_.innerHTML = this.lastFps_;
this.lastTime_ = now;
}
this.frameNumber_++;
};

View File

@@ -0,0 +1,143 @@
// Copyright 2007 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 Definition of the GcDiagnostics class.
*
*/
goog.provide('goog.debug.GcDiagnostics');
goog.require('goog.debug.Trace');
goog.require('goog.log');
goog.require('goog.userAgent');
/**
* Class used for singleton goog.debug.GcDiagnostics. Used to hook into
* the L2 ActiveX controller to profile garbage collection information in IE.
* Can be used in combination with tracers (goog.debug.Trace), to provide object
* allocation counts from within the tracers or used alone by invoking start and
* stop.
*
* See http://go/l2binary for the install.
* TODO(user): Move the L2 installer somewhere more general.
* @constructor
* @private
*/
goog.debug.GcDiagnostics_ = function() {};
/**
* Install the GcDiagnostics tool.
*/
goog.debug.GcDiagnostics_.prototype.install = function() {
if (goog.userAgent.IE) {
/** @preserveTry */
try {
var l2Helper = new ActiveXObject('L2.NativeHelper');
// If using tracers, use the higher precision timer provided by L2.
if (goog.debug.Trace_) {
goog.debug.Trace_.now = function() {
return l2Helper['getMilliSeconds']();
};
}
if (l2Helper['gcTracer']) {
l2Helper['gcTracer']['installGcTracing']();
this.gcTracer_ = l2Helper['gcTracer'];
if (goog.debug.Trace) {
// If tracers are in use, register the gcTracer so that per tracer
// allocations are recorded.
goog.debug.Trace.setGcTracer(this.gcTracer_);
}
}
goog.log.info(this.logger_, 'Installed L2 native helper');
} catch (e) {
goog.log.info(this.logger_, 'Failed to install L2 native helper: ' + e);
}
}
};
/**
* Logger for the gcDiagnotics
* @type {goog.log.Logger}
* @private
*/
goog.debug.GcDiagnostics_.prototype.logger_ =
goog.log.getLogger('goog.debug.GcDiagnostics');
/**
* Starts recording garbage collection information. If a trace is already in
* progress, it is ended.
*/
goog.debug.GcDiagnostics_.prototype.start = function() {
if (this.gcTracer_) {
if (this.gcTracer_['isTracing']()) {
this.gcTracer_['endGcTracing']();
}
this.gcTracer_['startGcTracing']();
}
};
/**
* Stops recording garbage collection information. Logs details on the garbage
* collections that occurred between start and stop. If tracers are in use,
* adds comments where each GC occurs.
*/
goog.debug.GcDiagnostics_.prototype.stop = function() {
if (this.gcTracer_ && this.gcTracer_['isTracing']()) {
var gcTracer = this.gcTracer_;
this.gcTracer_['endGcTracing']();
var numGCs = gcTracer['getNumTraces']();
goog.log.info(this.logger_, '*********GC TRACE*********');
goog.log.info(this.logger_, 'GC ran ' + numGCs + ' times.');
var totalTime = 0;
for (var i = 0; i < numGCs; i++) {
var trace = gcTracer['getTrace'](i);
var msStart = trace['gcStartTime'];
var msElapsed = trace['gcElapsedTime'];
var msRounded = Math.round(msElapsed * 10) / 10;
var s = 'GC ' + i + ': ' + msRounded + ' ms, ' +
'numVValAlloc=' + trace['numVValAlloc'] + ', ' +
'numVarAlloc=' + trace['numVarAlloc'] + ', ' +
'numBytesSysAlloc=' + trace['numBytesSysAlloc'];
if (goog.debug.Trace) {
goog.debug.Trace.addComment(s, null, msStart);
}
goog.log.info(this.logger_, s);
totalTime += msElapsed;
}
if (goog.debug.Trace) {
goog.debug.Trace.addComment('Total GC time was ' + totalTime + ' ms.');
}
goog.log.info(this.logger_, 'Total GC time was ' + totalTime + ' ms.');
goog.log.info(this.logger_, '*********GC TRACE*********');
}
};
/**
* Singleton GcDiagnostics object
* @type {goog.debug.GcDiagnostics_}
*/
goog.debug.GcDiagnostics = new goog.debug.GcDiagnostics_();

View File

@@ -0,0 +1,147 @@
// 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 buffer for log records. The purpose of this is to improve
* logging performance by re-using old objects when the buffer becomes full and
* to eliminate the need for each app to implement their own log buffer. The
* disadvantage to doing this is that log handlers cannot maintain references to
* log records and expect that they are not overwriten at a later point.
*
* @author agrieve@google.com (Andrew Grieve)
*/
goog.provide('goog.debug.LogBuffer');
goog.require('goog.asserts');
goog.require('goog.debug.LogRecord');
/**
* Creates the log buffer.
* @constructor
*/
goog.debug.LogBuffer = function() {
goog.asserts.assert(goog.debug.LogBuffer.isBufferingEnabled(),
'Cannot use goog.debug.LogBuffer without defining ' +
'goog.debug.LogBuffer.CAPACITY.');
this.clear();
};
/**
* A static method that always returns the same instance of LogBuffer.
* @return {!goog.debug.LogBuffer} The LogBuffer singleton instance.
*/
goog.debug.LogBuffer.getInstance = function() {
if (!goog.debug.LogBuffer.instance_) {
// This function is written with the return statement after the assignment
// to avoid the jscompiler StripCode bug described in http://b/2608064.
// After that bug is fixed this can be refactored.
goog.debug.LogBuffer.instance_ = new goog.debug.LogBuffer();
}
return goog.debug.LogBuffer.instance_;
};
/**
* @define {number} The number of log records to buffer. 0 means disable
* buffering.
*/
goog.define('goog.debug.LogBuffer.CAPACITY', 0);
/**
* The array to store the records.
* @type {!Array.<!goog.debug.LogRecord|undefined>}
* @private
*/
goog.debug.LogBuffer.prototype.buffer_;
/**
* The index of the most recently added record or -1 if there are no records.
* @type {number}
* @private
*/
goog.debug.LogBuffer.prototype.curIndex_;
/**
* Whether the buffer is at capacity.
* @type {boolean}
* @private
*/
goog.debug.LogBuffer.prototype.isFull_;
/**
* Adds a log record to the buffer, possibly overwriting the oldest record.
* @param {goog.debug.Logger.Level} level One of the level identifiers.
* @param {string} msg The string message.
* @param {string} loggerName The name of the source logger.
* @return {!goog.debug.LogRecord} The log record.
*/
goog.debug.LogBuffer.prototype.addRecord = function(level, msg, loggerName) {
var curIndex = (this.curIndex_ + 1) % goog.debug.LogBuffer.CAPACITY;
this.curIndex_ = curIndex;
if (this.isFull_) {
var ret = this.buffer_[curIndex];
ret.reset(level, msg, loggerName);
return ret;
}
this.isFull_ = curIndex == goog.debug.LogBuffer.CAPACITY - 1;
return this.buffer_[curIndex] =
new goog.debug.LogRecord(level, msg, loggerName);
};
/**
* @return {boolean} Whether the log buffer is enabled.
*/
goog.debug.LogBuffer.isBufferingEnabled = function() {
return goog.debug.LogBuffer.CAPACITY > 0;
};
/**
* Removes all buffered log records.
*/
goog.debug.LogBuffer.prototype.clear = function() {
this.buffer_ = new Array(goog.debug.LogBuffer.CAPACITY);
this.curIndex_ = -1;
this.isFull_ = false;
};
/**
* Calls the given function for each buffered log record, starting with the
* oldest one.
* @param {function(!goog.debug.LogRecord)} func The function to call.
*/
goog.debug.LogBuffer.prototype.forEachRecord = function(func) {
var buffer = this.buffer_;
// Corner case: no records.
if (!buffer[0]) {
return;
}
var curIndex = this.curIndex_;
var i = this.isFull_ ? curIndex : -1;
do {
i = (i + 1) % goog.debug.LogBuffer.CAPACITY;
func(/** @type {!goog.debug.LogRecord} */ (buffer[i]));
} while (i != curIndex);
};

View File

@@ -0,0 +1,853 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition of the Logger class. Please minimize dependencies
* this file has on other closure classes as any dependency it takes won't be
* able to use the logging infrastructure.
*
* @see ../demos/debug.html
*/
goog.provide('goog.debug.LogManager');
goog.provide('goog.debug.Logger');
goog.provide('goog.debug.Logger.Level');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.debug');
goog.require('goog.debug.LogBuffer');
goog.require('goog.debug.LogRecord');
/**
* The Logger is an object used for logging debug messages. Loggers are
* normally named, using a hierarchical dot-separated namespace. Logger names
* can be arbitrary strings, but they should normally be based on the package
* name or class name of the logged component, such as goog.net.BrowserChannel.
*
* The Logger object is loosely based on the java class
* java.util.logging.Logger. It supports different levels of filtering for
* different loggers.
*
* The logger object should never be instantiated by application code. It
* should always use the goog.debug.Logger.getLogger function.
*
* @constructor
* @param {string} name The name of the Logger.
*/
goog.debug.Logger = function(name) {
/**
* Name of the Logger. Generally a dot-separated namespace
* @type {string}
* @private
*/
this.name_ = name;
};
/**
* Parent Logger.
* @type {goog.debug.Logger}
* @private
*/
goog.debug.Logger.prototype.parent_ = null;
/**
* Level that this logger only filters above. Null indicates it should
* inherit from the parent.
* @type {goog.debug.Logger.Level}
* @private
*/
goog.debug.Logger.prototype.level_ = null;
/**
* Map of children loggers. The keys are the leaf names of the children and
* the values are the child loggers.
* @type {Object}
* @private
*/
goog.debug.Logger.prototype.children_ = null;
/**
* Handlers that are listening to this logger.
* @type {Array.<Function>}
* @private
*/
goog.debug.Logger.prototype.handlers_ = null;
/**
* @define {boolean} Toggles whether loggers other than the root logger can have
* log handlers attached to them and whether they can have their log level
* set. Logging is a bit faster when this is set to false.
*/
goog.define('goog.debug.Logger.ENABLE_HIERARCHY', true);
if (!goog.debug.Logger.ENABLE_HIERARCHY) {
/**
* @type {!Array.<Function>}
* @private
*/
goog.debug.Logger.rootHandlers_ = [];
/**
* @type {goog.debug.Logger.Level}
* @private
*/
goog.debug.Logger.rootLevel_;
}
/**
* The Level class defines a set of standard logging levels that
* can be used to control logging output. The logging Level objects
* are ordered and are specified by ordered integers. Enabling logging
* at a given level also enables logging at all higher levels.
* <p>
* Clients should normally use the predefined Level constants such
* as Level.SEVERE.
* <p>
* The levels in descending order are:
* <ul>
* <li>SEVERE (highest value)
* <li>WARNING
* <li>INFO
* <li>CONFIG
* <li>FINE
* <li>FINER
* <li>FINEST (lowest value)
* </ul>
* In addition there is a level OFF that can be used to turn
* off logging, and a level ALL that can be used to enable
* logging of all messages.
*
* @param {string} name The name of the level.
* @param {number} value The numeric value of the level.
* @constructor
*/
goog.debug.Logger.Level = function(name, value) {
/**
* The name of the level
* @type {string}
*/
this.name = name;
/**
* The numeric value of the level
* @type {number}
*/
this.value = value;
};
/**
* @return {string} String representation of the logger level.
* @override
*/
goog.debug.Logger.Level.prototype.toString = function() {
return this.name;
};
/**
* OFF is a special level that can be used to turn off logging.
* This level is initialized to <CODE>Infinity</CODE>.
* @type {!goog.debug.Logger.Level}
*/
goog.debug.Logger.Level.OFF =
new goog.debug.Logger.Level('OFF', Infinity);
/**
* SHOUT is a message level for extra debugging loudness.
* This level is initialized to <CODE>1200</CODE>.
* @type {!goog.debug.Logger.Level}
*/
goog.debug.Logger.Level.SHOUT = new goog.debug.Logger.Level('SHOUT', 1200);
/**
* SEVERE is a message level indicating a serious failure.
* This level is initialized to <CODE>1000</CODE>.
* @type {!goog.debug.Logger.Level}
*/
goog.debug.Logger.Level.SEVERE = new goog.debug.Logger.Level('SEVERE', 1000);
/**
* WARNING is a message level indicating a potential problem.
* This level is initialized to <CODE>900</CODE>.
* @type {!goog.debug.Logger.Level}
*/
goog.debug.Logger.Level.WARNING = new goog.debug.Logger.Level('WARNING', 900);
/**
* INFO is a message level for informational messages.
* This level is initialized to <CODE>800</CODE>.
* @type {!goog.debug.Logger.Level}
*/
goog.debug.Logger.Level.INFO = new goog.debug.Logger.Level('INFO', 800);
/**
* CONFIG is a message level for static configuration messages.
* This level is initialized to <CODE>700</CODE>.
* @type {!goog.debug.Logger.Level}
*/
goog.debug.Logger.Level.CONFIG = new goog.debug.Logger.Level('CONFIG', 700);
/**
* FINE is a message level providing tracing information.
* This level is initialized to <CODE>500</CODE>.
* @type {!goog.debug.Logger.Level}
*/
goog.debug.Logger.Level.FINE = new goog.debug.Logger.Level('FINE', 500);
/**
* FINER indicates a fairly detailed tracing message.
* This level is initialized to <CODE>400</CODE>.
* @type {!goog.debug.Logger.Level}
*/
goog.debug.Logger.Level.FINER = new goog.debug.Logger.Level('FINER', 400);
/**
* FINEST indicates a highly detailed tracing message.
* This level is initialized to <CODE>300</CODE>.
* @type {!goog.debug.Logger.Level}
*/
goog.debug.Logger.Level.FINEST = new goog.debug.Logger.Level('FINEST', 300);
/**
* ALL indicates that all messages should be logged.
* This level is initialized to <CODE>0</CODE>.
* @type {!goog.debug.Logger.Level}
*/
goog.debug.Logger.Level.ALL = new goog.debug.Logger.Level('ALL', 0);
/**
* The predefined levels.
* @type {!Array.<!goog.debug.Logger.Level>}
* @final
*/
goog.debug.Logger.Level.PREDEFINED_LEVELS = [
goog.debug.Logger.Level.OFF,
goog.debug.Logger.Level.SHOUT,
goog.debug.Logger.Level.SEVERE,
goog.debug.Logger.Level.WARNING,
goog.debug.Logger.Level.INFO,
goog.debug.Logger.Level.CONFIG,
goog.debug.Logger.Level.FINE,
goog.debug.Logger.Level.FINER,
goog.debug.Logger.Level.FINEST,
goog.debug.Logger.Level.ALL];
/**
* A lookup map used to find the level object based on the name or value of
* the level object.
* @type {Object}
* @private
*/
goog.debug.Logger.Level.predefinedLevelsCache_ = null;
/**
* Creates the predefined levels cache and populates it.
* @private
*/
goog.debug.Logger.Level.createPredefinedLevelsCache_ = function() {
goog.debug.Logger.Level.predefinedLevelsCache_ = {};
for (var i = 0, level; level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];
i++) {
goog.debug.Logger.Level.predefinedLevelsCache_[level.value] = level;
goog.debug.Logger.Level.predefinedLevelsCache_[level.name] = level;
}
};
/**
* Gets the predefined level with the given name.
* @param {string} name The name of the level.
* @return {goog.debug.Logger.Level} The level, or null if none found.
*/
goog.debug.Logger.Level.getPredefinedLevel = function(name) {
if (!goog.debug.Logger.Level.predefinedLevelsCache_) {
goog.debug.Logger.Level.createPredefinedLevelsCache_();
}
return goog.debug.Logger.Level.predefinedLevelsCache_[name] || null;
};
/**
* Gets the highest predefined level <= #value.
* @param {number} value Level value.
* @return {goog.debug.Logger.Level} The level, or null if none found.
*/
goog.debug.Logger.Level.getPredefinedLevelByValue = function(value) {
if (!goog.debug.Logger.Level.predefinedLevelsCache_) {
goog.debug.Logger.Level.createPredefinedLevelsCache_();
}
if (value in goog.debug.Logger.Level.predefinedLevelsCache_) {
return goog.debug.Logger.Level.predefinedLevelsCache_[value];
}
for (var i = 0; i < goog.debug.Logger.Level.PREDEFINED_LEVELS.length; ++i) {
var level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];
if (level.value <= value) {
return level;
}
}
return null;
};
/**
* Finds or creates a logger for a named subsystem. If a logger has already been
* created with the given name it is returned. Otherwise a new logger is
* created. If a new logger is created its log level will be configured based
* on the LogManager configuration and it will configured to also send logging
* output to its parent's handlers. It will be registered in the LogManager
* global namespace.
*
* @param {string} name A name for the logger. This should be a dot-separated
* name and should normally be based on the package name or class name of the
* subsystem, such as goog.net.BrowserChannel.
* @return {!goog.debug.Logger} The named logger.
*/
goog.debug.Logger.getLogger = function(name) {
return goog.debug.LogManager.getLogger(name);
};
/**
* Logs a message to profiling tools, if available.
* {@see http://code.google.com/webtoolkit/speedtracer/logging-api.html}
* {@see http://msdn.microsoft.com/en-us/library/dd433074(VS.85).aspx}
* @param {string} msg The message to log.
*/
goog.debug.Logger.logToProfilers = function(msg) {
// Using goog.global, as loggers might be used in window-less contexts.
if (goog.global['console']) {
if (goog.global['console']['timeStamp']) {
// Logs a message to Firebug, Web Inspector, SpeedTracer, etc.
goog.global['console']['timeStamp'](msg);
} else if (goog.global['console']['markTimeline']) {
// TODO(user): markTimeline is deprecated. Drop this else clause entirely
// after Chrome M14 hits stable.
goog.global['console']['markTimeline'](msg);
}
}
if (goog.global['msWriteProfilerMark']) {
// Logs a message to the Microsoft profiler
goog.global['msWriteProfilerMark'](msg);
}
};
/**
* Gets the name of this logger.
* @return {string} The name of this logger.
*/
goog.debug.Logger.prototype.getName = function() {
return this.name_;
};
/**
* Adds a handler to the logger. This doesn't use the event system because
* we want to be able to add logging to the event system.
* @param {Function} handler Handler function to add.
*/
goog.debug.Logger.prototype.addHandler = function(handler) {
if (goog.debug.LOGGING_ENABLED) {
if (goog.debug.Logger.ENABLE_HIERARCHY) {
if (!this.handlers_) {
this.handlers_ = [];
}
this.handlers_.push(handler);
} else {
goog.asserts.assert(!this.name_,
'Cannot call addHandler on a non-root logger when ' +
'goog.debug.Logger.ENABLE_HIERARCHY is false.');
goog.debug.Logger.rootHandlers_.push(handler);
}
}
};
/**
* Removes a handler from the logger. This doesn't use the event system because
* we want to be able to add logging to the event system.
* @param {Function} handler Handler function to remove.
* @return {boolean} Whether the handler was removed.
*/
goog.debug.Logger.prototype.removeHandler = function(handler) {
if (goog.debug.LOGGING_ENABLED) {
var handlers = goog.debug.Logger.ENABLE_HIERARCHY ? this.handlers_ :
goog.debug.Logger.rootHandlers_;
return !!handlers && goog.array.remove(handlers, handler);
} else {
return false;
}
};
/**
* Returns the parent of this logger.
* @return {goog.debug.Logger} The parent logger or null if this is the root.
*/
goog.debug.Logger.prototype.getParent = function() {
return this.parent_;
};
/**
* Returns the children of this logger as a map of the child name to the logger.
* @return {!Object} The map where the keys are the child leaf names and the
* values are the Logger objects.
*/
goog.debug.Logger.prototype.getChildren = function() {
if (!this.children_) {
this.children_ = {};
}
return this.children_;
};
/**
* Set the log level specifying which message levels will be logged by this
* logger. Message levels lower than this value will be discarded.
* The level value Level.OFF can be used to turn off logging. If the new level
* is null, it means that this node should inherit its level from its nearest
* ancestor with a specific (non-null) level value.
*
* @param {goog.debug.Logger.Level} level The new level.
*/
goog.debug.Logger.prototype.setLevel = function(level) {
if (goog.debug.LOGGING_ENABLED) {
if (goog.debug.Logger.ENABLE_HIERARCHY) {
this.level_ = level;
} else {
goog.asserts.assert(!this.name_,
'Cannot call setLevel() on a non-root logger when ' +
'goog.debug.Logger.ENABLE_HIERARCHY is false.');
goog.debug.Logger.rootLevel_ = level;
}
}
};
/**
* Gets the log level specifying which message levels will be logged by this
* logger. Message levels lower than this value will be discarded.
* The level value Level.OFF can be used to turn off logging. If the level
* is null, it means that this node should inherit its level from its nearest
* ancestor with a specific (non-null) level value.
*
* @return {goog.debug.Logger.Level} The level.
*/
goog.debug.Logger.prototype.getLevel = function() {
return goog.debug.LOGGING_ENABLED ?
this.level_ : goog.debug.Logger.Level.OFF;
};
/**
* Returns the effective level of the logger based on its ancestors' levels.
* @return {goog.debug.Logger.Level} The level.
*/
goog.debug.Logger.prototype.getEffectiveLevel = function() {
if (!goog.debug.LOGGING_ENABLED) {
return goog.debug.Logger.Level.OFF;
}
if (!goog.debug.Logger.ENABLE_HIERARCHY) {
return goog.debug.Logger.rootLevel_;
}
if (this.level_) {
return this.level_;
}
if (this.parent_) {
return this.parent_.getEffectiveLevel();
}
goog.asserts.fail('Root logger has no level set.');
return null;
};
/**
* Checks if a message of the given level would actually be logged by this
* logger. This check is based on the Loggers effective level, which may be
* inherited from its parent.
* @param {goog.debug.Logger.Level} level The level to check.
* @return {boolean} Whether the message would be logged.
*/
goog.debug.Logger.prototype.isLoggable = function(level) {
return goog.debug.LOGGING_ENABLED &&
level.value >= this.getEffectiveLevel().value;
};
/**
* Logs a message. If the logger is currently enabled for the
* given message level then the given message is forwarded to all the
* registered output Handler objects.
* @param {goog.debug.Logger.Level} level One of the level identifiers.
* @param {string} msg The string message.
* @param {Error|Object=} opt_exception An exception associated with the
* message.
*/
goog.debug.Logger.prototype.log = function(level, msg, opt_exception) {
// java caches the effective level, not sure it's necessary here
if (goog.debug.LOGGING_ENABLED && this.isLoggable(level)) {
this.doLogRecord_(this.getLogRecord(level, msg, opt_exception));
}
};
/**
* Creates a new log record and adds the exception (if present) to it.
* @param {goog.debug.Logger.Level} level One of the level identifiers.
* @param {string} msg The string message.
* @param {Error|Object=} opt_exception An exception associated with the
* message.
* @return {!goog.debug.LogRecord} A log record.
*/
goog.debug.Logger.prototype.getLogRecord = function(level, msg, opt_exception) {
if (goog.debug.LogBuffer.isBufferingEnabled()) {
var logRecord =
goog.debug.LogBuffer.getInstance().addRecord(level, msg, this.name_);
} else {
logRecord = new goog.debug.LogRecord(level, String(msg), this.name_);
}
if (opt_exception) {
logRecord.setException(opt_exception);
logRecord.setExceptionText(
goog.debug.exposeException(opt_exception, arguments.callee.caller));
}
return logRecord;
};
/**
* Logs a message at the Logger.Level.SHOUT level.
* If the logger is currently enabled for the given message level then the
* given message is forwarded to all the registered output Handler objects.
* @param {string} msg The string message.
* @param {Error=} opt_exception An exception associated with the message.
*/
goog.debug.Logger.prototype.shout = function(msg, opt_exception) {
if (goog.debug.LOGGING_ENABLED) {
this.log(goog.debug.Logger.Level.SHOUT, msg, opt_exception);
}
};
/**
* Logs a message at the Logger.Level.SEVERE level.
* If the logger is currently enabled for the given message level then the
* given message is forwarded to all the registered output Handler objects.
* @param {string} msg The string message.
* @param {Error=} opt_exception An exception associated with the message.
*/
goog.debug.Logger.prototype.severe = function(msg, opt_exception) {
if (goog.debug.LOGGING_ENABLED) {
this.log(goog.debug.Logger.Level.SEVERE, msg, opt_exception);
}
};
/**
* Logs a message at the Logger.Level.WARNING level.
* If the logger is currently enabled for the given message level then the
* given message is forwarded to all the registered output Handler objects.
* @param {string} msg The string message.
* @param {Error=} opt_exception An exception associated with the message.
*/
goog.debug.Logger.prototype.warning = function(msg, opt_exception) {
if (goog.debug.LOGGING_ENABLED) {
this.log(goog.debug.Logger.Level.WARNING, msg, opt_exception);
}
};
/**
* Logs a message at the Logger.Level.INFO level.
* If the logger is currently enabled for the given message level then the
* given message is forwarded to all the registered output Handler objects.
* @param {string} msg The string message.
* @param {Error=} opt_exception An exception associated with the message.
*/
goog.debug.Logger.prototype.info = function(msg, opt_exception) {
if (goog.debug.LOGGING_ENABLED) {
this.log(goog.debug.Logger.Level.INFO, msg, opt_exception);
}
};
/**
* Logs a message at the Logger.Level.CONFIG level.
* If the logger is currently enabled for the given message level then the
* given message is forwarded to all the registered output Handler objects.
* @param {string} msg The string message.
* @param {Error=} opt_exception An exception associated with the message.
*/
goog.debug.Logger.prototype.config = function(msg, opt_exception) {
if (goog.debug.LOGGING_ENABLED) {
this.log(goog.debug.Logger.Level.CONFIG, msg, opt_exception);
}
};
/**
* Logs a message at the Logger.Level.FINE level.
* If the logger is currently enabled for the given message level then the
* given message is forwarded to all the registered output Handler objects.
* @param {string} msg The string message.
* @param {Error=} opt_exception An exception associated with the message.
*/
goog.debug.Logger.prototype.fine = function(msg, opt_exception) {
if (goog.debug.LOGGING_ENABLED) {
this.log(goog.debug.Logger.Level.FINE, msg, opt_exception);
}
};
/**
* Logs a message at the Logger.Level.FINER level.
* If the logger is currently enabled for the given message level then the
* given message is forwarded to all the registered output Handler objects.
* @param {string} msg The string message.
* @param {Error=} opt_exception An exception associated with the message.
*/
goog.debug.Logger.prototype.finer = function(msg, opt_exception) {
if (goog.debug.LOGGING_ENABLED) {
this.log(goog.debug.Logger.Level.FINER, msg, opt_exception);
}
};
/**
* Logs a message at the Logger.Level.FINEST level.
* If the logger is currently enabled for the given message level then the
* given message is forwarded to all the registered output Handler objects.
* @param {string} msg The string message.
* @param {Error=} opt_exception An exception associated with the message.
*/
goog.debug.Logger.prototype.finest = function(msg, opt_exception) {
if (goog.debug.LOGGING_ENABLED) {
this.log(goog.debug.Logger.Level.FINEST, msg, opt_exception);
}
};
/**
* Logs a LogRecord. If the logger is currently enabled for the
* given message level then the given message is forwarded to all the
* registered output Handler objects.
* @param {goog.debug.LogRecord} logRecord A log record to log.
*/
goog.debug.Logger.prototype.logRecord = function(logRecord) {
if (goog.debug.LOGGING_ENABLED && this.isLoggable(logRecord.getLevel())) {
this.doLogRecord_(logRecord);
}
};
/**
* Logs a LogRecord.
* @param {goog.debug.LogRecord} logRecord A log record to log.
* @private
*/
goog.debug.Logger.prototype.doLogRecord_ = function(logRecord) {
goog.debug.Logger.logToProfilers('log:' + logRecord.getMessage());
if (goog.debug.Logger.ENABLE_HIERARCHY) {
var target = this;
while (target) {
target.callPublish_(logRecord);
target = target.getParent();
}
} else {
for (var i = 0, handler; handler = goog.debug.Logger.rootHandlers_[i++]; ) {
handler(logRecord);
}
}
};
/**
* Calls the handlers for publish.
* @param {goog.debug.LogRecord} logRecord The log record to publish.
* @private
*/
goog.debug.Logger.prototype.callPublish_ = function(logRecord) {
if (this.handlers_) {
for (var i = 0, handler; handler = this.handlers_[i]; i++) {
handler(logRecord);
}
}
};
/**
* Sets the parent of this logger. This is used for setting up the logger tree.
* @param {goog.debug.Logger} parent The parent logger.
* @private
*/
goog.debug.Logger.prototype.setParent_ = function(parent) {
this.parent_ = parent;
};
/**
* Adds a child to this logger. This is used for setting up the logger tree.
* @param {string} name The leaf name of the child.
* @param {goog.debug.Logger} logger The child logger.
* @private
*/
goog.debug.Logger.prototype.addChild_ = function(name, logger) {
this.getChildren()[name] = logger;
};
/**
* There is a single global LogManager object that is used to maintain a set of
* shared state about Loggers and log services. This is loosely based on the
* java class java.util.logging.LogManager.
*/
goog.debug.LogManager = {};
/**
* Map of logger names to logger objects.
*
* @type {!Object}
* @private
*/
goog.debug.LogManager.loggers_ = {};
/**
* The root logger which is the root of the logger tree.
* @type {goog.debug.Logger}
* @private
*/
goog.debug.LogManager.rootLogger_ = null;
/**
* Initializes the LogManager if not already initialized.
*/
goog.debug.LogManager.initialize = function() {
if (!goog.debug.LogManager.rootLogger_) {
goog.debug.LogManager.rootLogger_ = new goog.debug.Logger('');
goog.debug.LogManager.loggers_[''] = goog.debug.LogManager.rootLogger_;
goog.debug.LogManager.rootLogger_.setLevel(goog.debug.Logger.Level.CONFIG);
}
};
/**
* Returns all the loggers.
* @return {!Object} Map of logger names to logger objects.
*/
goog.debug.LogManager.getLoggers = function() {
return goog.debug.LogManager.loggers_;
};
/**
* Returns the root of the logger tree namespace, the logger with the empty
* string as its name.
*
* @return {!goog.debug.Logger} The root logger.
*/
goog.debug.LogManager.getRoot = function() {
goog.debug.LogManager.initialize();
return /** @type {!goog.debug.Logger} */ (goog.debug.LogManager.rootLogger_);
};
/**
* Finds a named logger.
*
* @param {string} name A name for the logger. This should be a dot-separated
* name and should normally be based on the package name or class name of the
* subsystem, such as goog.net.BrowserChannel.
* @return {!goog.debug.Logger} The named logger.
*/
goog.debug.LogManager.getLogger = function(name) {
goog.debug.LogManager.initialize();
var ret = goog.debug.LogManager.loggers_[name];
return ret || goog.debug.LogManager.createLogger_(name);
};
/**
* Creates a function that can be passed to goog.debug.catchErrors. The function
* will log all reported errors using the given logger.
* @param {goog.debug.Logger=} opt_logger The logger to log the errors to.
* Defaults to the root logger.
* @return {function(Object)} The created function.
*/
goog.debug.LogManager.createFunctionForCatchErrors = function(opt_logger) {
return function(info) {
var logger = opt_logger || goog.debug.LogManager.getRoot();
logger.severe('Error: ' + info.message + ' (' + info.fileName +
' @ Line: ' + info.line + ')');
};
};
/**
* Creates the named logger. Will also create the parents of the named logger
* if they don't yet exist.
* @param {string} name The name of the logger.
* @return {!goog.debug.Logger} The named logger.
* @private
*/
goog.debug.LogManager.createLogger_ = function(name) {
// find parent logger
var logger = new goog.debug.Logger(name);
if (goog.debug.Logger.ENABLE_HIERARCHY) {
var lastDotIndex = name.lastIndexOf('.');
var parentName = name.substr(0, lastDotIndex);
var leafName = name.substr(lastDotIndex + 1);
var parentLogger = goog.debug.LogManager.getLogger(parentName);
// tell the parent about the child and the child about the parent
parentLogger.addChild_(leafName, logger);
logger.setParent_(parentLogger);
}
goog.debug.LogManager.loggers_[name] = logger;
return logger;
};

View File

@@ -0,0 +1,271 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition of the LogRecord class. Please minimize
* dependencies this file has on other closure classes as any dependency it
* takes won't be able to use the logging infrastructure.
*
*/
goog.provide('goog.debug.LogRecord');
/**
* LogRecord objects are used to pass logging requests between
* the logging framework and individual log Handlers.
* @constructor
* @param {goog.debug.Logger.Level} level One of the level identifiers.
* @param {string} msg The string message.
* @param {string} loggerName The name of the source logger.
* @param {number=} opt_time Time this log record was created if other than now.
* If 0, we use #goog.now.
* @param {number=} opt_sequenceNumber Sequence number of this log record. This
* should only be passed in when restoring a log record from persistence.
*/
goog.debug.LogRecord = function(level, msg, loggerName,
opt_time, opt_sequenceNumber) {
this.reset(level, msg, loggerName, opt_time, opt_sequenceNumber);
};
/**
* Time the LogRecord was created.
* @type {number}
* @private
*/
goog.debug.LogRecord.prototype.time_;
/**
* Level of the LogRecord
* @type {goog.debug.Logger.Level}
* @private
*/
goog.debug.LogRecord.prototype.level_;
/**
* Message associated with the record
* @type {string}
* @private
*/
goog.debug.LogRecord.prototype.msg_;
/**
* Name of the logger that created the record.
* @type {string}
* @private
*/
goog.debug.LogRecord.prototype.loggerName_;
/**
* Sequence number for the LogRecord. Each record has a unique sequence number
* that is greater than all log records created before it.
* @type {number}
* @private
*/
goog.debug.LogRecord.prototype.sequenceNumber_ = 0;
/**
* Exception associated with the record
* @type {Object}
* @private
*/
goog.debug.LogRecord.prototype.exception_ = null;
/**
* Exception text associated with the record
* @type {?string}
* @private
*/
goog.debug.LogRecord.prototype.exceptionText_ = null;
/**
* @define {boolean} Whether to enable log sequence numbers.
*/
goog.define('goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS', true);
/**
* A sequence counter for assigning increasing sequence numbers to LogRecord
* objects.
* @type {number}
* @private
*/
goog.debug.LogRecord.nextSequenceNumber_ = 0;
/**
* Sets all fields of the log record.
* @param {goog.debug.Logger.Level} level One of the level identifiers.
* @param {string} msg The string message.
* @param {string} loggerName The name of the source logger.
* @param {number=} opt_time Time this log record was created if other than now.
* If 0, we use #goog.now.
* @param {number=} opt_sequenceNumber Sequence number of this log record. This
* should only be passed in when restoring a log record from persistence.
*/
goog.debug.LogRecord.prototype.reset = function(level, msg, loggerName,
opt_time, opt_sequenceNumber) {
if (goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS) {
this.sequenceNumber_ = typeof opt_sequenceNumber == 'number' ?
opt_sequenceNumber : goog.debug.LogRecord.nextSequenceNumber_++;
}
this.time_ = opt_time || goog.now();
this.level_ = level;
this.msg_ = msg;
this.loggerName_ = loggerName;
delete this.exception_;
delete this.exceptionText_;
};
/**
* Get the source Logger's name.
*
* @return {string} source logger name (may be null).
*/
goog.debug.LogRecord.prototype.getLoggerName = function() {
return this.loggerName_;
};
/**
* Get the exception that is part of the log record.
*
* @return {Object} the exception.
*/
goog.debug.LogRecord.prototype.getException = function() {
return this.exception_;
};
/**
* Set the exception that is part of the log record.
*
* @param {Object} exception the exception.
*/
goog.debug.LogRecord.prototype.setException = function(exception) {
this.exception_ = exception;
};
/**
* Get the exception text that is part of the log record.
*
* @return {?string} Exception text.
*/
goog.debug.LogRecord.prototype.getExceptionText = function() {
return this.exceptionText_;
};
/**
* Set the exception text that is part of the log record.
*
* @param {string} text The exception text.
*/
goog.debug.LogRecord.prototype.setExceptionText = function(text) {
this.exceptionText_ = text;
};
/**
* Get the source Logger's name.
*
* @param {string} loggerName source logger name (may be null).
*/
goog.debug.LogRecord.prototype.setLoggerName = function(loggerName) {
this.loggerName_ = loggerName;
};
/**
* Get the logging message level, for example Level.SEVERE.
* @return {goog.debug.Logger.Level} the logging message level.
*/
goog.debug.LogRecord.prototype.getLevel = function() {
return this.level_;
};
/**
* Set the logging message level, for example Level.SEVERE.
* @param {goog.debug.Logger.Level} level the logging message level.
*/
goog.debug.LogRecord.prototype.setLevel = function(level) {
this.level_ = level;
};
/**
* Get the "raw" log message, before localization or formatting.
*
* @return {string} the raw message string.
*/
goog.debug.LogRecord.prototype.getMessage = function() {
return this.msg_;
};
/**
* Set the "raw" log message, before localization or formatting.
*
* @param {string} msg the raw message string.
*/
goog.debug.LogRecord.prototype.setMessage = function(msg) {
this.msg_ = msg;
};
/**
* Get event time in milliseconds since 1970.
*
* @return {number} event time in millis since 1970.
*/
goog.debug.LogRecord.prototype.getMillis = function() {
return this.time_;
};
/**
* Set event time in milliseconds since 1970.
*
* @param {number} time event time in millis since 1970.
*/
goog.debug.LogRecord.prototype.setMillis = function(time) {
this.time_ = time;
};
/**
* Get the sequence number.
* <p>
* Sequence numbers are normally assigned in the LogRecord
* constructor, which assigns unique sequence numbers to
* each new LogRecord in increasing order.
* @return {number} the sequence number.
*/
goog.debug.LogRecord.prototype.getSequenceNumber = function() {
return this.sequenceNumber_;
};

View File

@@ -0,0 +1,121 @@
// Copyright 2011 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 Static methods for serializing and deserializing log
* messages. These methods are deliberately kept separate from logrecord.js
* and logger.js because they add dependencies on goog.json and goog.object.
*
*/
goog.provide('goog.debug.logRecordSerializer');
goog.require('goog.debug.LogRecord');
goog.require('goog.debug.Logger.Level');
goog.require('goog.json');
goog.require('goog.object');
/**
* Enumeration of object keys used when serializing a log message.
* @enum {string}
* @private
*/
goog.debug.logRecordSerializer.Param_ = {
TIME: 't',
LEVEL_NAME: 'ln',
LEVEL_VALUE: 'lv',
MSG: 'm',
LOGGER_NAME: 'n',
SEQUENCE_NUMBER: 's',
EXCEPTION: 'e',
EXCEPTION_TEXT: 'et'
};
/**
* Serializes a LogRecord to a JSON string. Note that any associated
* exception is likely to be lost.
* @param {goog.debug.LogRecord} record The record to serialize.
* @return {string} Serialized JSON string of the log message.
*/
goog.debug.logRecordSerializer.serialize = function(record) {
var param = goog.debug.logRecordSerializer.Param_;
return goog.json.serialize(goog.object.create(
param.TIME, record.getMillis(),
param.LEVEL_NAME, record.getLevel().name,
param.LEVEL_VALUE, record.getLevel().value,
param.MSG, record.getMessage(),
param.LOGGER_NAME, record.getLoggerName(),
param.SEQUENCE_NUMBER, record.getSequenceNumber(),
param.EXCEPTION, record.getException(),
param.EXCEPTION_TEXT, record.getExceptionText()));
};
/**
* Deserializes a JSON-serialized LogRecord.
* @param {string} s The JSON serialized record.
* @return {!goog.debug.LogRecord} The deserialized record.
*/
goog.debug.logRecordSerializer.parse = function(s) {
return goog.debug.logRecordSerializer.reconstitute_(goog.json.parse(s));
};
/**
* Deserializes a JSON-serialized LogRecord. Use this only if you're
* naive enough to blindly trust any JSON formatted input that comes
* your way.
* @param {string} s The JSON serialized record.
* @return {!goog.debug.LogRecord} The deserialized record.
*/
goog.debug.logRecordSerializer.unsafeParse = function(s) {
return goog.debug.logRecordSerializer.reconstitute_(goog.json.unsafeParse(s));
};
/**
* Common reconsitution method for for parse and unsafeParse.
* @param {Object} o The JSON object.
* @return {!goog.debug.LogRecord} The reconstituted record.
* @private
*/
goog.debug.logRecordSerializer.reconstitute_ = function(o) {
var param = goog.debug.logRecordSerializer.Param_;
var level = goog.debug.logRecordSerializer.getLevel_(
o[param.LEVEL_NAME], o[param.LEVEL_VALUE]);
var ret = new goog.debug.LogRecord(level, o[param.MSG],
o[param.LOGGER_NAME], o[param.TIME], o[param.SEQUENCE_NUMBER]);
ret.setException(o[param.EXCEPTION]);
ret.setExceptionText(o[param.EXCEPTION_TEXT]);
return ret;
};
/**
* @param {string} name The name of the log level to return.
* @param {number} value The numeric value of the log level to return.
* @return {goog.debug.Logger.Level} Returns a goog.debug.Logger.Level with
* the specified name and value. If the name and value match a predefined
* log level, that instance will be returned, otherwise a new one will be
* created.
* @private
*/
goog.debug.logRecordSerializer.getLevel_ = function(name, value) {
var level = goog.debug.Logger.Level.getPredefinedLevel(name);
return level && level.value == value ?
level : new goog.debug.Logger.Level(name, value);
};

View File

@@ -0,0 +1,178 @@
// Copyright 2011 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 JavaScript reflection tools. They should only be used for
* debugging non-compiled code or tests, because there is no guarantee that
* they work consistently in all browsers.
*
*/
goog.provide('goog.debug.reflect');
/**
* Maps the unique id of the known constructors to their full names.
* Initialized lazily.
* @type {Object.<number, string>}
* @private
*/
goog.debug.reflect.typeMap_ = null;
/**
* List of all known constructors. Initialized lazily.
* @type {Array.<!Function>}
* @private
*/
goog.debug.reflect.constructors_ = null;
/**
* Copy of {@code Object.prototype.toString} to use if it is overridden later.
* Although saving the original {@code toString} somewhat protects against
* third-party libraries which touch {@code Object.prototype}, the actual goal
* of this assignment is to allow overriding that method, thus more debug
* information can be exposed about objects.
* See {@link goog.debug.reflect.typeOf}.
* @private
*/
goog.debug.reflect.toString_ = Object.prototype.toString;
/**
* Registers a type which will be recognized by goog.debug.reflect.typeOf.
* @param {string} name Full name of the type.
* @param {!Function} ctor The constructor.
* @private
*/
goog.debug.reflect.registerType_ = function(name, ctor) {
goog.debug.reflect.constructors_.push(ctor);
goog.debug.reflect.typeMap_[goog.getUid(ctor)] = name;
};
/**
* Adds all known constructors to the type registry.
* @private
*/
goog.debug.reflect.init_ = function() {
if (goog.debug.reflect.typeMap_) {
return;
}
goog.debug.reflect.typeMap_ = {};
goog.debug.reflect.constructors_ = [];
var implicitNs = goog.getObjectByName('goog.implicitNamespaces_') || {};
for (var ns in implicitNs) {
if (implicitNs.hasOwnProperty(ns)) {
var nsObj = goog.getObjectByName(ns);
for (var name in nsObj) {
if (nsObj.hasOwnProperty(name) && goog.isFunction(nsObj[name])) {
goog.debug.reflect.registerType_(ns + '.' + name, nsObj[name]);
}
}
}
}
goog.debug.reflect.registerType_('Array', Array);
goog.debug.reflect.registerType_('Boolean', Boolean);
goog.debug.reflect.registerType_('Date', Date);
goog.debug.reflect.registerType_('Error', Error);
goog.debug.reflect.registerType_('Function', Function);
goog.debug.reflect.registerType_('Number', Number);
goog.debug.reflect.registerType_('Object', Object);
goog.debug.reflect.registerType_('String', String);
// The compiler gets upset if we alias regexp directly, because
// then it can't optimize regexps as well. Just be sneaky about it,
// because this is only for debugging.
goog.debug.reflect.registerType_('RegExp', goog.global['RegExp']);
};
/**
* Returns the name of a type of object.
* @param {!Function} classConstructor A object constructor to get the name of.
* @return {string|undefined} The string name of the class.
*/
goog.debug.reflect.className = function(classConstructor) {
goog.debug.reflect.init_();
if (goog.isDefAndNotNull(classConstructor)) {
return goog.debug.reflect.typeMap_[goog.getUid(classConstructor)];
} else {
return undefined;
}
};
/**
* Guesses the real type of the object, even if its {@code toString} method is
* overridden. Gives exact result for all goog.provided classes in non-compiled
* code, and some often used native classes in compiled code too. Not tested in
* multi-frame environment.
*
* Example use case to get better type information in the Watch tab of FireBug:
* <pre>
* Object.prototype.toString = function() {
* return goog.debug.reflect.typeOf(this);
* };
* </pre>
*
* @param {*} obj An arbitrary variable to get the type of.
* @return {string} The namespaced type of the argument or 'Object' if didn't
* manage to determine it. Warning: in IE7 ActiveX (including DOM) objects
* don't expose their type to JavaScript. Their {@code constructor}
* property is undefined and they are not even the instances of the
* {@code Object} type. This method will recognize them as 'ActiveXObject'.
*/
goog.debug.reflect.typeOf = function(obj) {
// Check primitive types.
if (!obj || goog.isNumber(obj) || goog.isString(obj) || goog.isBoolean(obj)) {
return goog.typeOf(obj);
}
// Check if the type is present in the registry.
goog.debug.reflect.init_();
if (obj.constructor) {
// Some DOM objects such as document don't have constructor in IE7.
var type = goog.debug.reflect.typeMap_[goog.getUid(obj.constructor)];
if (type) {
return type;
}
}
// In IE8 the internal 'class' property of ActiveXObjects is Object, but
// String(obj) tells their real type.
var isActiveXObject = goog.global.ActiveXObject &&
obj instanceof ActiveXObject;
var typeString = isActiveXObject ? String(obj) :
goog.debug.reflect.toString_.call(/** @type {Object} */ (obj));
var match = typeString.match(/^\[object (\w+)\]$/);
if (match) {
var name = match[1];
var ctor = goog.global[name];
try {
if (obj instanceof ctor) {
return name;
}
} catch (e) {
// instanceof may fail if the guessed name is not a real type.
}
}
// Fall back to Object or ActiveXObject.
return isActiveXObject ? 'ActiveXObject' : 'Object';
};

View File

@@ -0,0 +1,83 @@
// Copyright 2007 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 Definition the goog.debug.RelativeTimeProvider class.
*
*/
goog.provide('goog.debug.RelativeTimeProvider');
/**
* A simple object to keep track of a timestamp considered the start of
* something. The main use is for the logger system to maintain a start time
* that is occasionally reset. For example, in Gmail, we reset this relative
* time at the start of a user action so that timings are offset from the
* beginning of the action. This class also provides a singleton as the default
* behavior for most use cases is to share the same start time.
*
* @constructor
*/
goog.debug.RelativeTimeProvider = function() {
/**
* The start time.
* @type {number}
* @private
*/
this.relativeTimeStart_ = goog.now();
};
/**
* Default instance.
* @type {goog.debug.RelativeTimeProvider}
* @private
*/
goog.debug.RelativeTimeProvider.defaultInstance_ =
new goog.debug.RelativeTimeProvider();
/**
* Sets the start time to the specified time.
* @param {number} timeStamp The start time.
*/
goog.debug.RelativeTimeProvider.prototype.set = function(timeStamp) {
this.relativeTimeStart_ = timeStamp;
};
/**
* Resets the start time to now.
*/
goog.debug.RelativeTimeProvider.prototype.reset = function() {
this.set(goog.now());
};
/**
* @return {number} The start time.
*/
goog.debug.RelativeTimeProvider.prototype.get = function() {
return this.relativeTimeStart_;
};
/**
* @return {goog.debug.RelativeTimeProvider} The default instance.
*/
goog.debug.RelativeTimeProvider.getDefaultInstance = function() {
return goog.debug.RelativeTimeProvider.defaultInstance_;
};

View File

@@ -0,0 +1,724 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition of the Tracer class and associated classes.
*
* @see ../demos/tracer.html
*/
goog.provide('goog.debug.Trace');
goog.require('goog.array');
goog.require('goog.iter');
goog.require('goog.log');
goog.require('goog.structs.Map');
goog.require('goog.structs.SimplePool');
/**
* Class used for singleton goog.debug.Trace. Used for timing slow points in
* the code. Based on the java Tracer class but optimized for javascript.
* See com.google.common.tracing.Tracer.
* @constructor
* @private
*/
goog.debug.Trace_ = function() {
/**
* Events in order.
* @type {Array.<goog.debug.Trace_.Event_>}
* @private
*/
this.events_ = [];
/**
* Outstanding events that have started but haven't yet ended. The keys are
* numeric ids and the values are goog.debug.Trace_.Event_ objects.
* @type {goog.structs.Map}
* @private
*/
this.outstandingEvents_ = new goog.structs.Map();
/**
* Start time of the event trace
* @type {number}
* @private
*/
this.startTime_ = 0;
/**
* Cummulative overhead of calls to startTracer
* @type {number}
* @private
*/
this.tracerOverheadStart_ = 0;
/**
* Cummulative overhead of calls to endTracer
* @type {number}
* @private
*/
this.tracerOverheadEnd_ = 0;
/**
* Cummulative overhead of calls to addComment
* @type {number}
* @private
*/
this.tracerOverheadComment_ = 0;
/**
* Keeps stats on different types of tracers. The keys are strings and the
* values are goog.debug.Stat
* @type {goog.structs.Map}
* @private
*/
this.stats_ = new goog.structs.Map();
/**
* Total number of traces created in the trace.
* @type {number}
* @private
*/
this.tracerCount_ = 0;
/**
* Total number of comments created in the trace.
* @type {number}
* @private
*/
this.commentCount_ = 0;
/**
* Next id to use for the trace.
* @type {number}
* @private
*/
this.nextId_ = 1;
/**
* A pool for goog.debug.Trace_.Event_ objects so we don't keep creating and
* garbage collecting these (which is very expensive in IE6).
* @type {goog.structs.SimplePool}
* @private
*/
this.eventPool_ = new goog.structs.SimplePool(0, 4000);
this.eventPool_.createObject = function() {
return new goog.debug.Trace_.Event_();
};
/**
* A pool for goog.debug.Trace_.Stat_ objects so we don't keep creating and
* garbage collecting these (which is very expensive in IE6).
* @type {goog.structs.SimplePool}
* @private
*/
this.statPool_ = new goog.structs.SimplePool(0, 50);
this.statPool_.createObject = function() {
return new goog.debug.Trace_.Stat_();
};
var that = this;
this.idPool_ = new goog.structs.SimplePool(0, 2000);
// TODO(nicksantos): SimplePool is supposed to only return objects.
// Reconcile this so that we don't have to cast to number below.
this.idPool_.createObject = function() {
return String(that.nextId_++);
};
this.idPool_.disposeObject = function(obj) {};
/**
* Default threshold below which a tracer shouldn't be reported
* @type {number}
* @private
*/
this.defaultThreshold_ = 3;
};
/**
* Logger for the tracer
* @type {goog.log.Logger}
* @private
*/
goog.debug.Trace_.prototype.logger_ =
goog.log.getLogger('goog.debug.Trace');
/**
* Maximum size of the trace before we discard events
* @type {number}
*/
goog.debug.Trace_.prototype.MAX_TRACE_SIZE = 1000;
/**
* Event type supported by tracer
* @enum {number}
*/
goog.debug.Trace_.EventType = {
/**
* Start event type
*/
START: 0,
/**
* Stop event type
*/
STOP: 1,
/**
* Comment event type
*/
COMMENT: 2
};
/**
* Class to keep track of a stat of a single tracer type. Stores the count
* and cumulative time.
* @constructor
* @private
*/
goog.debug.Trace_.Stat_ = function() {
/**
* Number of tracers
* @type {number}
*/
this.count = 0;
/**
* Cumulative time of traces
* @type {number}
*/
this.time = 0;
/**
* Total number of allocations for this tracer type
* @type {number}
*/
this.varAlloc = 0;
};
/**
* @type {string|null|undefined}
*/
goog.debug.Trace_.Stat_.prototype.type;
/**
* @return {string} A string describing the tracer stat.
* @override
*/
goog.debug.Trace_.Stat_.prototype.toString = function() {
var sb = [];
sb.push(this.type, ' ', this.count, ' (', Math.round(this.time * 10) / 10,
' ms)');
if (this.varAlloc) {
sb.push(' [VarAlloc = ', this.varAlloc, ']');
}
return sb.join('');
};
/**
* Private class used to encapsulate a single event, either the start or stop
* of a tracer.
* @constructor
* @private
*/
goog.debug.Trace_.Event_ = function() {
// the fields are different for different events - see usage in code
};
/**
* @type {string|null|undefined}
*/
goog.debug.Trace_.Event_.prototype.type;
/**
* Returns a formatted string for the event.
* @param {number} startTime The start time of the trace to generate relative
* times.
* @param {number} prevTime The completion time of the previous event or -1.
* @param {string} indent Extra indent for the message
* if there was no previous event.
* @return {string} The formatted tracer string.
*/
goog.debug.Trace_.Event_.prototype.toTraceString = function(startTime, prevTime,
indent) {
var sb = [];
if (prevTime == -1) {
sb.push(' ');
} else {
sb.push(goog.debug.Trace_.longToPaddedString_(this.eventTime - prevTime));
}
sb.push(' ', goog.debug.Trace_.formatTime_(this.eventTime - startTime));
if (this.eventType == goog.debug.Trace_.EventType.START) {
sb.push(' Start ');
} else if (this.eventType == goog.debug.Trace_.EventType.STOP) {
sb.push(' Done ');
var delta = this.stopTime - this.startTime;
sb.push(goog.debug.Trace_.longToPaddedString_(delta), ' ms ');
} else {
sb.push(' Comment ');
}
sb.push(indent, this);
if (this.totalVarAlloc > 0) {
sb.push('[VarAlloc ', this.totalVarAlloc, '] ');
}
return sb.join('');
};
/**
* @return {string} A string describing the tracer event.
* @override
*/
goog.debug.Trace_.Event_.prototype.toString = function() {
if (this.type == null) {
return this.comment;
} else {
return '[' + this.type + '] ' + this.comment;
}
};
/**
* Add the ability to explicitly set the start time. This is useful for example
* for measuring initial load time where you can set a variable as soon as the
* main page of the app is loaded and then later call this function when the
* Tracer code has been loaded.
* @param {number} startTime The start time to set.
*/
goog.debug.Trace_.prototype.setStartTime = function(startTime) {
this.startTime_ = startTime;
};
/**
* Initializes and resets the current trace
* @param {number} defaultThreshold The default threshold below which the
* tracer output will be supressed. Can be overridden on a per-Tracer basis.
*/
goog.debug.Trace_.prototype.initCurrentTrace = function(defaultThreshold) {
this.reset(defaultThreshold);
};
/**
* Clears the current trace
*/
goog.debug.Trace_.prototype.clearCurrentTrace = function() {
this.reset(0);
};
/**
* Resets the trace.
* @param {number} defaultThreshold The default threshold below which the
* tracer output will be supressed. Can be overridden on a per-Tracer basis.
*/
goog.debug.Trace_.prototype.reset = function(defaultThreshold) {
this.defaultThreshold_ = defaultThreshold;
for (var i = 0; i < this.events_.length; i++) {
var id = /** @type {Object} */ (this.eventPool_).id;
if (id) {
this.idPool_.releaseObject(id);
}
this.eventPool_.releaseObject(this.events_[i]);
}
this.events_.length = 0;
this.outstandingEvents_.clear();
this.startTime_ = goog.debug.Trace_.now();
this.tracerOverheadStart_ = 0;
this.tracerOverheadEnd_ = 0;
this.tracerOverheadComment_ = 0;
this.tracerCount_ = 0;
this.commentCount_ = 0;
var keys = this.stats_.getKeys();
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var stat = this.stats_.get(key);
stat.count = 0;
stat.time = 0;
stat.varAlloc = 0;
this.statPool_.releaseObject(/** @type {Object} */ (stat));
}
this.stats_.clear();
};
/**
* Starts a tracer
* @param {string} comment A comment used to identify the tracer. Does not
* need to be unique.
* @param {string=} opt_type Type used to identify the tracer. If a Trace is
* given a type (the first argument to the constructor) and multiple Traces
* are done on that type then a "TOTAL line will be produced showing the
* total number of traces and the sum of the time
* ("TOTAL Database 2 (37 ms)" in our example). These traces should be
* mutually exclusive or else the sum won't make sense (the time will
* be double counted if the second starts before the first ends).
* @return {number} The identifier for the tracer that should be passed to the
* the stopTracer method.
*/
goog.debug.Trace_.prototype.startTracer = function(comment, opt_type) {
var tracerStartTime = goog.debug.Trace_.now();
var varAlloc = this.getTotalVarAlloc();
var outstandingEventCount = this.outstandingEvents_.getCount();
if (this.events_.length + outstandingEventCount > this.MAX_TRACE_SIZE) {
goog.log.warning(this.logger_,
'Giant thread trace. Clearing to avoid memory leak.');
// This is the more likely case. This usually means that we
// either forgot to clear the trace or else we are performing a
// very large number of events
if (this.events_.length > this.MAX_TRACE_SIZE / 2) {
for (var i = 0; i < this.events_.length; i++) {
var event = this.events_[i];
if (event.id) {
this.idPool_.releaseObject(event.id);
}
this.eventPool_.releaseObject(event);
}
this.events_.length = 0;
}
// This is less likely and probably indicates that a lot of traces
// aren't being closed. We want to avoid unnecessarily clearing
// this though in case the events do eventually finish.
if (outstandingEventCount > this.MAX_TRACE_SIZE / 2) {
this.outstandingEvents_.clear();
}
}
goog.debug.Logger.logToProfilers('Start : ' + comment);
var event = /** @type {goog.debug.Trace_.Event_} */ (
this.eventPool_.getObject());
event.totalVarAlloc = varAlloc;
event.eventType = goog.debug.Trace_.EventType.START;
event.id = Number(this.idPool_.getObject());
event.comment = comment;
event.type = opt_type;
this.events_.push(event);
this.outstandingEvents_.set(String(event.id), event);
this.tracerCount_++;
var now = goog.debug.Trace_.now();
event.startTime = event.eventTime = now;
this.tracerOverheadStart_ += now - tracerStartTime;
return event.id;
};
/**
* Stops a tracer
* @param {number|undefined|null} id The id of the tracer that is ending.
* @param {number=} opt_silenceThreshold Threshold below which the tracer is
* silenced.
* @return {?number} The elapsed time for the tracer or null if the tracer
* identitifer was not recognized.
*/
goog.debug.Trace_.prototype.stopTracer = function(id, opt_silenceThreshold) {
// this used to call goog.isDef(opt_silenceThreshold) but that causes an
// object allocation in IE for some reason (doh!). The following code doesn't
// cause an allocation
var now = goog.debug.Trace_.now();
var silenceThreshold;
if (opt_silenceThreshold === 0) {
silenceThreshold = 0;
} else if (opt_silenceThreshold) {
silenceThreshold = opt_silenceThreshold;
} else {
silenceThreshold = this.defaultThreshold_;
}
var startEvent = this.outstandingEvents_.get(String(id));
if (startEvent == null) {
return null;
}
this.outstandingEvents_.remove(String(id));
var stopEvent;
var elapsed = now - startEvent.startTime;
if (elapsed < silenceThreshold) {
var count = this.events_.length;
for (var i = count - 1; i >= 0; i--) {
var nextEvent = this.events_[i];
if (nextEvent == startEvent) {
this.events_.splice(i, 1);
this.idPool_.releaseObject(startEvent.id);
this.eventPool_.releaseObject(/** @type {Object} */ (startEvent));
break;
}
}
} else {
stopEvent = /** @type {goog.debug.Trace_.Event_} */ (
this.eventPool_.getObject());
stopEvent.eventType = goog.debug.Trace_.EventType.STOP;
stopEvent.startTime = startEvent.startTime;
stopEvent.comment = startEvent.comment;
stopEvent.type = startEvent.type;
stopEvent.stopTime = stopEvent.eventTime = now;
this.events_.push(stopEvent);
}
var type = startEvent.type;
var stat = null;
if (type) {
stat = this.getStat_(type);
stat.count++;
stat.time += elapsed;
}
if (stopEvent) {
goog.debug.Logger.logToProfilers('Stop : ' + stopEvent.comment);
stopEvent.totalVarAlloc = this.getTotalVarAlloc();
if (stat) {
stat.varAlloc += (stopEvent.totalVarAlloc - startEvent.totalVarAlloc);
}
}
var tracerFinishTime = goog.debug.Trace_.now();
this.tracerOverheadEnd_ += tracerFinishTime - now;
return elapsed;
};
/**
* Sets the ActiveX object that can be used to get GC tracing in IE6.
* @param {Object} gcTracer GCTracer ActiveX object.
*/
goog.debug.Trace_.prototype.setGcTracer = function(gcTracer) {
this.gcTracer_ = gcTracer;
};
/**
* Returns the total number of allocations since the GC stats were reset. Only
* works in IE.
* @return {number} The number of allocaitons or -1 if not supported.
*/
goog.debug.Trace_.prototype.getTotalVarAlloc = function() {
var gcTracer = this.gcTracer_;
// isTracing is defined on the ActiveX object.
if (gcTracer && gcTracer['isTracing']()) {
return gcTracer['totalVarAlloc'];
}
return -1;
};
/**
* Adds a comment to the trace. Makes it possible to see when a specific event
* happened in relation to the traces.
* @param {string} comment A comment that is inserted into the trace.
* @param {?string=} opt_type Type used to identify the tracer. If a comment is
* given a type and multiple comments are done on that type then a "TOTAL
* line will be produced showing the total number of comments of that type.
* @param {?number=} opt_timeStamp The timestamp to insert the comment. If not
* specified, the current time wil be used.
*/
goog.debug.Trace_.prototype.addComment = function(comment, opt_type,
opt_timeStamp) {
var now = goog.debug.Trace_.now();
var timeStamp = opt_timeStamp ? opt_timeStamp : now;
var eventComment = /** @type {goog.debug.Trace_.Event_} */ (
this.eventPool_.getObject());
eventComment.eventType = goog.debug.Trace_.EventType.COMMENT;
eventComment.eventTime = timeStamp;
eventComment.type = opt_type;
eventComment.comment = comment;
eventComment.totalVarAlloc = this.getTotalVarAlloc();
this.commentCount_++;
if (opt_timeStamp) {
var numEvents = this.events_.length;
for (var i = 0; i < numEvents; i++) {
var event = this.events_[i];
var eventTime = event.eventTime;
if (eventTime > timeStamp) {
goog.array.insertAt(this.events_, eventComment, i);
break;
}
}
if (i == numEvents) {
this.events_.push(eventComment);
}
} else {
this.events_.push(eventComment);
}
var type = eventComment.type;
if (type) {
var stat = this.getStat_(type);
stat.count++;
}
this.tracerOverheadComment_ += goog.debug.Trace_.now() - now;
};
/**
* Gets a stat object for a particular type. The stat object is created if it
* hasn't yet been.
* @param {string} type The type of stat.
* @return {goog.debug.Trace_.Stat_} The stat object.
* @private
*/
goog.debug.Trace_.prototype.getStat_ = function(type) {
var stat = this.stats_.get(type);
if (!stat) {
stat = /** @type {goog.debug.Trace_.Event_} */ (
this.statPool_.getObject());
stat.type = type;
this.stats_.set(type, stat);
}
return /** @type {goog.debug.Trace_.Stat_} */(stat);
};
/**
* Returns a formatted string for the current trace
* @return {string} A formatted string that shows the timings of the current
* trace.
*/
goog.debug.Trace_.prototype.getFormattedTrace = function() {
return this.toString();
};
/**
* Returns a formatted string that describes the thread trace.
* @return {string} A formatted string.
* @override
*/
goog.debug.Trace_.prototype.toString = function() {
var sb = [];
var etime = -1;
var indent = [];
for (var i = 0; i < this.events_.length; i++) {
var e = this.events_[i];
if (e.eventType == goog.debug.Trace_.EventType.STOP) {
indent.pop();
}
sb.push(' ', e.toTraceString(this.startTime_, etime, indent.join('')));
etime = e.eventTime;
sb.push('\n');
if (e.eventType == goog.debug.Trace_.EventType.START) {
indent.push('| ');
}
}
if (this.outstandingEvents_.getCount() != 0) {
var now = goog.debug.Trace_.now();
sb.push(' Unstopped timers:\n');
goog.iter.forEach(this.outstandingEvents_, function(startEvent) {
sb.push(' ', startEvent, ' (', now - startEvent.startTime,
' ms, started at ',
goog.debug.Trace_.formatTime_(startEvent.startTime),
')\n');
});
}
var statKeys = this.stats_.getKeys();
for (var i = 0; i < statKeys.length; i++) {
var stat = this.stats_.get(statKeys[i]);
if (stat.count > 1) {
sb.push(' TOTAL ', stat, '\n');
}
}
sb.push('Total tracers created ', this.tracerCount_, '\n',
'Total comments created ', this.commentCount_, '\n',
'Overhead start: ', this.tracerOverheadStart_, ' ms\n',
'Overhead end: ', this.tracerOverheadEnd_, ' ms\n',
'Overhead comment: ', this.tracerOverheadComment_, ' ms\n');
return sb.join('');
};
/**
* Converts 'v' to a string and pads it with up to 3 spaces for
* improved alignment. TODO there must be a better way
* @param {number} v A number.
* @return {string} A padded string.
* @private
*/
goog.debug.Trace_.longToPaddedString_ = function(v) {
v = Math.round(v);
// todo (pupius) - there should be a generic string in goog.string for this
var space = '';
if (v < 1000) space = ' ';
if (v < 100) space = ' ';
if (v < 10) space = ' ';
return space + v;
};
/**
* Return the sec.ms part of time (if time = "20:06:11.566", "11.566
* @param {number} time The time in MS.
* @return {string} A formatted string as sec.ms'.
* @private
*/
goog.debug.Trace_.formatTime_ = function(time) {
time = Math.round(time);
var sec = (time / 1000) % 60;
var ms = time % 1000;
// TODO their must be a nicer way to get zero padded integers
return String(100 + sec).substring(1, 3) + '.' +
String(1000 + ms).substring(1, 4);
};
/**
* Returns the current time. Done through a wrapper function so it can be
* overridden by application code. Gmail has an ActiveX extension that provides
* higher precision timing info.
* @return {number} The current time in milliseconds.
*/
goog.debug.Trace_.now = function() {
return goog.now();
};
/**
* Singleton trace object
* @type {goog.debug.Trace_}
*/
goog.debug.Trace = new goog.debug.Trace_();

View File

@@ -0,0 +1,18 @@
// 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.
/** @nocompile */
['Big Table', 'Googlebot', 'Instant Indexing', 'Mustang', 'Page Rank',
'Proto Buffer']

View File

@@ -0,0 +1,33 @@
// 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.
/** @nocompile */
[
['apple',
{name: 'Fuji', url: 'http://www.google.com/images?q=fuji+apple'},
{name: 'Gala', url: 'http://www.google.com/images?q=gala+apple'},
{name: 'Golden Delicious',
url: 'http://www.google.com/images?q=golden delicious+apple'}
],
['citrus',
{name: 'Lemon', url: 'http://www.google.com/images?q=lemon+fruit'},
{name: 'Orange', url: 'http://www.google.com/images?q=orange+fruit'}
],
['berry',
{name: 'Strawberry', url: 'http://www.google.com/images?q=strawberry+fruit'},
{name: 'Blueberry', url: 'http://www.google.com/images?q=blueberry+fruit'},
{name: 'Blackberry', url: 'http://www.google.com/images?q=blackberry+fruit'}
]
]

View File

@@ -0,0 +1,21 @@
// Copyright 2009 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.
//
// This file has been auto-generated by GenJsDeps, please do not edit.
goog.addDependency('demos/editor/equationeditor.js', ['goog.demos.editor.EquationEditor'], ['goog.ui.equation.EquationEditorDialog']);
goog.addDependency('demos/editor/helloworld.js', ['goog.demos.editor.HelloWorld'], ['goog.dom', 'goog.dom.TagName', 'goog.editor.Plugin']);
goog.addDependency('demos/editor/helloworlddialog.js', ['goog.demos.editor.HelloWorldDialog', 'goog.demos.editor.HelloWorldDialog.OkEvent'], ['goog.dom.TagName', 'goog.events.Event', 'goog.string', 'goog.ui.editor.AbstractDialog', 'goog.ui.editor.AbstractDialog.Builder', 'goog.ui.editor.AbstractDialog.EventType']);
goog.addDependency('demos/editor/helloworlddialogplugin.js', ['goog.demos.editor.HelloWorldDialogPlugin', 'goog.demos.editor.HelloWorldDialogPlugin.Command'], ['goog.demos.editor.HelloWorldDialog', 'goog.dom.TagName', 'goog.editor.plugins.AbstractDialogPlugin', 'goog.editor.range', 'goog.functions', 'goog.ui.editor.AbstractDialog.EventType']);

View File

@@ -0,0 +1,40 @@
// Copyright 2008 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.
/**
* @see equationeditor.html
*/
goog.provide('goog.demos.editor.EquationEditor');
goog.require('goog.ui.equation.EquationEditorDialog');
/**
* @constructor
*/
goog.demos.editor.EquationEditor = function() {
};
/**
* Creates a new editor and opens the dialog.
* @param {string} initialEquation The initial equation value to use.
*/
goog.demos.editor.EquationEditor.prototype.openEditor = function(
initialEquation) {
var editorDialog = new goog.ui.equation.EquationEditorDialog(initialEquation);
editorDialog.setVisible(true);
};

View File

@@ -0,0 +1,81 @@
// Copyright 2008 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 simple plugin that inserts 'Hello World!' on command. This
* plugin is intended to be an example of a very simple plugin for plugin
* developers.
*
* @author gak@google.com (Gregory Kick)
* @see helloworld.html
*/
goog.provide('goog.demos.editor.HelloWorld');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.editor.Plugin');
/**
* Plugin to insert 'Hello World!' into an editable field.
* @constructor
* @extends {goog.editor.Plugin}
*/
goog.demos.editor.HelloWorld = function() {
goog.editor.Plugin.call(this);
};
goog.inherits(goog.demos.editor.HelloWorld, goog.editor.Plugin);
/** @override */
goog.demos.editor.HelloWorld.prototype.getTrogClassId = function() {
return 'HelloWorld';
};
/**
* Commands implemented by this plugin.
* @enum {string}
*/
goog.demos.editor.HelloWorld.COMMAND = {
HELLO_WORLD: '+helloWorld'
};
/** @override */
goog.demos.editor.HelloWorld.prototype.isSupportedCommand = function(
command) {
return command == goog.demos.editor.HelloWorld.COMMAND.HELLO_WORLD;
};
/**
* Executes a command. Does not fire any BEFORECHANGE, CHANGE, or
* SELECTIONCHANGE events (these are handled by the super class implementation
* of {@code execCommand}.
* @param {string} command Command to execute.
* @override
* @protected
*/
goog.demos.editor.HelloWorld.prototype.execCommandInternal = function(
command) {
var domHelper = this.getFieldObject().getEditableDomHelper();
var range = this.getFieldObject().getRange();
range.removeContents();
var newNode =
domHelper.createDom(goog.dom.TagName.SPAN, null, 'Hello World!');
range.insertNode(newNode, false);
};

View File

@@ -0,0 +1,171 @@
// Copyright 2008 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 An example of how to write a dialog to be opened by a plugin.
*
*/
goog.provide('goog.demos.editor.HelloWorldDialog');
goog.provide('goog.demos.editor.HelloWorldDialog.OkEvent');
goog.require('goog.dom.TagName');
goog.require('goog.events.Event');
goog.require('goog.string');
goog.require('goog.ui.editor.AbstractDialog');
// *** Public interface ***************************************************** //
/**
* Creates a dialog to let the user enter a customized hello world message.
* @param {goog.dom.DomHelper} domHelper DomHelper to be used to create the
* dialog's dom structure.
* @constructor
* @extends {goog.ui.editor.AbstractDialog}
*/
goog.demos.editor.HelloWorldDialog = function(domHelper) {
goog.ui.editor.AbstractDialog.call(this, domHelper);
};
goog.inherits(goog.demos.editor.HelloWorldDialog,
goog.ui.editor.AbstractDialog);
// *** Event **************************************************************** //
/**
* OK event object for the hello world dialog.
* @param {string} message Customized hello world message chosen by the user.
* @constructor
* @extends {goog.events.Event}
*/
goog.demos.editor.HelloWorldDialog.OkEvent = function(message) {
this.message = message;
};
goog.inherits(goog.demos.editor.HelloWorldDialog.OkEvent, goog.events.Event);
/**
* Event type.
* @type {goog.ui.editor.AbstractDialog.EventType}
* @override
*/
goog.demos.editor.HelloWorldDialog.OkEvent.prototype.type =
goog.ui.editor.AbstractDialog.EventType.OK;
/**
* Customized hello world message chosen by the user.
* @type {string}
*/
goog.demos.editor.HelloWorldDialog.OkEvent.prototype.message;
// *** Protected interface ************************************************** //
/** @override */
goog.demos.editor.HelloWorldDialog.prototype.createDialogControl = function() {
var builder = new goog.ui.editor.AbstractDialog.Builder(this);
/** @desc Title of the hello world dialog. */
var MSG_HELLO_WORLD_DIALOG_TITLE = goog.getMsg('Add a Hello World message');
builder.setTitle(MSG_HELLO_WORLD_DIALOG_TITLE).
setContent(this.createContent_());
return builder.build();
};
/**
* Creates and returns the event object to be used when dispatching the OK
* event to listeners, or returns null to prevent the dialog from closing.
* @param {goog.events.Event} e The event object dispatched by the wrapped
* dialog.
* @return {goog.demos.editor.HelloWorldDialog.OkEvent} The event object to be
* used when dispatching the OK event to listeners.
* @protected
* @override
*/
goog.demos.editor.HelloWorldDialog.prototype.createOkEvent = function(e) {
var message = this.getMessage_();
if (message &&
goog.demos.editor.HelloWorldDialog.isValidHelloWorld_(message)) {
return new goog.demos.editor.HelloWorldDialog.OkEvent(message);
} else {
/** @desc Error message telling the user why their message was rejected. */
var MSG_HELLO_WORLD_DIALOG_ERROR =
goog.getMsg('Your message must contain the words "hello" and "world".');
this.dom.getWindow().alert(MSG_HELLO_WORLD_DIALOG_ERROR);
return null; // Prevents the dialog from closing.
}
};
// *** Private implementation *********************************************** //
/**
* Input element where the user will type their hello world message.
* @type {Element}
* @private
*/
goog.demos.editor.HelloWorldDialog.prototype.input_;
/**
* Creates the DOM structure that makes up the dialog's content area.
* @return {Element} The DOM structure that makes up the dialog's content area.
* @private
*/
goog.demos.editor.HelloWorldDialog.prototype.createContent_ = function() {
/** @desc Sample hello world message to prepopulate the dialog with. */
var MSG_HELLO_WORLD_DIALOG_SAMPLE = goog.getMsg('Hello, world!');
this.input_ = this.dom.createDom(goog.dom.TagName.INPUT,
{size: 25, value: MSG_HELLO_WORLD_DIALOG_SAMPLE});
/** @desc Prompt telling the user to enter a hello world message. */
var MSG_HELLO_WORLD_DIALOG_PROMPT =
goog.getMsg('Enter your Hello World message');
return this.dom.createDom(goog.dom.TagName.DIV,
null,
[MSG_HELLO_WORLD_DIALOG_PROMPT, this.input_]);
};
/**
* Returns the hello world message currently typed into the dialog's input.
* @return {?string} The hello world message currently typed into the dialog's
* input, or null if called before the input is created.
* @private
*/
goog.demos.editor.HelloWorldDialog.prototype.getMessage_ = function() {
return this.input_ && this.input_.value;
};
/**
* Returns whether or not the given message contains the strings "hello" and
* "world". Case-insensitive and order doesn't matter.
* @param {string} message The message to be checked.
* @return {boolean} Whether or not the given message contains the strings
* "hello" and "world".
* @private
*/
goog.demos.editor.HelloWorldDialog.isValidHelloWorld_ = function(message) {
message = message.toLowerCase();
return goog.string.contains(message, 'hello') &&
goog.string.contains(message, 'world');
};

View File

@@ -0,0 +1,116 @@
// Copyright 2008 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 An example of how to write a dialog plugin.
*
*/
goog.provide('goog.demos.editor.HelloWorldDialogPlugin');
goog.provide('goog.demos.editor.HelloWorldDialogPlugin.Command');
goog.require('goog.demos.editor.HelloWorldDialog');
goog.require('goog.dom.TagName');
goog.require('goog.editor.plugins.AbstractDialogPlugin');
goog.require('goog.editor.range');
goog.require('goog.functions');
goog.require('goog.ui.editor.AbstractDialog');
// *** Public interface ***************************************************** //
/**
* A plugin that opens the hello world dialog.
* @constructor
* @extends {goog.editor.plugins.AbstractDialogPlugin}
*/
goog.demos.editor.HelloWorldDialogPlugin = function() {
goog.editor.plugins.AbstractDialogPlugin.call(this,
goog.demos.editor.HelloWorldDialogPlugin.Command.HELLO_WORLD_DIALOG);
};
goog.inherits(goog.demos.editor.HelloWorldDialogPlugin,
goog.editor.plugins.AbstractDialogPlugin);
/**
* Commands implemented by this plugin.
* @enum {string}
*/
goog.demos.editor.HelloWorldDialogPlugin.Command = {
HELLO_WORLD_DIALOG: 'helloWorldDialog'
};
/** @override */
goog.demos.editor.HelloWorldDialogPlugin.prototype.getTrogClassId =
goog.functions.constant('HelloWorldDialog');
// *** Protected interface ************************************************** //
/**
* Creates a new instance of the dialog and registers for the relevant events.
* @param {goog.dom.DomHelper} dialogDomHelper The dom helper to be used to
* create the dialog.
* @return {goog.demos.editor.HelloWorldDialog} The dialog.
* @override
* @protected
*/
goog.demos.editor.HelloWorldDialogPlugin.prototype.createDialog = function(
dialogDomHelper) {
var dialog = new goog.demos.editor.HelloWorldDialog(dialogDomHelper);
dialog.addEventListener(goog.ui.editor.AbstractDialog.EventType.OK,
this.handleOk_,
false,
this);
return dialog;
};
// *** Private implementation *********************************************** //
/**
* Handles the OK event from the dialog by inserting the hello world message
* into the field.
* @param {goog.demos.editor.HelloWorldDialog.OkEvent} e OK event object.
* @private
*/
goog.demos.editor.HelloWorldDialogPlugin.prototype.handleOk_ = function(e) {
// First restore the selection so we can manipulate the field's content
// according to what was selected.
this.restoreOriginalSelection();
// Notify listeners that the field's contents are about to change.
this.getFieldObject().dispatchBeforeChange();
// Now we can clear out what was previously selected (if anything).
var range = this.getFieldObject().getRange();
range.removeContents();
// And replace it with a span containing our hello world message.
var createdNode = this.getFieldDomHelper().createDom(goog.dom.TagName.SPAN,
null,
e.message);
createdNode = range.insertNode(createdNode, false);
// Place the cursor at the end of the new text node (false == to the right).
goog.editor.range.placeCursorNextTo(createdNode, false);
// Notify listeners that the field's selection has changed.
this.getFieldObject().dispatchSelectionChangeEvent();
// Notify listeners that the field's contents have changed.
this.getFieldObject().dispatchChange();
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,197 @@
// Copyright 2007 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 simple, sample component.
*
*/
goog.provide('goog.demos.SampleComponent');
goog.require('goog.dom');
goog.require('goog.dom.classes');
goog.require('goog.events.EventType');
goog.require('goog.events.KeyCodes');
goog.require('goog.events.KeyHandler');
goog.require('goog.ui.Component');
/**
* A simple box that changes colour when clicked. This class demonstrates the
* goog.ui.Component API, and is keyboard accessible, as per
* http://wiki/Main/ClosureKeyboardAccessible
*
* @param {string=} opt_label A label to display. Defaults to "Click Me" if none
* provided.
* @param {goog.dom.DomHelper=} opt_domHelper DOM helper to use.
*
* @extends {goog.ui.Component}
* @constructor
*/
goog.demos.SampleComponent = function(opt_label, opt_domHelper) {
goog.base(this, opt_domHelper);
/**
* The label to display.
* @type {string}
* @private
*/
this.initialLabel_ = opt_label || 'Click Me';
/**
* The current color.
* @type {string}
* @private
*/
this.color_ = 'red';
/**
* Keyboard handler for this object. This object is created once the
* component's DOM element is known.
*
* @type {goog.events.KeyHandler?}
* @private
*/
this.kh_ = null;
};
goog.inherits(goog.demos.SampleComponent, goog.ui.Component);
/**
* Changes the color of the element.
* @private
*/
goog.demos.SampleComponent.prototype.changeColor_ = function() {
if (this.color_ == 'red') {
this.color_ = 'green';
} else if (this.color_ == 'green') {
this.color_ = 'blue';
} else {
this.color_ = 'red';
}
this.getElement().style.backgroundColor = this.color_;
};
/**
* Creates an initial DOM representation for the component.
* @override
*/
goog.demos.SampleComponent.prototype.createDom = function() {
this.decorateInternal(this.dom_.createElement('div'));
};
/**
* Decorates an existing HTML DIV element as a SampleComponent.
*
* @param {Element} element The DIV element to decorate. The element's
* text, if any will be used as the component's label.
* @override
*/
goog.demos.SampleComponent.prototype.decorateInternal = function(element) {
goog.base(this, 'decorateInternal', element);
if (!this.getLabelText()) {
this.setLabelText(this.initialLabel_);
}
var elem = this.getElement();
goog.dom.classes.add(elem, goog.getCssName('goog-sample-component'));
elem.style.backgroundColor = this.color_;
elem.tabIndex = 0;
this.kh_ = new goog.events.KeyHandler(elem);
this.getHandler().listen(this.kh_, goog.events.KeyHandler.EventType.KEY,
this.onKey_);
};
/** @override */
goog.demos.SampleComponent.prototype.disposeInternal = function() {
goog.base(this, 'disposeInternal');
if (this.kh_) {
this.kh_.dispose();
}
};
/**
* Called when component's element is known to be in the document.
* @override
*/
goog.demos.SampleComponent.prototype.enterDocument = function() {
goog.base(this, 'enterDocument');
this.getHandler().listen(this.getElement(), goog.events.EventType.CLICK,
this.onDivClicked_);
};
/**
* Called when component's element is known to have been removed from the
* document.
* @override
*/
goog.demos.SampleComponent.prototype.exitDocument = function() {
goog.base(this, 'exitDocument');
};
/**
* Gets the current label text.
*
* @return {string} The current text set into the label, or empty string if
* none set.
*/
goog.demos.SampleComponent.prototype.getLabelText = function() {
if (!this.getElement()) {
return '';
}
return goog.dom.getTextContent(this.getElement());
};
/**
* Handles DIV element clicks, causing the DIV's colour to change.
* @param {goog.events.Event} event The click event.
* @private
*/
goog.demos.SampleComponent.prototype.onDivClicked_ = function(event) {
this.changeColor_();
};
/**
* Fired when user presses a key while the DIV has focus. If the user presses
* space or enter, the color will be changed.
* @param {goog.events.Event} event The key event.
* @private
*/
goog.demos.SampleComponent.prototype.onKey_ = function(event) {
var keyCodes = goog.events.KeyCodes;
if (event.keyCode == keyCodes.SPACE || event.keyCode == keyCodes.ENTER) {
this.changeColor_();
}
};
/**
* Sets the current label text. Has no effect if component is not rendered.
*
* @param {string} text The text to set as the label.
*/
goog.demos.SampleComponent.prototype.setLabelText = function(text) {
if (this.getElement()) {
goog.dom.setTextContent(this.getElement(), text);
}
};

View File

@@ -0,0 +1,260 @@
// 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.
var testData =
['Countries', [['A', [['Afghanistan'],
['Albania'],
['Algeria'],
['American Samoa'],
['Andorra'],
['Angola'],
['Anguilla'],
['Antarctica'],
['Antigua and Barbuda'],
['Argentina'],
['Armenia'],
['Aruba'],
['Australia'],
['Austria'],
['Azerbaijan']]],
['B', [['Bahamas'],
['Bahrain'],
['Bangladesh'],
['Barbados'],
['Belarus'],
['Belgium'],
['Belize'],
['Benin'],
['Bermuda'],
['Bhutan'],
['Bolivia'],
['Bosnia and Herzegovina'],
['Botswana'],
['Bouvet Island'],
['Brazil'],
['British Indian Ocean Territory'],
['Brunei Darussalam'],
['Bulgaria'],
['Burkina Faso'],
['Burundi']]],
['C', [['Cambodia'],
['Cameroon'],
['Canada'],
['Cape Verde'],
['Cayman Islands'],
['Central African Republic'],
['Chad'],
['Chile'],
['China'],
['Christmas Island'],
['Cocos (Keeling) Islands'],
['Colombia'],
['Comoros'],
['Congo'],
['Congo, the Democratic Republic of the'],
['Cook Islands'],
['Costa Rica'],
['Croatia'],
['Cuba'],
['Cyprus'],
['Czech Republic'],
['C\u00f4te d\u2019Ivoire']]],
['D', [['Denmark'],
['Djibouti'],
['Dominica'],
['Dominican Republic']]],
['E', [['Ecuador'],
['Egypt'],
['El Salvador'],
['Equatorial Guinea'],
['Eritrea'],
['Estonia'],
['Ethiopia']]],
['F', [['Falkland Islands (Malvinas)'],
['Faroe Islands'],
['Fiji'],
['Finland'],
['France'],
['French Guiana'],
['French Polynesia'],
['French Southern Territories']]],
['G', [['Gabon'],
['Gambia'],
['Georgia'],
['Germany'],
['Ghana'],
['Gibraltar'],
['Greece'],
['Greenland'],
['Grenada'],
['Guadeloupe'],
['Guam'],
['Guatemala'],
['Guernsey'],
['Guinea'],
['Guinea-Bissau'],
['Guyana']]],
['H', [['Haiti'],
['Heard Island and McDonald Islands'],
['Holy See (Vatican City State)'],
['Honduras'],
['Hong Kong'],
['Hungary']]],
['I', [['Iceland'],
['India'],
['Indonesia'],
['Iran, Islamic Republic of'],
['Iraq'],
['Ireland'],
['Isle of Man'],
['Israel'],
['Italy']]],
['J', [['Jamaica'],
['Japan'],
['Jersey'],
['Jordan']]],
['K', [['Kazakhstan'],
['Kenya'],
['Kiribati'],
['Korea, Democratic People\u2019s Republic of'],
['Korea, Republic of'],
['Kuwait'],
['Kyrgyzstan']]],
['L', [['Lao People\u2019s Democratic Republic'],
['Latvia'],
['Lebanon'],
['Lesotho'],
['Liberia'],
['Libyan Arab Jamahiriya'],
['Liechtenstein'],
['Lithuania'],
['Luxembourg']]],
['M', [['Macao'],
['Macedonia, the former Yugoslav Republic of'],
['Madagascar'],
['Malawi'],
['Malaysia'],
['Maldives'],
['Mali'],
['Malta'],
['Marshall Islands'],
['Martinique'],
['Mauritania'],
['Mauritius'],
['Mayotte'],
['Mexico'],
['Micronesia, Federated States of'],
['Moldova, Republic of'],
['Monaco'],
['Mongolia'],
['Montenegro'],
['Montserrat'],
['Morocco'],
['Mozambique'],
['Myanmar']]],
['N', [['Namibia'],
['Nauru'],
['Nepal'],
['Netherlands'],
['Netherlands Antilles'],
['New Caledonia'],
['New Zealand'],
['Nicaragua'],
['Niger'],
['Nigeria'],
['Niue'],
['Norfolk Island'],
['Northern Mariana Islands'],
['Norway']]],
['O', [['Oman']]],
['P', [['Pakistan'],
['Palau'],
['Palestinian Territory, Occupied'],
['Panama'],
['Papua New Guinea'],
['Paraguay'],
['Peru'],
['Philippines'],
['Pitcairn'],
['Poland'],
['Portugal'],
['Puerto Rico']]],
['Q', [['Qatar']]],
['R', [['Romania'],
['Russian Federation'],
['Rwanda'],
['R\u00e9union']]],
['S', [['Saint Barth\u00e9lemy'],
['Saint Helena'],
['Saint Kitts and Nevis'],
['Saint Lucia'],
['Saint Martin (French part)'],
['Saint Pierre and Miquelon'],
['Saint Vincent and the Grenadines'],
['Samoa'],
['San Marino'],
['Sao Tome and Principe'],
['Saudi Arabia'],
['Senegal'],
['Serbia'],
['Seychelles'],
['Sierra Leone'],
['Singapore'],
['Slovakia'],
['Slovenia'],
['Solomon Islands'],
['Somalia'],
['South Africa'],
['South Georgia and the South Sandwich Islands'],
['Spain'],
['Sri Lanka'],
['Sudan'],
['Suriname'],
['Svalbard and Jan Mayen'],
['Swaziland'],
['Sweden'],
['Switzerland'],
['Syrian Arab Republic']]],
['T', [['Taiwan, Province of China'],
['Tajikistan'],
['Tanzania, United Republic of'],
['Thailand'],
['Timor-Leste'],
['Togo'],
['Tokelau'],
['Tonga'],
['Trinidad and Tobago'],
['Tunisia'],
['Turkey'],
['Turkmenistan'],
['Turks and Caicos Islands'],
['Tuvalu']]],
['U', [['Uganda'],
['Ukraine'],
['United Arab Emirates'],
['United Kingdom'],
['United States'],
['United States Minor Outlying Islands'],
['Uruguay'],
['Uzbekistan']]],
['V', [['Vanuatu'],
['Venezuela'],
['Viet Nam'],
['Virgin Islands, British'],
['Virgin Islands, U.S.']]],
['W', [['Wallis and Futuna'],
['Western Sahara']]],
['Y', [['Yemen']]],
['Z', [['Zambia'],
['Zimbabwe']]],
['\u00c5', [['\u00c5land Islands']]]]]

View File

@@ -0,0 +1,307 @@
// Copyright 2007 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 Contains application code for the XPC demo.
* This script is used in both the container page and the iframe.
*
*/
goog.require('goog.Uri');
goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.json');
goog.require('goog.log');
goog.require('goog.net.xpc.CfgFields');
goog.require('goog.net.xpc.CrossPageChannel');
/**
* Namespace for the demo. We don't use goog.provide here because it's not a
* real module (cannot be required).
*/
var xpcdemo = {};
/**
* Global function to kick off initialization in the containing document.
*/
goog.global.initOuter = function() {
goog.events.listen(window, 'load', function() { xpcdemo.initOuter(); });
};
/**
* Global function to kick off initialization in the iframe.
*/
goog.global.initInner = function() {
goog.events.listen(window, 'load', function() { xpcdemo.initInner(); });
};
/**
* Initializes XPC in the containing page.
*/
xpcdemo.initOuter = function() {
// Build the configuration object.
var cfg = {};
var ownUri = new goog.Uri(window.location.href);
var relayUri = ownUri.resolve(new goog.Uri('relay.html'));
var pollUri = ownUri.resolve(new goog.Uri('blank.html'));
// Determine the peer domain. Uses the value of the URI-parameter
// 'peerdomain'. If that parameter is not present, it falls back to
// the own domain so that the demo will work out of the box (but
// communication will of course not cross domain-boundaries). For
// real cross-domain communication, the easiest way is to point two
// different host-names to the same webserver and then hit the
// following URI:
// http://host1.com/path/to/closure/demos/xpc/index.html?peerdomain=host2.com
var peerDomain = ownUri.getParameterValue('peerdomain') || ownUri.getDomain();
cfg[goog.net.xpc.CfgFields.LOCAL_RELAY_URI] = relayUri.toString();
cfg[goog.net.xpc.CfgFields.PEER_RELAY_URI] =
relayUri.setDomain(peerDomain).toString();
cfg[goog.net.xpc.CfgFields.LOCAL_POLL_URI] = pollUri.toString();
cfg[goog.net.xpc.CfgFields.PEER_POLL_URI] =
pollUri.setDomain(peerDomain).toString();
// Force transport to be used if tp-parameter is set.
var tp = ownUri.getParameterValue('tp');
if (tp) {
cfg[goog.net.xpc.CfgFields.TRANSPORT] = parseInt(tp, 10);
}
// Construct the URI of the peer page.
var peerUri = ownUri.resolve(
new goog.Uri('inner.html')).setDomain(peerDomain);
// Passthrough of verbose and compiled flags.
if (goog.isDef(ownUri.getParameterValue('verbose'))) {
peerUri.setParameterValue('verbose', '');
}
if (goog.isDef(ownUri.getParameterValue('compiled'))) {
peerUri.setParameterValue('compiled', '');
}
cfg[goog.net.xpc.CfgFields.PEER_URI] = peerUri;
// Instantiate the channel.
xpcdemo.channel = new goog.net.xpc.CrossPageChannel(cfg);
// Create the peer iframe.
xpcdemo.peerIframe = xpcdemo.channel.createPeerIframe(
goog.dom.getElement('iframeContainer'));
xpcdemo.initCommon_();
goog.dom.getElement('inactive').style.display = 'none';
goog.dom.getElement('active').style.display = '';
};
/**
* Initialization in the iframe.
*/
xpcdemo.initInner = function() {
// Get the channel configuration passed by the containing document.
var cfg = goog.json.parse(
(new goog.Uri(window.location.href)).getParameterValue('xpc'));
xpcdemo.channel = new goog.net.xpc.CrossPageChannel(cfg);
xpcdemo.initCommon_();
};
/**
* Initializes the demo.
* Registers service-handlers and connects the channel.
* @private
*/
xpcdemo.initCommon_ = function() {
var xpcLogger = goog.log.getLogger('goog.net.xpc');
goog.log.addHandler(xpcLogger, function(logRecord) {
xpcdemo.log('[XPC] ' + logRecord.getMessage());
});
xpcLogger.setLevel(window.location.href.match(/verbose/) ?
goog.log.Level.ALL : goog.log.Level.INFO);
// Register services.
xpcdemo.channel.registerService('log', xpcdemo.log);
xpcdemo.channel.registerService('ping', xpcdemo.pingHandler_);
xpcdemo.channel.registerService('events', xpcdemo.eventsMsgHandler_);
// Connect the channel.
xpcdemo.channel.connect(function() {
xpcdemo.channel.send('log', 'Hi from ' + window.location.host);
goog.events.listen(goog.dom.getElement('clickfwd'),
'click', xpcdemo.mouseEventHandler_);
});
};
/**
* Kills the peer iframe and the disposes the channel.
*/
xpcdemo.teardown = function() {
goog.events.unlisten(goog.dom.getElement('clickfwd'),
goog.events.EventType.CLICK, xpcdemo.mouseEventHandler_);
xpcdemo.channel.dispose();
delete xpcdemo.channel;
goog.dom.removeNode(xpcdemo.peerIframe);
xpcdemo.peerIframe = null;
goog.dom.getElement('inactive').style.display = '';
goog.dom.getElement('active').style.display = 'none';
};
/**
* Logging function. Inserts log-message into element with it id 'console'.
* @param {string} msgString The log-message.
*/
xpcdemo.log = function(msgString) {
xpcdemo.consoleElm || (xpcdemo.consoleElm = goog.dom.getElement('console'));
var msgElm = goog.dom.createDom('div');
msgElm.innerHTML = msgString;
xpcdemo.consoleElm.insertBefore(msgElm, xpcdemo.consoleElm.firstChild);
};
/**
* Sends a ping request to the peer.
*/
xpcdemo.ping = function() {
// send current time
xpcdemo.channel.send('ping', goog.now() + '');
};
/**
* The handler function for incoming pings (messages sent to the service
* called 'ping');
* @param {string} payload The message payload.
* @private
*/
xpcdemo.pingHandler_ = function(payload) {
// is the incoming message a response to a ping we sent?
if (payload.charAt(0) == '#') {
// calculate roundtrip time and log
var dt = goog.now() - parseInt(payload.substring(1), 10);
xpcdemo.log('roundtrip: ' + dt + 'ms');
} else {
// incoming message is a ping initiated from peer
// -> prepend with '#' and send back
xpcdemo.channel.send('ping', '#' + payload);
xpcdemo.log('ping reply sent');
}
};
/**
* Counter for mousemove events.
* @type {number}
* @private
*/
xpcdemo.mmCount_ = 0;
/**
* Holds timestamp when the last mousemove rate has been logged.
* @type {number}
* @private
*/
xpcdemo.mmLastRateOutput_ = 0;
/**
* Start mousemove event forwarding. Registers a listener on the document which
* sends them over the channel.
*/
xpcdemo.startMousemoveForwarding = function() {
goog.events.listen(document, goog.events.EventType.MOUSEMOVE,
xpcdemo.mouseEventHandler_);
xpcdemo.mmLastRateOutput_ = goog.now();
};
/**
* Stop mousemove event forwarding.
*/
xpcdemo.stopMousemoveForwarding = function() {
goog.events.unlisten(document, goog.events.EventType.MOUSEMOVE,
xpcdemo.mouseEventHandler_);
};
/**
* Function to be used as handler for mouse-events.
* @param {goog.events.BrowserEvent} e The mouse event.
* @private
*/
xpcdemo.mouseEventHandler_ = function(e) {
xpcdemo.channel.send('events',
[e.type, e.clientX, e.clientY, goog.now()].join(','));
};
/**
* Handler for the 'events' service.
* @param {string} payload The string returned from the xpcdemo.
* @private
*/
xpcdemo.eventsMsgHandler_ = function(payload) {
var now = goog.now();
var args = payload.split(',');
var type = args[0];
var pageX = args[1];
var pageY = args[2];
var time = parseInt(args[3], 10);
var msg = type + ': (' + pageX + ',' + pageY + '), latency: ' + (now - time);
xpcdemo.log(msg);
if (type == goog.events.EventType.MOUSEMOVE) {
xpcdemo.mmCount_++;
var dt = now - xpcdemo.mmLastRateOutput_;
if (dt > 1000) {
msg = 'RATE (mousemove/s): ' + (1000 * xpcdemo.mmCount_ / dt);
xpcdemo.log(msg);
xpcdemo.mmLastRateOutput_ = now;
xpcdemo.mmCount_ = 0;
}
}
};
/**
* Send multiple messages.
* @param {number} n The number of messages to send.
*/
xpcdemo.sendN = function(n) {
xpcdemo.count_ || (xpcdemo.count_ = 1);
for (var i = 0; i < n; i++) {
xpcdemo.channel.send('log', '' + xpcdemo.count_++);
}
};

View File

@@ -0,0 +1,294 @@
// Copyright 2005 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 Implements the disposable interface. The dispose method is used
* to clean up references and resources.
* @author arv@google.com (Erik Arvidsson)
*/
goog.provide('goog.Disposable');
goog.provide('goog.dispose');
goog.require('goog.disposable.IDisposable');
/**
* Class that provides the basic implementation for disposable objects. If your
* class holds one or more references to COM objects, DOM nodes, or other
* disposable objects, it should extend this class or implement the disposable
* interface (defined in goog.disposable.IDisposable).
* @constructor
* @implements {goog.disposable.IDisposable}
*/
goog.Disposable = function() {
if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) {
if (goog.Disposable.INCLUDE_STACK_ON_CREATION) {
this.creationStack = new Error().stack;
}
goog.Disposable.instances_[goog.getUid(this)] = this;
}
};
/**
* @enum {number} Different monitoring modes for Disposable.
*/
goog.Disposable.MonitoringMode = {
/**
* No monitoring.
*/
OFF: 0,
/**
* Creating and disposing the goog.Disposable instances is monitored. All
* disposable objects need to call the {@code goog.Disposable} base
* constructor. The PERMANENT mode must bet switched on before creating any
* goog.Disposable instances.
*/
PERMANENT: 1,
/**
* INTERACTIVE mode can be switched on and off on the fly without producing
* errors. It also doesn't warn if the disposable objects don't call the
* {@code goog.Disposable} base constructor.
*/
INTERACTIVE: 2
};
/**
* @define {number} The monitoring mode of the goog.Disposable
* instances. Default is OFF. Switching on the monitoring is only
* recommended for debugging because it has a significant impact on
* performance and memory usage. If switched off, the monitoring code
* compiles down to 0 bytes.
*/
goog.define('goog.Disposable.MONITORING_MODE', 0);
/**
* @define {boolean} Whether to attach creation stack to each created disposable
* instance; This is only relevant for when MonitoringMode != OFF.
*/
goog.define('goog.Disposable.INCLUDE_STACK_ON_CREATION', true);
/**
* Maps the unique ID of every undisposed {@code goog.Disposable} object to
* the object itself.
* @type {!Object.<number, !goog.Disposable>}
* @private
*/
goog.Disposable.instances_ = {};
/**
* @return {!Array.<!goog.Disposable>} All {@code goog.Disposable} objects that
* haven't been disposed of.
*/
goog.Disposable.getUndisposedObjects = function() {
var ret = [];
for (var id in goog.Disposable.instances_) {
if (goog.Disposable.instances_.hasOwnProperty(id)) {
ret.push(goog.Disposable.instances_[Number(id)]);
}
}
return ret;
};
/**
* Clears the registry of undisposed objects but doesn't dispose of them.
*/
goog.Disposable.clearUndisposedObjects = function() {
goog.Disposable.instances_ = {};
};
/**
* Whether the object has been disposed of.
* @type {boolean}
* @private
*/
goog.Disposable.prototype.disposed_ = false;
/**
* Callbacks to invoke when this object is disposed.
* @type {Array.<!Function>}
* @private
*/
goog.Disposable.prototype.onDisposeCallbacks_;
/**
* If monitoring the goog.Disposable instances is enabled, stores the creation
* stack trace of the Disposable instance.
* @type {string}
*/
goog.Disposable.prototype.creationStack;
/**
* @return {boolean} Whether the object has been disposed of.
* @override
*/
goog.Disposable.prototype.isDisposed = function() {
return this.disposed_;
};
/**
* @return {boolean} Whether the object has been disposed of.
* @deprecated Use {@link #isDisposed} instead.
*/
goog.Disposable.prototype.getDisposed = goog.Disposable.prototype.isDisposed;
/**
* Disposes of the object. If the object hasn't already been disposed of, calls
* {@link #disposeInternal}. Classes that extend {@code goog.Disposable} should
* override {@link #disposeInternal} in order to delete references to COM
* objects, DOM nodes, and other disposable objects. Reentrant.
*
* @return {void} Nothing.
* @override
*/
goog.Disposable.prototype.dispose = function() {
if (!this.disposed_) {
// Set disposed_ to true first, in case during the chain of disposal this
// gets disposed recursively.
this.disposed_ = true;
this.disposeInternal();
if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) {
var uid = goog.getUid(this);
if (goog.Disposable.MONITORING_MODE ==
goog.Disposable.MonitoringMode.PERMANENT &&
!goog.Disposable.instances_.hasOwnProperty(uid)) {
throw Error(this + ' did not call the goog.Disposable base ' +
'constructor or was disposed of after a clearUndisposedObjects ' +
'call');
}
delete goog.Disposable.instances_[uid];
}
}
};
/**
* Associates a disposable object with this object so that they will be disposed
* together.
* @param {goog.disposable.IDisposable} disposable that will be disposed when
* this object is disposed.
*/
goog.Disposable.prototype.registerDisposable = function(disposable) {
this.addOnDisposeCallback(goog.partial(goog.dispose, disposable));
};
/**
* Invokes a callback function when this object is disposed. Callbacks are
* invoked in the order in which they were added.
* @param {function(this:T):?} callback The callback function.
* @param {T=} opt_scope An optional scope to call the callback in.
* @template T
*/
goog.Disposable.prototype.addOnDisposeCallback = function(callback, opt_scope) {
if (!this.onDisposeCallbacks_) {
this.onDisposeCallbacks_ = [];
}
this.onDisposeCallbacks_.push(goog.bind(callback, opt_scope));
};
/**
* Deletes or nulls out any references to COM objects, DOM nodes, or other
* disposable objects. Classes that extend {@code goog.Disposable} should
* override this method.
* Not reentrant. To avoid calling it twice, it must only be called from the
* subclass' {@code disposeInternal} method. Everywhere else the public
* {@code dispose} method must be used.
* For example:
* <pre>
* mypackage.MyClass = function() {
* goog.base(this);
* // Constructor logic specific to MyClass.
* ...
* };
* goog.inherits(mypackage.MyClass, goog.Disposable);
*
* mypackage.MyClass.prototype.disposeInternal = function() {
* // Dispose logic specific to MyClass.
* ...
* // Call superclass's disposeInternal at the end of the subclass's, like
* // in C++, to avoid hard-to-catch issues.
* goog.base(this, 'disposeInternal');
* };
* </pre>
* @protected
*/
goog.Disposable.prototype.disposeInternal = function() {
if (this.onDisposeCallbacks_) {
while (this.onDisposeCallbacks_.length) {
this.onDisposeCallbacks_.shift()();
}
}
};
/**
* Returns True if we can verify the object is disposed.
* Calls {@code isDisposed} on the argument if it supports it. If obj
* is not an object with an isDisposed() method, return false.
* @param {*} obj The object to investigate.
* @return {boolean} True if we can verify the object is disposed.
*/
goog.Disposable.isDisposed = function(obj) {
if (obj && typeof obj.isDisposed == 'function') {
return obj.isDisposed();
}
return false;
};
/**
* Calls {@code dispose} on the argument if it supports it. If obj is not an
* object with a dispose() method, this is a no-op.
* @param {*} obj The object to dispose of.
*/
goog.dispose = function(obj) {
if (obj && typeof obj.dispose == 'function') {
obj.dispose();
}
};
/**
* Calls {@code dispose} on each member of the list that supports it. (If the
* member is an ArrayLike, then {@code goog.disposeAll()} will be called
* recursively on each of its members.) If the member is not an object with a
* {@code dispose()} method, then it is ignored.
* @param {...*} var_args The list.
*/
goog.disposeAll = function(var_args) {
for (var i = 0, len = arguments.length; i < len; ++i) {
var disposable = arguments[i];
if (goog.isArrayLike(disposable)) {
goog.disposeAll.apply(null, disposable);
} else {
goog.dispose(disposable);
}
}
};

View File

@@ -0,0 +1,45 @@
// Copyright 2011 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 Definition of the disposable interface. A disposable object
* has a dispose method to to clean up references and resources.
* @author nnaze@google.com (Nathan Naze)
*/
goog.provide('goog.disposable.IDisposable');
/**
* Interface for a disposable object. If a instance requires cleanup
* (references COM objects, DOM notes, or other disposable objects), it should
* implement this interface (it may subclass goog.Disposable).
* @interface
*/
goog.disposable.IDisposable = function() {};
/**
* Disposes of the object and its resources.
* @return {void} Nothing.
*/
goog.disposable.IDisposable.prototype.dispose;
/**
* @return {boolean} Whether the object has been disposed of.
*/
goog.disposable.IDisposable.prototype.isDisposed;

View File

@@ -0,0 +1,169 @@
// Copyright 2007 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 Utilities for adding, removing and setting ARIA roles
* as defined by W3C ARIA Working Draft:
* http://www.w3.org/TR/2010/WD-wai-aria-20100916/
* All modern browsers have some form of ARIA support, so no browser checks are
* performed when adding ARIA to components.
*
*
* @deprecated Use {@link goog.a11y.aria} instead.
* This file will be removed on 1 Apr 2013.
*
*/
goog.provide('goog.dom.a11y');
goog.provide('goog.dom.a11y.Announcer');
goog.provide('goog.dom.a11y.LivePriority');
goog.provide('goog.dom.a11y.Role');
goog.provide('goog.dom.a11y.State');
goog.require('goog.a11y.aria');
goog.require('goog.a11y.aria.Announcer');
goog.require('goog.a11y.aria.LivePriority');
goog.require('goog.a11y.aria.Role');
goog.require('goog.a11y.aria.State');
/**
* Enumeration of ARIA states and properties.
* @enum {string}
* @deprecated Use {@link goog.a11y.aria.State} instead.
* This alias will be removed on 1 Apr 2013.
*/
goog.dom.a11y.State = goog.a11y.aria.State;
/**
* Enumeration of ARIA roles.
* @enum {string}
* @deprecated Use {@link goog.a11y.aria.Role} instead.
* This alias will be removed on 1 Apr 2013.
*/
goog.dom.a11y.Role = goog.a11y.aria.Role;
/**
* Enumeration of ARIA state values for live regions.
*
* See http://www.w3.org/TR/wai-aria/states_and_properties#aria-live
* for more information.
* @enum {string}
* @deprecated Use {@link goog.a11y.aria.LivePriority} instead.
* This alias will be removed on 1 Apr 2013.
*/
goog.dom.a11y.LivePriority = goog.a11y.aria.LivePriority;
/**
* Sets the role of an element.
* @param {Element} element DOM node to set role of.
* @param {goog.dom.a11y.Role|string} roleName role name(s).
* @deprecated Use {@link goog.a11y.aria.setRole} instead.
* This alias will be removed on 1 Apr 2013.
*/
goog.dom.a11y.setRole = function(element, roleName) {
goog.a11y.aria.setRole(
/** @type {!Element} */ (element),
/** @type {!goog.dom.a11y.Role} */ (roleName));
};
/**
* Gets role of an element.
* @param {Element} element DOM node to get role of.
* @return {?(goog.dom.a11y.Role|string)} rolename.
* @deprecated Use {@link goog.a11y.aria.getRole} instead.
* This alias will be removed on 1 Apr 2013.
*/
goog.dom.a11y.getRole = function(element) {
return /** @type {?(goog.dom.a11y.Role|string)} */ (
goog.a11y.aria.getRole(/** @type {!Element} */ (element)));
};
/**
* Sets the state or property of an element.
* @param {Element} element DOM node where we set state.
* @param {goog.dom.a11y.State|string} state State attribute being set.
* Automatically adds prefix 'aria-' to the state name.
* @param {boolean|number|string} value Value for the
* state attribute.
* @deprecated Use {@link goog.a11y.aria.setState} instead.
* This alias will be removed on 1 Apr 2013.
*/
goog.dom.a11y.setState = function(element, state, value) {
goog.a11y.aria.setState(
/** @type {!Element} */ (element),
/** @type {!goog.dom.a11y.State} */ (state),
/** @type {boolean|number|string} */ (value));
};
/**
* Gets value of specified state or property.
* @param {Element} element DOM node to get state from.
* @param {goog.dom.a11y.State|string} stateName State name.
* @return {string} Value of the state attribute.
* @deprecated Use {@link goog.a11y.aria.getState} instead.
* This alias will be removed on 1 Apr 2013.
*/
goog.dom.a11y.getState = function(element, stateName) {
return goog.a11y.aria.getState(
/** @type {!Element} */ (element),
/** @type {!goog.dom.a11y.State} */ (stateName));
};
/**
* Gets the activedescendant of the given element.
* @param {Element} element DOM node to get activedescendant from.
* @return {Element} DOM node of the activedescendant.
* @deprecated Use {@link goog.a11y.aria.getActiveDescendant} instead.
* This alias will be removed on 1 Apr 2013.
*/
goog.dom.a11y.getActiveDescendant = function(element) {
return goog.a11y.aria.getActiveDescendant(
/** @type {!Element} */ (element));
};
/**
* Sets the activedescendant value for an element.
* @param {Element} element DOM node to set activedescendant to.
* @param {Element} activeElement DOM node being set as activedescendant.
* @deprecated Use {@link goog.a11y.aria.setActiveDescendant} instead.
* This alias will be removed on 1 Apr 2013.
*/
goog.dom.a11y.setActiveDescendant = function(element, activeElement) {
goog.a11y.aria.setActiveDescendant(
/** @type {!Element} */ (element),
activeElement);
};
/**
* Class that allows messages to be spoken by assistive technologies that the
* user may have active.
*
* @param {goog.dom.DomHelper} domHelper DOM helper.
* @constructor
* @extends {goog.Disposable}
* @deprecated Use {@link goog.a11y.aria.Announcer} instead.
* This alias will be removed on 1 Apr 2013.
*/
goog.dom.a11y.Announcer = goog.a11y.aria.Announcer;

View File

@@ -0,0 +1,77 @@
// Copyright 2008 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 Utilities for working with ranges comprised of multiple
* sub-ranges.
*
* @author robbyw@google.com (Robby Walker)
* @author jparent@google.com (Julie Parent)
*/
goog.provide('goog.dom.AbstractMultiRange');
goog.require('goog.array');
goog.require('goog.dom');
goog.require('goog.dom.AbstractRange');
/**
* Creates a new multi range with no properties. Do not use this
* constructor: use one of the goog.dom.Range.createFrom* methods instead.
* @constructor
* @extends {goog.dom.AbstractRange}
*/
goog.dom.AbstractMultiRange = function() {
};
goog.inherits(goog.dom.AbstractMultiRange, goog.dom.AbstractRange);
/** @override */
goog.dom.AbstractMultiRange.prototype.containsRange = function(
otherRange, opt_allowPartial) {
// TODO(user): This will incorrectly return false if two (or more) adjacent
// elements are both in the control range, and are also in the text range
// being compared to.
var ranges = this.getTextRanges();
var otherRanges = otherRange.getTextRanges();
var fn = opt_allowPartial ? goog.array.some : goog.array.every;
return fn(otherRanges, function(otherRange) {
return goog.array.some(ranges, function(range) {
return range.containsRange(otherRange, opt_allowPartial);
});
});
};
/** @override */
goog.dom.AbstractMultiRange.prototype.insertNode = function(node, before) {
if (before) {
goog.dom.insertSiblingBefore(node, this.getStartNode());
} else {
goog.dom.insertSiblingAfter(node, this.getEndNode());
}
return node;
};
/** @override */
goog.dom.AbstractMultiRange.prototype.surroundWithNodes = function(startNode,
endNode) {
this.insertNode(startNode, true);
this.insertNode(endNode, false);
};

View File

@@ -0,0 +1,528 @@
// Copyright 2007 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 Interface definitions for working with ranges
* in HTML documents.
*
* @author robbyw@google.com (Robby Walker)
*/
goog.provide('goog.dom.AbstractRange');
goog.provide('goog.dom.RangeIterator');
goog.provide('goog.dom.RangeType');
goog.require('goog.dom');
goog.require('goog.dom.NodeType');
goog.require('goog.dom.SavedCaretRange');
goog.require('goog.dom.TagIterator');
goog.require('goog.userAgent');
/**
* Types of ranges.
* @enum {string}
*/
goog.dom.RangeType = {
TEXT: 'text',
CONTROL: 'control',
MULTI: 'mutli'
};
/**
* Creates a new selection with no properties. Do not use this constructor -
* use one of the goog.dom.Range.from* methods instead.
* @constructor
*/
goog.dom.AbstractRange = function() {
};
/**
* Gets the browser native selection object from the given window.
* @param {Window} win The window to get the selection object from.
* @return {Object} The browser native selection object, or null if it could
* not be retrieved.
*/
goog.dom.AbstractRange.getBrowserSelectionForWindow = function(win) {
if (win.getSelection) {
// W3C
return win.getSelection();
} else {
// IE
var doc = win.document;
var sel = doc.selection;
if (sel) {
// IE has a bug where it sometimes returns a selection from the wrong
// document. Catching these cases now helps us avoid problems later.
try {
var range = sel.createRange();
// Only TextRanges have a parentElement method.
if (range.parentElement) {
if (range.parentElement().document != doc) {
return null;
}
} else if (!range.length || range.item(0).document != doc) {
// For ControlRanges, check that the range has items, and that
// the first item in the range is in the correct document.
return null;
}
} catch (e) {
// If the selection is in the wrong document, and the wrong document is
// in a different domain, IE will throw an exception.
return null;
}
// TODO(user|robbyw) Sometimes IE 6 returns a selection instance
// when there is no selection. This object has a 'type' property equals
// to 'None' and a typeDetail property bound to undefined. Ideally this
// function should not return this instance.
return sel;
}
return null;
}
};
/**
* Tests if the given Object is a controlRange.
* @param {Object} range The range object to test.
* @return {boolean} Whether the given Object is a controlRange.
*/
goog.dom.AbstractRange.isNativeControlRange = function(range) {
// For now, tests for presence of a control range function.
return !!range && !!range.addElement;
};
/**
* @return {goog.dom.AbstractRange} A clone of this range.
*/
goog.dom.AbstractRange.prototype.clone = goog.abstractMethod;
/**
* @return {goog.dom.RangeType} The type of range represented by this object.
*/
goog.dom.AbstractRange.prototype.getType = goog.abstractMethod;
/**
* @return {Range|TextRange} The native browser range object.
*/
goog.dom.AbstractRange.prototype.getBrowserRangeObject = goog.abstractMethod;
/**
* Sets the native browser range object, overwriting any state this range was
* storing.
* @param {Range|TextRange} nativeRange The native browser range object.
* @return {boolean} Whether the given range was accepted. If not, the caller
* will need to call goog.dom.Range.createFromBrowserRange to create a new
* range object.
*/
goog.dom.AbstractRange.prototype.setBrowserRangeObject = function(nativeRange) {
return false;
};
/**
* @return {number} The number of text ranges in this range.
*/
goog.dom.AbstractRange.prototype.getTextRangeCount = goog.abstractMethod;
/**
* Get the i-th text range in this range. The behavior is undefined if
* i >= getTextRangeCount or i < 0.
* @param {number} i The range number to retrieve.
* @return {goog.dom.TextRange} The i-th text range.
*/
goog.dom.AbstractRange.prototype.getTextRange = goog.abstractMethod;
/**
* Gets an array of all text ranges this range is comprised of. For non-multi
* ranges, returns a single element array containing this.
* @return {Array.<goog.dom.TextRange>} Array of text ranges.
*/
goog.dom.AbstractRange.prototype.getTextRanges = function() {
var output = [];
for (var i = 0, len = this.getTextRangeCount(); i < len; i++) {
output.push(this.getTextRange(i));
}
return output;
};
/**
* @return {Node} The deepest node that contains the entire range.
*/
goog.dom.AbstractRange.prototype.getContainer = goog.abstractMethod;
/**
* Returns the deepest element in the tree that contains the entire range.
* @return {Element} The deepest element that contains the entire range.
*/
goog.dom.AbstractRange.prototype.getContainerElement = function() {
var node = this.getContainer();
return /** @type {Element} */ (
node.nodeType == goog.dom.NodeType.ELEMENT ? node : node.parentNode);
};
/**
* @return {Node} The element or text node the range starts in. For text
* ranges, the range comprises all text between the start and end position.
* For other types of range, start and end give bounds of the range but
* do not imply all nodes in those bounds are selected.
*/
goog.dom.AbstractRange.prototype.getStartNode = goog.abstractMethod;
/**
* @return {number} The offset into the node the range starts in. For text
* nodes, this is an offset into the node value. For elements, this is
* an offset into the childNodes array.
*/
goog.dom.AbstractRange.prototype.getStartOffset = goog.abstractMethod;
/**
* @return {goog.math.Coordinate} The coordinate of the selection start node
* and offset.
*/
goog.dom.AbstractRange.prototype.getStartPosition = goog.abstractMethod;
/**
* @return {Node} The element or text node the range ends in.
*/
goog.dom.AbstractRange.prototype.getEndNode = goog.abstractMethod;
/**
* @return {number} The offset into the node the range ends in. For text
* nodes, this is an offset into the node value. For elements, this is
* an offset into the childNodes array.
*/
goog.dom.AbstractRange.prototype.getEndOffset = goog.abstractMethod;
/**
* @return {goog.math.Coordinate} The coordinate of the selection end
* node and offset.
*/
goog.dom.AbstractRange.prototype.getEndPosition = goog.abstractMethod;
/**
* @return {Node} The element or text node the range is anchored at.
*/
goog.dom.AbstractRange.prototype.getAnchorNode = function() {
return this.isReversed() ? this.getEndNode() : this.getStartNode();
};
/**
* @return {number} The offset into the node the range is anchored at. For
* text nodes, this is an offset into the node value. For elements, this
* is an offset into the childNodes array.
*/
goog.dom.AbstractRange.prototype.getAnchorOffset = function() {
return this.isReversed() ? this.getEndOffset() : this.getStartOffset();
};
/**
* @return {Node} The element or text node the range is focused at - i.e. where
* the cursor is.
*/
goog.dom.AbstractRange.prototype.getFocusNode = function() {
return this.isReversed() ? this.getStartNode() : this.getEndNode();
};
/**
* @return {number} The offset into the node the range is focused at - i.e.
* where the cursor is. For text nodes, this is an offset into the node
* value. For elements, this is an offset into the childNodes array.
*/
goog.dom.AbstractRange.prototype.getFocusOffset = function() {
return this.isReversed() ? this.getStartOffset() : this.getEndOffset();
};
/**
* @return {boolean} Whether the selection is reversed.
*/
goog.dom.AbstractRange.prototype.isReversed = function() {
return false;
};
/**
* @return {Document} The document this selection is a part of.
*/
goog.dom.AbstractRange.prototype.getDocument = function() {
// Using start node in IE was crashing the browser in some cases so use
// getContainer for that browser. It's also faster for IE, but still slower
// than start node for other browsers so we continue to use getStartNode when
// it is not problematic. See bug 1687309.
return goog.dom.getOwnerDocument(goog.userAgent.IE ?
this.getContainer() : this.getStartNode());
};
/**
* @return {Window} The window this selection is a part of.
*/
goog.dom.AbstractRange.prototype.getWindow = function() {
return goog.dom.getWindow(this.getDocument());
};
/**
* Tests if this range contains the given range.
* @param {goog.dom.AbstractRange} range The range to test.
* @param {boolean=} opt_allowPartial If true, the range can be partially
* contained in the selection, otherwise the range must be entirely
* contained.
* @return {boolean} Whether this range contains the given range.
*/
goog.dom.AbstractRange.prototype.containsRange = goog.abstractMethod;
/**
* Tests if this range contains the given node.
* @param {Node} node The node to test for.
* @param {boolean=} opt_allowPartial If not set or false, the node must be
* entirely contained in the selection for this function to return true.
* @return {boolean} Whether this range contains the given node.
*/
goog.dom.AbstractRange.prototype.containsNode = function(node,
opt_allowPartial) {
return this.containsRange(goog.dom.Range.createFromNodeContents(node),
opt_allowPartial);
};
/**
* Tests whether this range is valid (i.e. whether its endpoints are still in
* the document). A range becomes invalid when, after this object was created,
* either one or both of its endpoints are removed from the document. Use of
* an invalid range can lead to runtime errors, particularly in IE.
* @return {boolean} Whether the range is valid.
*/
goog.dom.AbstractRange.prototype.isRangeInDocument = goog.abstractMethod;
/**
* @return {boolean} Whether the range is collapsed.
*/
goog.dom.AbstractRange.prototype.isCollapsed = goog.abstractMethod;
/**
* @return {string} The text content of the range.
*/
goog.dom.AbstractRange.prototype.getText = goog.abstractMethod;
/**
* Returns the HTML fragment this range selects. This is slow on all browsers.
* The HTML fragment may not be valid HTML, for instance if the user selects
* from a to b inclusively in the following html:
*
* &gt;div&lt;a&gt;/div&lt;b
*
* This method will return
*
* a&lt;/div&gt;b
*
* If you need valid HTML, use {@link #getValidHtml} instead.
*
* @return {string} HTML fragment of the range, does not include context
* containing elements.
*/
goog.dom.AbstractRange.prototype.getHtmlFragment = goog.abstractMethod;
/**
* Returns valid HTML for this range. This is fast on IE, and semi-fast on
* other browsers.
* @return {string} Valid HTML of the range, including context containing
* elements.
*/
goog.dom.AbstractRange.prototype.getValidHtml = goog.abstractMethod;
/**
* Returns pastable HTML for this range. This guarantees that any child items
* that must have specific ancestors will have them, for instance all TDs will
* be contained in a TR in a TBODY in a TABLE and all LIs will be contained in
* a UL or OL as appropriate. This is semi-fast on all browsers.
* @return {string} Pastable HTML of the range, including context containing
* elements.
*/
goog.dom.AbstractRange.prototype.getPastableHtml = goog.abstractMethod;
/**
* Returns a RangeIterator over the contents of the range. Regardless of the
* direction of the range, the iterator will move in document order.
* @param {boolean=} opt_keys Unused for this iterator.
* @return {goog.dom.RangeIterator} An iterator over tags in the range.
*/
goog.dom.AbstractRange.prototype.__iterator__ = goog.abstractMethod;
// RANGE ACTIONS
/**
* Sets this range as the selection in its window.
*/
goog.dom.AbstractRange.prototype.select = goog.abstractMethod;
/**
* Removes the contents of the range from the document.
*/
goog.dom.AbstractRange.prototype.removeContents = goog.abstractMethod;
/**
* Inserts a node before (or after) the range. The range may be disrupted
* beyond recovery because of the way this splits nodes.
* @param {Node} node The node to insert.
* @param {boolean} before True to insert before, false to insert after.
* @return {Node} The node added to the document. This may be different
* than the node parameter because on IE we have to clone it.
*/
goog.dom.AbstractRange.prototype.insertNode = goog.abstractMethod;
/**
* Replaces the range contents with (possibly a copy of) the given node. The
* range may be disrupted beyond recovery because of the way this splits nodes.
* @param {Node} node The node to insert.
* @return {Node} The node added to the document. This may be different
* than the node parameter because on IE we have to clone it.
*/
goog.dom.AbstractRange.prototype.replaceContentsWithNode = function(node) {
if (!this.isCollapsed()) {
this.removeContents();
}
return this.insertNode(node, true);
};
/**
* Surrounds this range with the two given nodes. The range may be disrupted
* beyond recovery because of the way this splits nodes.
* @param {Element} startNode The node to insert at the start.
* @param {Element} endNode The node to insert at the end.
*/
goog.dom.AbstractRange.prototype.surroundWithNodes = goog.abstractMethod;
// SAVE/RESTORE
/**
* Saves the range so that if the start and end nodes are left alone, it can
* be restored.
* @return {goog.dom.SavedRange} A range representation that can be restored
* as long as the endpoint nodes of the selection are not modified.
*/
goog.dom.AbstractRange.prototype.saveUsingDom = goog.abstractMethod;
/**
* Saves the range using HTML carets. As long as the carets remained in the
* HTML, the range can be restored...even when the HTML is copied across
* documents.
* @return {goog.dom.SavedCaretRange?} A range representation that can be
* restored as long as carets are not removed. Returns null if carets
* could not be created.
*/
goog.dom.AbstractRange.prototype.saveUsingCarets = function() {
return (this.getStartNode() && this.getEndNode()) ?
new goog.dom.SavedCaretRange(this) : null;
};
// RANGE MODIFICATION
/**
* Collapses the range to one of its boundary points.
* @param {boolean} toAnchor Whether to collapse to the anchor of the range.
*/
goog.dom.AbstractRange.prototype.collapse = goog.abstractMethod;
// RANGE ITERATION
/**
* Subclass of goog.dom.TagIterator that iterates over a DOM range. It
* adds functions to determine the portion of each text node that is selected.
* @param {Node} node The node to start traversal at. When null, creates an
* empty iterator.
* @param {boolean=} opt_reverse Whether to traverse nodes in reverse.
* @constructor
* @extends {goog.dom.TagIterator}
*/
goog.dom.RangeIterator = function(node, opt_reverse) {
goog.dom.TagIterator.call(this, node, opt_reverse, true);
};
goog.inherits(goog.dom.RangeIterator, goog.dom.TagIterator);
/**
* @return {number} The offset into the current node, or -1 if the current node
* is not a text node.
*/
goog.dom.RangeIterator.prototype.getStartTextOffset = goog.abstractMethod;
/**
* @return {number} The end offset into the current node, or -1 if the current
* node is not a text node.
*/
goog.dom.RangeIterator.prototype.getEndTextOffset = goog.abstractMethod;
/**
* @return {Node} node The iterator's start node.
*/
goog.dom.RangeIterator.prototype.getStartNode = goog.abstractMethod;
/**
* @return {Node} The iterator's end node.
*/
goog.dom.RangeIterator.prototype.getEndNode = goog.abstractMethod;
/**
* @return {boolean} Whether a call to next will fail.
*/
goog.dom.RangeIterator.prototype.isLast = goog.abstractMethod;

View File

@@ -0,0 +1,355 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Methods for annotating occurrences of query terms in text or
* in a DOM tree. Adapted from Gmail code.
*
*/
goog.provide('goog.dom.annotate');
goog.require('goog.array');
goog.require('goog.dom');
goog.require('goog.dom.NodeType');
goog.require('goog.string');
/**
* Calls {@code annotateFn} for each occurrence of a search term in text nodes
* under {@code node}. Returns the number of hits.
*
* @param {Node} node A DOM node.
* @param {Array} terms An array of [searchTerm, matchWholeWordOnly] tuples.
* The matchWholeWordOnly value is a per-term attribute because some terms
* may be CJK, while others are not. (For correctness, matchWholeWordOnly
* should always be false for CJK terms.).
* @param {Function} annotateFn A function that takes
* (1) the number of the term that is "hit",
* (2) the HTML string (search term) to be annotated,
* and returns the annotated term as an HTML string.
* @param {*=} opt_ignoreCase Whether to ignore the case of the query
* terms when looking for matches.
* @param {Array.<string>=} opt_classesToSkip Nodes with one of these CSS class
* names (and its descendants) will be skipped.
* @param {number=} opt_maxMs Number of milliseconds after which this function,
* if still annotating, should stop and return.
*
* @return {boolean} Whether any terms were annotated.
*/
goog.dom.annotate.annotateTerms = function(node, terms, annotateFn,
opt_ignoreCase,
opt_classesToSkip,
opt_maxMs) {
if (opt_ignoreCase) {
terms = goog.dom.annotate.lowercaseTerms_(terms);
}
var stopTime = opt_maxMs > 0 ? goog.now() + opt_maxMs : 0;
return goog.dom.annotate.annotateTermsInNode_(
node, terms, annotateFn, opt_ignoreCase, opt_classesToSkip || [],
stopTime, 0);
};
/**
* The maximum recursion depth allowed. Any DOM nodes deeper than this are
* ignored.
* @type {number}
* @private
*/
goog.dom.annotate.MAX_RECURSION_ = 200;
/**
* The node types whose descendants should not be affected by annotation.
* @type {Array}
* @private
*/
goog.dom.annotate.NODES_TO_SKIP_ = ['SCRIPT', 'STYLE', 'TEXTAREA'];
/**
* Recursive helper function.
*
* @param {Node} node A DOM node.
* @param {Array} terms An array of [searchTerm, matchWholeWordOnly] tuples.
* The matchWholeWordOnly value is a per-term attribute because some terms
* may be CJK, while others are not. (For correctness, matchWholeWordOnly
* should always be false for CJK terms.).
* @param {Function} annotateFn function(number, string) : string A function
* that takes :
* (1) the number of the term that is "hit",
* (2) the HTML string (search term) to be annotated,
* and returns the annotated term as an HTML string.
* @param {*} ignoreCase Whether to ignore the case of the query terms
* when looking for matches.
* @param {Array.<string>} classesToSkip Nodes with one of these CSS class
* names will be skipped (as will their descendants).
* @param {number} stopTime Deadline for annotation operation (ignored if 0).
* @param {number} recursionLevel How deep this recursive call is; pass the
* value 0 in the initial call.
* @return {boolean} Whether any terms were annotated.
* @private
*/
goog.dom.annotate.annotateTermsInNode_ =
function(node, terms, annotateFn, ignoreCase, classesToSkip,
stopTime, recursionLevel) {
if ((stopTime > 0 && goog.now() >= stopTime) ||
recursionLevel > goog.dom.annotate.MAX_RECURSION_) {
return false;
}
var annotated = false;
if (node.nodeType == goog.dom.NodeType.TEXT) {
var html = goog.dom.annotate.helpAnnotateText_(node.nodeValue, terms,
annotateFn, ignoreCase);
if (html != null) {
// Replace the text with the annotated html. First we put the html into
// a temporary node, to get its DOM structure. To avoid adding a wrapper
// element as a side effect, we'll only actually use the temporary node's
// children.
var tempNode = goog.dom.getOwnerDocument(node).createElement('SPAN');
tempNode.innerHTML = html;
var parentNode = node.parentNode;
var nodeToInsert;
while ((nodeToInsert = tempNode.firstChild) != null) {
// Each parentNode.insertBefore call removes the inserted node from
// tempNode's list of children.
parentNode.insertBefore(nodeToInsert, node);
}
parentNode.removeChild(node);
annotated = true;
}
} else if (node.hasChildNodes() &&
!goog.array.contains(goog.dom.annotate.NODES_TO_SKIP_,
node.tagName)) {
var classes = node.className.split(/\s+/);
var skip = goog.array.some(classes, function(className) {
return goog.array.contains(classesToSkip, className);
});
if (!skip) {
++recursionLevel;
var curNode = node.firstChild;
var numTermsAnnotated = 0;
while (curNode) {
var nextNode = curNode.nextSibling;
var curNodeAnnotated = goog.dom.annotate.annotateTermsInNode_(
curNode, terms, annotateFn, ignoreCase, classesToSkip,
stopTime, recursionLevel);
annotated = annotated || curNodeAnnotated;
curNode = nextNode;
}
}
}
return annotated;
};
/**
* Regular expression that matches non-word characters.
*
* Performance note: Testing a one-character string using this regex is as fast
* as the equivalent string test ("a-zA-Z0-9_".indexOf(c) < 0), give or take a
* few percent. (The regex is about 5% faster in IE 6 and about 4% slower in
* Firefox 1.5.) If performance becomes critical, it may be better to convert
* the character to a numerical char code and check whether it falls in the
* word character ranges. A quick test suggests that could be 33% faster.
*
* @type {RegExp}
* @private
*/
goog.dom.annotate.NONWORD_RE_ = /\W/;
/**
* Annotates occurrences of query terms in plain text. This process consists of
* identifying all occurrences of all query terms, calling a provided function
* to get the appropriate replacement HTML for each occurrence, and
* HTML-escaping all the text.
*
* @param {string} text The plain text to be searched.
* @param {Array} terms An array of
* [{string} searchTerm, {boolean} matchWholeWordOnly] tuples.
* The matchWholeWordOnly value is a per-term attribute because some terms
* may be CJK, while others are not. (For correctness, matchWholeWordOnly
* should always be false for CJK terms.).
* @param {Function} annotateFn {function(number, string) : string} A function
* that takes
* (1) the number of the term that is "hit",
* (2) the HTML string (search term) to be annotated,
* and returns the annotated term as an HTML string.
* @param {*=} opt_ignoreCase Whether to ignore the case of the query
* terms when looking for matches.
* @return {?string} The HTML equivalent of {@code text} with terms
* annotated, or null if the text did not contain any of the terms.
*/
goog.dom.annotate.annotateText = function(text, terms, annotateFn,
opt_ignoreCase) {
if (opt_ignoreCase) {
terms = goog.dom.annotate.lowercaseTerms_(terms);
}
return goog.dom.annotate.helpAnnotateText_(text, terms, annotateFn,
opt_ignoreCase);
};
/**
* Annotates occurrences of query terms in plain text. This process consists of
* identifying all occurrences of all query terms, calling a provided function
* to get the appropriate replacement HTML for each occurrence, and
* HTML-escaping all the text.
*
* @param {string} text The plain text to be searched.
* @param {Array} terms An array of
* [{string} searchTerm, {boolean} matchWholeWordOnly] tuples.
* If {@code ignoreCase} is true, each search term must already be lowercase.
* The matchWholeWordOnly value is a per-term attribute because some terms
* may be CJK, while others are not. (For correctness, matchWholeWordOnly
* should always be false for CJK terms.).
* @param {Function} annotateFn {function(number, string) : string} A function
* that takes
* (1) the number of the term that is "hit",
* (2) the HTML string (search term) to be annotated,
* and returns the annotated term as an HTML string.
* @param {*} ignoreCase Whether to ignore the case of the query terms
* when looking for matches.
* @return {?string} The HTML equivalent of {@code text} with terms
* annotated, or null if the text did not contain any of the terms.
* @private
*/
goog.dom.annotate.helpAnnotateText_ = function(text, terms, annotateFn,
ignoreCase) {
var hit = false;
var resultHtml = null;
var textToSearch = ignoreCase ? text.toLowerCase() : text;
var textLen = textToSearch.length;
var numTerms = terms.length;
// Each element will be an array of hit positions for the term.
var termHits = new Array(numTerms);
// First collect all the hits into allHits.
for (var i = 0; i < numTerms; i++) {
var term = terms[i];
var hits = [];
var termText = term[0];
if (termText != '') {
var matchWholeWordOnly = term[1];
var termLen = termText.length;
var pos = 0;
// Find each hit for term t and append to termHits.
while (pos < textLen) {
var hitPos = textToSearch.indexOf(termText, pos);
if (hitPos == -1) {
break;
} else {
var prevCharPos = hitPos - 1;
var nextCharPos = hitPos + termLen;
if (!matchWholeWordOnly ||
((prevCharPos < 0 ||
goog.dom.annotate.NONWORD_RE_.test(
textToSearch.charAt(prevCharPos))) &&
(nextCharPos >= textLen ||
goog.dom.annotate.NONWORD_RE_.test(
textToSearch.charAt(nextCharPos))))) {
hits.push(hitPos);
hit = true;
}
pos = hitPos + termLen;
}
}
}
termHits[i] = hits;
}
if (hit) {
var html = [];
var pos = 0;
while (true) {
// First determine which of the n terms is the next hit.
var termIndexOfNextHit;
var posOfNextHit = -1;
for (var i = 0; i < numTerms; i++) {
var hits = termHits[i];
// pull off the position of the next hit of term t
// (it's always the first in the array because we're shifting
// hits off the front of the array as we process them)
// this is the next candidate to consider for the next overall hit
if (!goog.array.isEmpty(hits)) {
var hitPos = hits[0];
// Discard any hits embedded in the previous hit.
while (hitPos >= 0 && hitPos < pos) {
hits.shift();
hitPos = goog.array.isEmpty(hits) ? -1 : hits[0];
}
if (hitPos >= 0 && (posOfNextHit < 0 || hitPos < posOfNextHit)) {
termIndexOfNextHit = i;
posOfNextHit = hitPos;
}
}
}
// Quit if there are no more hits.
if (posOfNextHit < 0) break;
// Remove the next hit from our hit list.
termHits[termIndexOfNextHit].shift();
// Append everything from the end of the last hit up to this one.
html.push(goog.string.htmlEscape(text.substr(pos, posOfNextHit - pos)));
// Append the annotated term.
var termLen = terms[termIndexOfNextHit][0].length;
var termHtml = goog.string.htmlEscape(text.substr(posOfNextHit, termLen));
html.push(annotateFn(termIndexOfNextHit, termHtml));
pos = posOfNextHit + termLen;
}
// Append everything after the last hit.
html.push(goog.string.htmlEscape(text.substr(pos)));
return html.join('');
} else {
return null;
}
};
/**
* Converts terms to lowercase.
*
* @param {Array} terms An array of
* [{string} searchTerm, {boolean} matchWholeWordOnly] tuples.
* @return {Array} An array of
* [{string} searchTerm, {boolean} matchWholeWordOnly] tuples.
* @private
*/
goog.dom.annotate.lowercaseTerms_ = function(terms) {
var lowercaseTerms = [];
for (var i = 0; i < terms.length; ++i) {
var term = terms[i];
lowercaseTerms[i] = [term[0].toLowerCase(), term[1]];
}
return lowercaseTerms;
};

View File

@@ -0,0 +1,67 @@
// 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 Browser capability checks for the dom package.
*
*/
goog.provide('goog.dom.BrowserFeature');
goog.require('goog.userAgent');
/**
* Enum of browser capabilities.
* @enum {boolean}
*/
goog.dom.BrowserFeature = {
/**
* Whether attributes 'name' and 'type' can be added to an element after it's
* created. False in Internet Explorer prior to version 9.
*/
CAN_ADD_NAME_OR_TYPE_ATTRIBUTES: !goog.userAgent.IE ||
goog.userAgent.isDocumentModeOrHigher(9),
/**
* Whether we can use element.children to access an element's Element
* children. Available since Gecko 1.9.1, IE 9. (IE<9 also includes comment
* nodes in the collection.)
*/
CAN_USE_CHILDREN_ATTRIBUTE: !goog.userAgent.GECKO && !goog.userAgent.IE ||
goog.userAgent.IE && goog.userAgent.isDocumentModeOrHigher(9) ||
goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9.1'),
/**
* Opera, Safari 3, and Internet Explorer 9 all support innerText but they
* include text nodes in script and style tags. Not document-mode-dependent.
*/
CAN_USE_INNER_TEXT: (
goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9')),
/**
* MSIE, Opera, and Safari>=4 support element.parentElement to access an
* element's parent if it is an Element.
*/
CAN_USE_PARENT_ELEMENT_PROPERTY: goog.userAgent.IE || goog.userAgent.OPERA ||
goog.userAgent.WEBKIT,
/**
* Whether NoScope elements need a scoped element written before them in
* innerHTML.
* MSDN: http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx#1
*/
INNER_HTML_NEEDS_SCOPED_ELEMENT: goog.userAgent.IE
};

Some files were not shown because too many files have changed in this diff Show More