Adding mapbox-gl branch

This commit is contained in:
Andreas Hocevar
2015-03-16 18:50:27 +01:00
parent 7985f030fa
commit 57ee7f52fd
3109 changed files with 943365 additions and 0 deletions
@@ -0,0 +1,125 @@
// 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}
* @final
*/
goog.a11y.aria.Announcer = function(opt_domHelper) {
goog.a11y.aria.Announcer.base(this, 'constructor');
/**
* @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.a11y.aria.Announcer.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) {
var priority = opt_priority || goog.a11y.aria.LivePriority.POLITE;
var liveRegion = this.getLiveRegion_(priority);
goog.dom.setTextContent(liveRegion, 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) {
// Removing the previous live region ensures that the newest message is always
// read without delay, even if the message is an exact duplicate of the prior
// message.
this.removeLiveRegion_(priority);
var 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;
};
/**
* Removes any previous live region that was used to communicate announcements.
* @param {!goog.a11y.aria.LivePriority} priority The required priority.
* @private
*/
goog.a11y.aria.Announcer.prototype.removeLiveRegion_ = function(priority) {
var liveRegion = this.liveRegions_[priority];
if (liveRegion) {
this.domHelper_.removeNode(liveRegion);
delete this.liveRegions_[priority];
}
};
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2008 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<!--
Author: attila@google.com (Attila Bodis)
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.a11y.aria announcer
</title>
<script src="../../base.js">
</script>
<script>
goog.require('goog.a11y.aria.AnnouncerTest');
</script>
</head>
<body>
<div id="sandbox">
</div>
</body>
</html>
@@ -0,0 +1,129 @@
// 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.
goog.provide('goog.a11y.aria.AnnouncerTest');
goog.setTestOnly('goog.a11y.aria.AnnouncerTest');
goog.require('goog.a11y.aria');
goog.require('goog.a11y.aria.Announcer');
goog.require('goog.a11y.aria.LivePriority');
goog.require('goog.a11y.aria.State');
goog.require('goog.array');
goog.require('goog.dom');
goog.require('goog.dom.iframe');
goog.require('goog.testing.jsunit');
var sandbox;
var someDiv;
var someSpan;
function setUp() {
sandbox = goog.dom.getElement('sandbox');
someDiv = goog.dom.createDom('div', {id: 'someDiv'}, 'DIV');
someSpan = goog.dom.createDom('span', {id: 'someSpan'}, 'SPAN');
sandbox.appendChild(someDiv);
someDiv.appendChild(someSpan);
}
function tearDown() {
sandbox.innerHTML = '';
someDiv = null;
someSpan = null;
}
function testAnnouncerAndDispose() {
var text = 'test content';
var announcer = new goog.a11y.aria.Announcer(goog.dom.getDomHelper());
announcer.say(text);
checkLiveRegionContains(text, 'polite');
goog.dispose(announcer);
}
function testAnnouncerTwice() {
var text = 'test content1';
var text2 = 'test content2';
var announcer = new goog.a11y.aria.Announcer(goog.dom.getDomHelper());
announcer.say(text);
announcer.say(text2);
checkLiveRegionContains(text2, 'polite');
goog.dispose(announcer);
}
function testAnnouncerTwiceSameMessage() {
var text = 'test content';
var announcer = new goog.a11y.aria.Announcer(goog.dom.getDomHelper());
announcer.say(text);
var firstLiveRegion = getLiveRegion('polite');
announcer.say(text);
var secondLiveRegion = getLiveRegion('polite');
assertNotEquals(firstLiveRegion, secondLiveRegion);
checkLiveRegionContains(text, 'polite');
goog.dispose(announcer);
}
function testAnnouncerAssertive() {
var text = 'test content';
var announcer = new goog.a11y.aria.Announcer(goog.dom.getDomHelper());
announcer.say(text, goog.a11y.aria.LivePriority.ASSERTIVE);
checkLiveRegionContains(text, 'assertive');
goog.dispose(announcer);
}
function testAnnouncerInIframe() {
var text = 'test content';
var frame = goog.dom.iframe.createWithContent(sandbox);
var helper = goog.dom.getDomHelper(
goog.dom.getFrameContentDocument(frame).body);
var announcer = new goog.a11y.aria.Announcer(helper);
announcer.say(text, 'polite', helper);
checkLiveRegionContains(text, 'polite', helper);
goog.dispose(announcer);
}
function testAnnouncerWithAriaHidden() {
var text = 'test content1';
var text2 = 'test content2';
var announcer = new goog.a11y.aria.Announcer(goog.dom.getDomHelper());
announcer.say(text);
// Set aria-hidden attribute on the live region (simulates a modal dialog
// being opened).
var liveRegion = getLiveRegion('polite');
goog.a11y.aria.setState(liveRegion, goog.a11y.aria.State.HIDDEN, true);
// Announce a new message and make sure that the aria-hidden was removed.
announcer.say(text2);
checkLiveRegionContains(text2, 'polite');
liveRegion = getLiveRegion('polite');
assertEquals('',
goog.a11y.aria.getState(liveRegion, goog.a11y.aria.State.HIDDEN));
goog.dispose(announcer);
}
function getLiveRegion(priority, opt_domHelper) {
var dom = opt_domHelper || goog.dom.getDomHelper();
var divs = dom.getElementsByTagNameAndClass('div', null);
var liveRegions = [];
goog.array.forEach(divs, function(div) {
if (goog.a11y.aria.getState(div, 'live') == priority) {
liveRegions.push(div);
}
});
assertEquals(1, liveRegions.length);
return liveRegions[0];
}
function checkLiveRegionContains(text, priority, opt_domHelper) {
var liveRegion = getLiveRegion(priority, opt_domHelper);
assertEquals(text, goog.dom.getTextContent(liveRegion));
}
@@ -0,0 +1,386 @@
// 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|!Array<string>} value Value
* for the state attribute.
*/
goog.a11y.aria.setState = function(element, stateName, value) {
if (goog.isArray(value)) {
value = value.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);
}
};
/**
* Toggles the ARIA attribute of an element.
* Meant for attributes with a true/false value, but works with any attribute.
* If the attribute does not have a true/false value, the following rules apply:
* A not empty attribute will be removed.
* An empty attribute will be set to true.
* @param {!Element} el DOM node for which to set attribute.
* @param {!(goog.a11y.aria.State|string)} attr ARIA attribute being set.
* Automatically adds prefix 'aria-' to the attribute name if the attribute
* is not an extra attribute.
*/
goog.a11y.aria.toggleState = function(el, attr) {
var val = goog.a11y.aria.getState(el, attr);
if (!goog.string.isEmptyOrWhitespace(goog.string.makeSafe(val)) &&
!(val == 'true' || val == 'false')) {
goog.a11y.aria.removeState(el, /** @type {!goog.a11y.aria.State} */ (attr));
return;
}
goog.a11y.aria.setState(el, attr, val == 'true' ? 'false' : 'true');
};
/**
* 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.
* @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', not 'false', or not set.
*/
goog.a11y.aria.getStateBoolean = function(element, stateName) {
var attr =
/** @type {string|boolean} */ (element.getAttribute(
goog.a11y.aria.getAriaAttributeName_(stateName)));
goog.asserts.assert(
goog.isBoolean(attr) || attr == null || attr == 'true' ||
attr == 'false');
if (attr == null) {
return attr;
}
return goog.isBoolean(attr) ? attr : attr == 'true';
};
/**
* Gets the number value of an ARIA state/property.
* @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 or not set.
*/
goog.a11y.aria.getStateNumber = function(element, stateName) {
var attr =
/** @type {string|number} */ (element.getAttribute(
goog.a11y.aria.getAriaAttributeName_(stateName)));
goog.asserts.assert((attr == null || !isNaN(Number(attr))) &&
!goog.isBoolean(attr));
return attr == null ? null : Number(attr);
};
/**
* Gets the string value of an ARIA state/property.
* @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 or not set.
*/
goog.a11y.aria.getStateString = function(element, stateName) {
var attr = element.getAttribute(
goog.a11y.aria.getAriaAttributeName_(stateName));
goog.asserts.assert((attr == null || goog.isString(attr)) &&
isNaN(Number(attr)) && attr != 'true' && attr != 'false');
return attr == null ? null : attr;
};
/**
* 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);
};
/**
* 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;
};
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2008 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<!--
Author: attila@google.com (Attila Bodis)
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.a11y.aria
</title>
<script src="../../base.js">
</script>
<script>
goog.require('goog.a11y.ariaTest');
</script>
</head>
<body>
<div id="sandbox">
</div>
</body>
</html>
@@ -0,0 +1,269 @@
// 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.goog.provide('goog.a11y.ariaTest');
goog.provide('goog.a11y.ariaTest');
goog.setTestOnly('goog.a11y.ariaTest');
goog.require('goog.a11y.aria');
goog.require('goog.a11y.aria.Role');
goog.require('goog.a11y.aria.State');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.testing.jsunit');
var aria = goog.a11y.aria;
var Role = goog.a11y.aria.Role;
var State = goog.a11y.aria.State;
var sandbox;
var someDiv;
var someSpan;
var htmlButton;
function setUp() {
sandbox = goog.dom.getElement('sandbox');
someDiv = goog.dom.createDom(
goog.dom.TagName.DIV, {id: 'someDiv'}, 'DIV');
someSpan = goog.dom.createDom(
goog.dom.TagName.SPAN, {id: 'someSpan'}, 'SPAN');
htmlButton = goog.dom.createDom(
goog.dom.TagName.BUTTON, {id: 'someButton'}, 'BUTTON');
goog.dom.appendChild(sandbox, someDiv);
goog.dom.appendChild(someDiv, someSpan);
}
function tearDown() {
goog.dom.removeChildren(sandbox);
someDiv = null;
someSpan = null;
htmlButton = null;
}
function testGetSetRole() {
assertNull('someDiv\'s role should be null', aria.getRole(someDiv));
assertNull('someSpan\'s role should be null', aria.getRole(someSpan));
aria.setRole(someDiv, Role.MENU);
aria.setRole(someSpan, Role.MENU_ITEM);
assertEquals('someDiv\'s role should be MENU',
Role.MENU, aria.getRole(someDiv));
assertEquals('someSpan\'s role should be MENU_ITEM',
Role.MENU_ITEM, aria.getRole(someSpan));
var div = goog.dom.createElement(goog.dom.TagName.DIV);
goog.dom.appendChild(sandbox, div);
goog.dom.appendChild(div, goog.dom.createDom(goog.dom.TagName.SPAN,
{id: 'anotherSpan', role: Role.CHECKBOX}));
assertEquals('anotherSpan\'s role should be CHECKBOX',
Role.CHECKBOX, aria.getRole(goog.dom.getElement('anotherSpan')));
}
function testGetSetToggleState() {
assertThrows('Should throw because no state is specified.',
function() {
aria.getState(someDiv);
});
assertThrows('Should throw because no state is specified.',
function() {
aria.getState(someDiv);
});
aria.setState(someDiv, State.LABELLEDBY, 'someSpan');
assertEquals('someDiv\'s labelledby state should be "someSpan"',
'someSpan', aria.getState(someDiv, State.LABELLEDBY));
// Test setting for aria-activedescendant with empty value.
assertFalse(someDiv.hasAttribute ?
someDiv.hasAttribute('aria-activedescendant') :
!!someDiv.getAttribute('aria-activedescendant'));
aria.setState(someDiv, State.ACTIVEDESCENDANT, 'someSpan');
assertEquals('someSpan', aria.getState(someDiv, State.ACTIVEDESCENDANT));
aria.setState(someDiv, State.ACTIVEDESCENDANT, '');
assertFalse(someDiv.hasAttribute ?
someDiv.hasAttribute('aria-activedescendant') :
!!someDiv.getAttribute('aria-activedescendant'));
// Test setting state that has a default value to empty value.
assertFalse(someDiv.hasAttribute ?
someDiv.hasAttribute('aria-relevant') :
!!someDiv.getAttribute('aria-relevant'));
aria.setState(someDiv, State.RELEVANT, aria.RelevantValues.TEXT);
assertEquals(
aria.RelevantValues.TEXT, aria.getState(someDiv, State.RELEVANT));
aria.setState(someDiv, State.RELEVANT, '');
assertEquals(
aria.RelevantValues.ADDITIONS + ' ' + aria.RelevantValues.TEXT,
aria.getState(someDiv, State.RELEVANT));
// Test toggling an attribute that has a true/false value.
aria.setState(someDiv, State.EXPANDED, false);
assertEquals('false', aria.getState(someDiv, State.EXPANDED));
aria.toggleState(someDiv, State.EXPANDED);
assertEquals('true', aria.getState(someDiv, State.EXPANDED));
aria.setState(someDiv, State.EXPANDED, true);
assertEquals('true', aria.getState(someDiv, State.EXPANDED));
aria.toggleState(someDiv, State.EXPANDED);
assertEquals('false', aria.getState(someDiv, State.EXPANDED));
// Test toggling an attribute that does not have a true/false value.
aria.setState(someDiv, State.RELEVANT, aria.RelevantValues.TEXT);
assertEquals(
aria.RelevantValues.TEXT, aria.getState(someDiv, State.RELEVANT));
aria.toggleState(someDiv, State.RELEVANT);
assertEquals('', aria.getState(someDiv, State.RELEVANT));
aria.removeState(someDiv, State.RELEVANT);
assertEquals('', aria.getState(someDiv, State.RELEVANT));
// This is not a valid value, but this is what happens if toggle is misused.
aria.toggleState(someDiv, State.RELEVANT);
assertEquals('true', aria.getState(someDiv, State.RELEVANT));
}
function testGetStateString() {
aria.setState(someDiv, State.LABEL, 'test_label');
aria.setState(
someSpan, State.LABEL, aria.getStateString(someDiv, State.LABEL));
assertEquals(aria.getState(someDiv, State.LABEL),
aria.getState(someSpan, State.LABEL));
assertEquals('The someDiv\'s enum value should be "test_label".',
'test_label', aria.getState(someDiv, State.LABEL));
assertEquals('The someSpan\'s enum value should be "copy move".',
'test_label', aria.getStateString(someSpan, State.LABEL));
aria.setState(someDiv, State.MULTILINE, true);
var thrown = false;
try {
aria.getStateString(someDiv, State.MULTILINE);
} catch (e) {
thrown = true;
}
assertTrue('invalid use of getStateString on boolean.', thrown);
aria.setState(someDiv, State.LIVE, aria.LivePriority.ASSERTIVE);
thrown = false;
aria.setState(someDiv, State.LEVEL, 1);
try {
aria.getStateString(someDiv, State.LEVEL);
} catch (e) {
thrown = true;
}
assertTrue('invalid use of getStateString on numbers.', thrown);
}
function testGetStateStringArray() {
aria.setState(someDiv, State.LABELLEDBY, ['1', '2']);
aria.setState(someSpan, State.LABELLEDBY,
aria.getStringArrayStateInternalUtil(someDiv, State.LABELLEDBY));
assertEquals(aria.getState(someDiv, State.LABELLEDBY),
aria.getState(someSpan, State.LABELLEDBY));
assertEquals('The someDiv\'s enum value should be "1 2".', '1 2',
aria.getState(someDiv, State.LABELLEDBY));
assertEquals('The someSpan\'s enum value should be "1 2".', '1 2',
aria.getState(someSpan, State.LABELLEDBY));
assertSameElements('The someDiv\'s enum value should be "1 2".',
['1', '2'],
aria.getStringArrayStateInternalUtil(someDiv, State.LABELLEDBY));
assertSameElements('The someSpan\'s enum value should be "1 2".',
['1', '2'],
aria.getStringArrayStateInternalUtil(someSpan, State.LABELLEDBY));
}
function testGetStateNumber() {
aria.setState(someDiv, State.LEVEL, 1);
aria.setState(
someSpan, State.LEVEL, aria.getStateNumber(someDiv, State.LEVEL));
assertEquals(aria.getState(someDiv, State.LEVEL),
aria.getState(someSpan, State.LEVEL));
assertEquals('The someDiv\'s enum value should be "1".', '1',
aria.getState(someDiv, State.LEVEL));
assertEquals('The someSpan\'s enum value should be "1".', '1',
aria.getState(someSpan, State.LEVEL));
assertEquals('The someDiv\'s enum value should be "1".', 1,
aria.getStateNumber(someDiv, State.LEVEL));
assertEquals('The someSpan\'s enum value should be "1".', 1,
aria.getStateNumber(someSpan, State.LEVEL));
aria.setState(someDiv, State.MULTILINE, true);
var thrown = false;
try {
aria.getStateNumber(someDiv, State.MULTILINE);
} catch (e) {
thrown = true;
}
assertTrue('invalid use of getStateNumber on boolean.', thrown);
aria.setState(someDiv, State.LIVE, aria.LivePriority.ASSERTIVE);
thrown = false;
try {
aria.getStateBoolean(someDiv, State.LIVE);
} catch (e) {
thrown = true;
}
assertTrue('invalid use of getStateNumber on strings.', thrown);
}
function testGetStateBoolean() {
assertNull(aria.getStateBoolean(someDiv, State.MULTILINE));
aria.setState(someDiv, State.MULTILINE, false);
assertFalse(aria.getStateBoolean(someDiv, State.MULTILINE));
aria.setState(someDiv, State.MULTILINE, true);
aria.setState(someSpan, State.MULTILINE,
aria.getStateBoolean(someDiv, State.MULTILINE));
assertEquals(aria.getState(someDiv, State.MULTILINE),
aria.getState(someSpan, State.MULTILINE));
assertEquals('The someDiv\'s enum value should be "true".', 'true',
aria.getState(someDiv, State.MULTILINE));
assertEquals('The someSpan\'s enum value should be "true".', 'true',
aria.getState(someSpan, State.MULTILINE));
assertEquals('The someDiv\'s enum value should be "true".', true,
aria.getStateBoolean(someDiv, State.MULTILINE));
assertEquals('The someSpan\'s enum value should be "true".', true,
aria.getStateBoolean(someSpan, State.MULTILINE));
aria.setState(someDiv, State.LEVEL, 1);
var thrown = false;
try {
aria.getStateBoolean(someDiv, State.LEVEL);
} catch (e) {
thrown = true;
}
assertTrue('invalid use of getStateBoolean on numbers.', thrown);
aria.setState(someDiv, State.LIVE, aria.LivePriority.ASSERTIVE);
thrown = false;
try {
aria.getStateBoolean(someDiv, State.LIVE);
} catch (e) {
thrown = true;
}
assertTrue('invalid use of getStateBoolean on strings.', thrown);
}
function testGetSetActiveDescendant() {
aria.setActiveDescendant(someDiv, null);
assertNull('someDiv\'s activedescendant should be null',
aria.getActiveDescendant(someDiv));
aria.setActiveDescendant(someDiv, someSpan);
assertEquals('someDiv\'s active descendant should be "someSpan"',
someSpan, aria.getActiveDescendant(someDiv));
}
function testGetSetLabel() {
assertEquals('someDiv\'s label should be ""', '', aria.getLabel(someDiv));
aria.setLabel(someDiv, 'somelabel');
assertEquals('someDiv\'s label should be "somelabel"', 'somelabel',
aria.getLabel(someDiv));
}
@@ -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'
};
@@ -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_;
};
@@ -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
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2006 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.array
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.arrayTest');
</script>
</head>
<body>
<div class="foo">
</div>
</body>
</html>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,365 @@
// 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.
*
* @author agrieve@google.com (Andrew Grieve)
*/
goog.provide('goog.asserts');
goog.provide('goog.asserts.AssertionError');
goog.require('goog.debug.Error');
goog.require('goog.dom.NodeType');
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}
* @final
*/
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';
/**
* The default error handler.
* @param {!goog.asserts.AssertionError} e The exception to be handled.
*/
goog.asserts.DEFAULT_ERROR_HANDLER = function(e) { throw e; };
/**
* The handler responsible for throwing or logging assertion errors.
* @private {function(!goog.asserts.AssertionError)}
*/
goog.asserts.errorHandler_ = goog.asserts.DEFAULT_ERROR_HANDLER;
/**
* 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).
var e = new goog.asserts.AssertionError('' + message, args || []);
goog.asserts.errorHandler_(e);
};
/**
* Sets a custom error handler that can be used to customize the behavior of
* assertion failures, for example by turning all assertion failures into log
* messages.
* @param {function(!goog.asserts.AssertionError)} errorHandler
*/
goog.asserts.setErrorHandler = function(errorHandler) {
if (goog.asserts.ENABLE_ASSERTS) {
goog.asserts.errorHandler_ = errorHandler;
}
};
/**
* Checks if the condition evaluates to true if goog.asserts.ENABLE_ASSERTS is
* true.
* @template T
* @param {T} 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 {T} 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) {
goog.asserts.errorHandler_(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 a DOM Element 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 {!Element} The value, likely to be a DOM Element when asserts are
* enabled.
* @throws {goog.asserts.AssertionError} When the value is not a boolean.
*/
goog.asserts.assertElement = function(value, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && (!goog.isObject(value) ||
value.nodeType != goog.dom.NodeType.ELEMENT)) {
goog.asserts.doAssertFailure_('Expected Element but got %s: %s.',
[goog.typeOf(value), value], opt_message,
Array.prototype.slice.call(arguments, 2));
}
return /** @type {!Element} */ (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_('Expected instanceof %s but got %s.',
[goog.asserts.getType_(type), goog.asserts.getType_(value)],
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.');
}
};
/**
* Returns the type of a value. If a constructor is passed, and a suitable
* string cannot be found, 'unknown type name' will be returned.
* @param {*} value A constructor, object, or primitive.
* @return {string} The best display name for the value, or 'unknown type name'.
* @private
*/
goog.asserts.getType_ = function(value) {
if (value instanceof Function) {
return value.displayName || value.name || 'unknown type name';
} else if (value instanceof Object) {
return value.constructor.displayName || value.constructor.name ||
Object.prototype.toString.call(value);
} else {
return value === null ? 'null' : typeof value;
}
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2008 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.asserts.assert
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.assertsTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,242 @@
// 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.
goog.provide('goog.assertsTest');
goog.setTestOnly('goog.assertsTest');
goog.require('goog.asserts');
goog.require('goog.asserts.AssertionError');
goog.require('goog.dom');
goog.require('goog.string');
goog.require('goog.testing.jsunit');
goog.require('goog.userAgent');
function doTestMessage(failFunc, expectedMsg) {
var error = assertThrows('failFunc should throw.', failFunc);
// Test error message.
// Opera 10 adds cruft to the end of the message, so do a startsWith check.
assertTrue('Message check failed. Expected: ' + expectedMsg + ' Actual: ' +
error.message, goog.string.startsWith(error.message, expectedMsg));
}
function testAssert() {
// None of them may throw exception
goog.asserts.assert(true);
goog.asserts.assert(1);
goog.asserts.assert([]);
goog.asserts.assert({});
assertThrows('assert(false)', goog.partial(goog.asserts.assert, false));
assertThrows('assert(0)', goog.partial(goog.asserts.assert, 0));
assertThrows('assert(null)', goog.partial(goog.asserts.assert, null));
assertThrows('assert(undefined)',
goog.partial(goog.asserts.assert, undefined));
// Test error messages.
doTestMessage(goog.partial(goog.asserts.assert, false), 'Assertion failed');
doTestMessage(goog.partial(goog.asserts.assert, false, 'ouch %s', 1),
'Assertion failed: ouch 1');
}
function testFail() {
assertThrows('fail()', goog.asserts.fail);
// Test error messages.
doTestMessage(goog.partial(goog.asserts.fail, false), 'Failure');
doTestMessage(goog.partial(goog.asserts.fail, 'ouch %s', 1),
'Failure: ouch 1');
}
function testNumber() {
goog.asserts.assertNumber(1);
assertThrows('assertNumber(null)',
goog.partial(goog.asserts.assertNumber, null));
// Test error messages.
doTestMessage(goog.partial(goog.asserts.assertNumber, null),
'Assertion failed: Expected number but got null: null.');
doTestMessage(goog.partial(goog.asserts.assertNumber, '1234'),
'Assertion failed: Expected number but got string: 1234.');
doTestMessage(goog.partial(goog.asserts.assertNumber, null, 'ouch %s', 1),
'Assertion failed: ouch 1');
}
function testString() {
assertEquals('1', goog.asserts.assertString('1'));
assertThrows('assertString(null)',
goog.partial(goog.asserts.assertString, null));
// Test error messages.
doTestMessage(goog.partial(goog.asserts.assertString, null),
'Assertion failed: Expected string but got null: null.');
doTestMessage(goog.partial(goog.asserts.assertString, 1234),
'Assertion failed: Expected string but got number: 1234.');
doTestMessage(goog.partial(goog.asserts.assertString, null, 'ouch %s', 1),
'Assertion failed: ouch 1');
}
function testFunction() {
function f() {};
assertEquals(f, goog.asserts.assertFunction(f));
assertThrows('assertFunction(null)',
goog.partial(goog.asserts.assertFunction, null));
// Test error messages.
doTestMessage(goog.partial(goog.asserts.assertFunction, null),
'Assertion failed: Expected function but got null: null.');
doTestMessage(goog.partial(goog.asserts.assertFunction, 1234),
'Assertion failed: Expected function but got number: 1234.');
doTestMessage(goog.partial(goog.asserts.assertFunction, null, 'ouch %s', 1),
'Assertion failed: ouch 1');
}
function testObject() {
var o = {};
assertEquals(o, goog.asserts.assertObject(o));
assertThrows('assertObject(null)',
goog.partial(goog.asserts.assertObject, null));
// Test error messages.
doTestMessage(goog.partial(goog.asserts.assertObject, null),
'Assertion failed: Expected object but got null: null.');
doTestMessage(goog.partial(goog.asserts.assertObject, 1234),
'Assertion failed: Expected object but got number: 1234.');
doTestMessage(goog.partial(goog.asserts.assertObject, null, 'ouch %s', 1),
'Assertion failed: ouch 1');
}
function testArray() {
var a = [];
assertEquals(a, goog.asserts.assertArray(a));
assertThrows('assertArray({})',
goog.partial(goog.asserts.assertArray, {}));
// Test error messages.
doTestMessage(goog.partial(goog.asserts.assertArray, null),
'Assertion failed: Expected array but got null: null.');
doTestMessage(goog.partial(goog.asserts.assertArray, 1234),
'Assertion failed: Expected array but got number: 1234.');
doTestMessage(goog.partial(goog.asserts.assertArray, null, 'ouch %s', 1),
'Assertion failed: ouch 1');
}
function testBoolean() {
assertEquals(true, goog.asserts.assertBoolean(true));
assertEquals(false, goog.asserts.assertBoolean(false));
assertThrows(goog.partial(goog.asserts.assertBoolean, null));
assertThrows(goog.partial(goog.asserts.assertBoolean, 'foo'));
// Test error messages.
doTestMessage(goog.partial(goog.asserts.assertBoolean, null),
'Assertion failed: Expected boolean but got null: null.');
doTestMessage(goog.partial(goog.asserts.assertBoolean, 1234),
'Assertion failed: Expected boolean but got number: 1234.');
doTestMessage(goog.partial(goog.asserts.assertBoolean, null, 'ouch %s', 1),
'Assertion failed: ouch 1');
}
function testElement() {
assertThrows(goog.partial(goog.asserts.assertElement, null));
assertThrows(goog.partial(goog.asserts.assertElement, 'foo'));
assertThrows(goog.partial(goog.asserts.assertElement,
goog.dom.createTextNode('foo')));
var elem = goog.dom.createElement('div');
assertEquals(elem, goog.asserts.assertElement(elem));
}
function testInstanceof() {
/** @constructor */
var F = function() {};
goog.asserts.assertInstanceof(new F(), F);
assertThrows('assertInstanceof({}, F)',
goog.partial(goog.asserts.assertInstanceof, {}, F));
// IE lacks support for function.name and will fallback to toString().
var object = goog.userAgent.IE ? '[object Object]' : 'Object';
// Test error messages.
doTestMessage(goog.partial(goog.asserts.assertInstanceof, {}, F),
'Assertion failed: Expected instanceof unknown type name but got ' +
object + '.');
doTestMessage(goog.partial(goog.asserts.assertInstanceof, {}, F, 'a %s', 1),
'Assertion failed: a 1');
doTestMessage(goog.partial(goog.asserts.assertInstanceof, null, F),
'Assertion failed: Expected instanceof unknown type name but got null.');
doTestMessage(goog.partial(goog.asserts.assertInstanceof, 5, F),
'Assertion failed: ' +
'Expected instanceof unknown type name but got number.');
// Test a constructor a with a name (IE does not support function.name).
if (!goog.userAgent.IE) {
F = function foo() {};
doTestMessage(goog.partial(goog.asserts.assertInstanceof, {}, F),
'Assertion failed: Expected instanceof foo but got ' + object + '.');
}
// Test a constructor with a displayName.
F.displayName = 'bar';
doTestMessage(goog.partial(goog.asserts.assertInstanceof, {}, F),
'Assertion failed: Expected instanceof bar but got ' + object + '.');
}
function testObjectPrototypeIsIntact() {
goog.asserts.assertObjectPrototypeIsIntact();
var originalToString = Object.prototype.toString;
Object.prototype.toString = goog.nullFunction;
try {
goog.asserts.assertObjectPrototypeIsIntact();
Object.prototype.foo = 1;
doTestMessage(goog.asserts.assertObjectPrototypeIsIntact,
'Failure: foo should not be enumerable in Object.prototype.');
} finally {
Object.prototype.toString = originalToString;
delete Object.prototype.foo;
}
}
function testAssertionError() {
var error = new goog.asserts.AssertionError('foo %s %s', [1, 'two']);
assertEquals('Wrong message', 'foo 1 two', error.message);
assertEquals('Wrong messagePattern', 'foo %s %s', error.messagePattern);
}
function testFailWithCustomErrorHandler() {
try {
var handledException;
goog.asserts.setErrorHandler(
function(e) { handledException = e; });
var expectedMessage = 'Failure: Gevalt!';
goog.asserts.fail('Gevalt!');
assertTrue('handledException is null.', handledException != null);
assertTrue('Message check failed. Expected: ' + expectedMessage +
' Actual: ' + handledException.message,
goog.string.startsWith(expectedMessage, handledException.message));
} finally {
goog.asserts.setErrorHandler(goog.asserts.DEFAULT_ERROR_HANDLER);
}
}
function testAssertWithCustomErrorHandler() {
try {
var handledException;
goog.asserts.setErrorHandler(
function(e) { handledException = e; });
var expectedMessage = 'Assertion failed: Gevalt!';
goog.asserts.assert(false, 'Gevalt!');
assertTrue('handledException is null.', handledException != null);
assertTrue('Message check failed. Expected: ' + expectedMessage +
' Actual: ' + handledException.message,
goog.string.startsWith(expectedMessage, handledException.message));
} finally {
goog.asserts.setErrorHandler(goog.asserts.DEFAULT_ERROR_HANDLER);
}
}
@@ -0,0 +1,273 @@
// 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.
*
* @author nicksantos@google.com (Nick Santos)
*/
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}
* @final
*/
goog.async.AnimationDelay = function(listener, opt_window, opt_handler) {
goog.async.AnimationDelay.base(this, 'constructor');
/**
* 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.async.AnimationDelay.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.cancelAnimationFrame ||
win.cancelRequestAnimationFrame ||
win.webkitCancelRequestAnimationFrame ||
win.mozCancelRequestAnimationFrame ||
win.oCancelRequestAnimationFrame ||
win.msCancelRequestAnimationFrame ||
null;
};
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2012 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
Author: nicksantos@google.com (Nick Santos)
-->
<head>
<title>
JsUnit tests for goog.async.AnimationDelay
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.async.AnimationDelayTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,81 @@
// 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.
goog.provide('goog.async.AnimationDelayTest');
goog.setTestOnly('goog.async.AnimationDelayTest');
goog.require('goog.async.AnimationDelay');
goog.require('goog.testing.AsyncTestCase');
goog.require('goog.testing.PropertyReplacer');
goog.require('goog.testing.jsunit');
goog.require('goog.testing.recordFunction');
var testCase = goog.testing.AsyncTestCase.createAndInstall();
var stubs = new goog.testing.PropertyReplacer();
function tearDown() {
stubs.reset();
}
function testStart() {
var callCount = 0;
var start = goog.now();
var delay = new goog.async.AnimationDelay(function(end) {
callCount++;
});
delay.start();
testCase.waitForAsync('waiting for delay');
window.setTimeout(function() {
testCase.continueTesting();
assertEquals(1, callCount);
}, 500);
}
function testStop() {
var callCount = 0;
var start = goog.now();
var delay = new goog.async.AnimationDelay(function(end) {
callCount++;
});
delay.start();
testCase.waitForAsync('waiting for delay');
delay.stop();
window.setTimeout(function() {
testCase.continueTesting();
assertEquals(0, callCount);
}, 500);
}
function testAlwaysUseGoogNowForHandlerTimestamp() {
var expectedValue = 12345.1;
stubs.set(goog, 'now', function() {
return expectedValue;
});
var handler = goog.testing.recordFunction(function(timestamp) {
assertEquals(expectedValue, timestamp);
});
var delay = new goog.async.AnimationDelay(handler);
delay.start();
testCase.waitForAsync('waiting for delay');
window.setTimeout(function() {
testCase.continueTesting();
assertEquals(1, handler.getCallCount());
}, 500);
}
@@ -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));
}
}
}
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2008 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.async.ConditionalDelay
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.async.ConditionalDelayTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,222 @@
// 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.
goog.provide('goog.async.ConditionalDelayTest');
goog.setTestOnly('goog.async.ConditionalDelayTest');
goog.require('goog.async.ConditionalDelay');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.jsunit');
var invoked = false;
var delay = null;
var clock = null;
var returnValue = true;
var onSuccessCalled = false;
var onFailureCalled = false;
function callback() {
invoked = true;
return returnValue;
}
function setUp() {
clock = new goog.testing.MockClock(true);
invoked = false;
returnValue = true;
onSuccessCalled = false;
onFailureCalled = false;
delay = new goog.async.ConditionalDelay(callback);
delay.onSuccess = function() {
onSuccessCalled = true;
};
delay.onFailure = function() {
onFailureCalled = true;
};
}
function tearDown() {
clock.dispose();
delay.dispose();
}
function testDelay() {
delay.start(200, 200);
assertFalse(invoked);
clock.tick(100);
assertFalse(invoked);
clock.tick(100);
assertTrue(invoked);
}
function testStop() {
delay.start(200, 500);
assertTrue(delay.isActive());
clock.tick(100);
assertFalse(invoked);
delay.stop();
clock.tick(100);
assertFalse(invoked);
assertFalse(delay.isActive());
}
function testIsActive() {
assertFalse(delay.isActive());
delay.start(200, 200);
assertTrue(delay.isActive());
clock.tick(200);
assertFalse(delay.isActive());
}
function testRestart() {
delay.start(200, 50000);
clock.tick(100);
delay.stop();
assertFalse(invoked);
delay.start(200, 50000);
clock.tick(199);
assertFalse(invoked);
clock.tick(1);
assertTrue(invoked);
invoked = false;
delay.start(200, 200);
clock.tick(200);
assertTrue(invoked);
assertFalse(delay.isActive());
}
function testDispose() {
delay.start(200, 200);
delay.dispose();
assertTrue(delay.isDisposed());
clock.tick(500);
assertFalse(invoked);
}
function testConditionalDelay_Success() {
returnValue = false;
delay.start(100, 300);
clock.tick(99);
assertFalse(invoked);
clock.tick(1);
assertTrue(invoked);
assertTrue(delay.isActive());
assertFalse(delay.isDone());
assertFalse(onSuccessCalled);
assertFalse(onFailureCalled);
returnValue = true;
invoked = false;
clock.tick(100);
assertTrue(invoked);
assertFalse(delay.isActive());
assertTrue(delay.isDone());
assertTrue(onSuccessCalled);
assertFalse(onFailureCalled);
invoked = false;
clock.tick(200);
assertFalse(invoked);
}
function testConditionalDelay_Failure() {
returnValue = false;
delay.start(100, 300);
clock.tick(99);
assertFalse(invoked);
clock.tick(1);
assertTrue(invoked);
assertTrue(delay.isActive());
assertFalse(delay.isDone());
assertFalse(onSuccessCalled);
assertFalse(onFailureCalled);
invoked = false;
clock.tick(100);
assertTrue(invoked);
assertFalse(onSuccessCalled);
assertFalse(onFailureCalled);
invoked = false;
clock.tick(90);
assertFalse(invoked);
clock.tick(10);
assertTrue(invoked);
assertFalse(delay.isActive());
assertFalse(delay.isDone());
assertFalse(onSuccessCalled);
assertTrue(onFailureCalled);
}
function testInfiniteDelay() {
returnValue = false;
delay.start(100, -1);
// Test in a big enough loop.
for (var i = 0; i < 1000; ++i) {
clock.tick(80);
assertTrue(delay.isActive());
assertFalse(delay.isDone());
assertFalse(onSuccessCalled);
assertFalse(onFailureCalled);
}
delay.stop();
assertFalse(delay.isActive());
assertFalse(delay.isDone());
assertFalse(onSuccessCalled);
assertFalse(onFailureCalled);
}
function testCallbackScope() {
var callbackCalled = false;
var scopeObject = {};
function internalCallback() {
assertEquals(this, scopeObject);
callbackCalled = true;
return true;
}
delay = new goog.async.ConditionalDelay(internalCallback, scopeObject);
delay.start(200, 200);
clock.tick(201);
assertTrue(callbackCalled);
}
@@ -0,0 +1,179 @@
// 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}
* @final
*/
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
* @final
*/
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_);
}
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2007 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.async.Delay
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.async.DelayTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,142 @@
// 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.
goog.provide('goog.async.DelayTest');
goog.setTestOnly('goog.async.DelayTest');
goog.require('goog.async.Delay');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.jsunit');
var invoked = false;
var delay = null;
var clock = null;
function callback() {
invoked = true;
}
function setUp() {
clock = new goog.testing.MockClock(true);
invoked = false;
delay = new goog.async.Delay(callback, 200);
}
function tearDown() {
clock.dispose();
delay.dispose();
}
function testDelay() {
delay.start();
assertFalse(invoked);
clock.tick(100);
assertFalse(invoked);
clock.tick(100);
assertTrue(invoked);
}
function testStop() {
delay.start();
clock.tick(100);
assertFalse(invoked);
delay.stop();
clock.tick(100);
assertFalse(invoked);
}
function testIsActive() {
assertFalse(delay.isActive());
delay.start();
assertTrue(delay.isActive());
clock.tick(200);
assertFalse(delay.isActive());
}
function testRestart() {
delay.start();
clock.tick(100);
delay.stop();
assertFalse(invoked);
delay.start();
clock.tick(199);
assertFalse(invoked);
clock.tick(1);
assertTrue(invoked);
invoked = false;
delay.start();
clock.tick(200);
assertTrue(invoked);
}
function testOverride() {
delay.start(50);
clock.tick(49);
assertFalse(invoked);
clock.tick(1);
assertTrue(invoked);
}
function testDispose() {
delay.start();
delay.dispose();
assertTrue(delay.isDisposed());
clock.tick(500);
assertFalse(invoked);
}
function testFire() {
delay.start();
clock.tick(50);
delay.fire();
assertTrue(invoked);
assertFalse(delay.isActive());
invoked = false;
clock.tick(200);
assertFalse('Delay fired early with fire call, timeout should have been ' +
'cleared', invoked);
}
function testFireIfActive() {
delay.fireIfActive();
assertFalse(invoked);
delay.start();
delay.fireIfActive();
assertTrue(invoked);
invoked = false;
clock.tick(300);
assertFalse('Delay fired early with fireIfActive, timeout should have been ' +
'cleared', invoked);
}
@@ -0,0 +1,231 @@
// 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.provide('goog.async.throwException');
goog.require('goog.debug.entryPointRegistry');
goog.require('goog.functions');
goog.require('goog.labs.userAgent.browser');
/**
* Throw an item without interrupting the current execution context. For
* example, if processing a group of items in a loop, sometimes it is useful
* to report an error while still allowing the rest of the batch to be
* processed.
* @param {*} exception
*/
goog.async.throwException = function(exception) {
// Each throw needs to be in its own context.
goog.global.setTimeout(function() { throw exception; }, 0);
};
/**
* Fires the provided callbacks as soon as possible after the current JS
* execution context. setTimeout(…, 0) takes at least 4ms when called from
* within another setTimeout(…, 0) for legacy reasons.
*
* This will not schedule the callback as a microtask (i.e. a task that can
* preempt user input or networking callbacks). It is meant to emulate what
* setTimeout(_, 0) would do if it were not throttled. If you desire microtask
* behavior, use {@see goog.Promise} instead.
*
* @param {function(this:SCOPE)} callback Callback function to fire as soon as
* possible.
* @param {SCOPE=} opt_context Object in whose scope to call the listener.
* @param {boolean=} opt_useSetImmediate Avoid the IE workaround that
* ensures correctness at the cost of speed. See comments for details.
* @template SCOPE
*/
goog.async.nextTick = function(callback, opt_context, opt_useSetImmediate) {
var cb = callback;
if (opt_context) {
cb = goog.bind(callback, opt_context);
}
cb = goog.async.nextTick.wrapCallback_(cb);
// window.setImmediate was introduced and currently only supported by IE10+,
// but due to a bug in the implementation it is not guaranteed that
// setImmediate is faster than setTimeout nor that setImmediate N is before
// setImmediate N+1. That is why we do not use the native version if
// available. We do, however, call setImmediate if it is a normal function
// because that indicates that it has been replaced by goog.testing.MockClock
// which we do want to support.
// See
// http://connect.microsoft.com/IE/feedback/details/801823/setimmediate-and-messagechannel-are-broken-in-ie10
//
// Note we do allow callers to also request setImmediate if they are willing
// to accept the possible tradeoffs of incorrectness in exchange for speed.
// The IE fallback of readystate change is much slower.
if (goog.isFunction(goog.global.setImmediate) &&
(opt_useSetImmediate || !goog.global.Window ||
goog.global.Window.prototype.setImmediate != 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.documentElement.appendChild(iframe);
var win = iframe.contentWindow;
var doc = win.document;
doc.open();
doc.write('');
doc.close();
// Do not post anything sensitive over this channel, as the workaround for
// pages with file: origin could allow that information to be modified or
// intercepted.
var message = 'callImmediate' + Math.random();
// The same origin policy rejects attempts to postMessage from file: urls
// unless the origin is '*'.
// TODO(b/16335441): Use '*' origin for data: and other similar protocols.
var origin = win.location.protocol == 'file:' ?
'*' : 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 the origin is set to '*' (see above) only the
// message needs to match since, for example, '*' != 'file://'. Allowing
// the wildcard is ok, as we are not concerned with security here.
if ((origin != '*' && 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' &&
(!goog.labs.userAgent.browser.isIE())) {
// Exclude all of IE due to
// http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/
// which allows starving postMessage with a busy setTimeout loop.
// This currently affects IE10 and IE11 which would otherwise be able
// to use the postMessage based fallbacks.
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() {
if (goog.isDef(head.next)) {
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+: 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;
});
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2013 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.async.nextTick
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.async.nextTickTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,243 @@
// 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.
goog.provide('goog.async.nextTickTest');
goog.setTestOnly('goog.async.nextTickTest');
goog.require('goog.async.nextTick');
goog.require('goog.debug.ErrorHandler');
goog.require('goog.debug.entryPointRegistry');
goog.require('goog.dom');
goog.require('goog.labs.userAgent.browser');
goog.require('goog.testing.AsyncTestCase');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.PropertyReplacer');
goog.require('goog.testing.jsunit');
var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall(
'asyncNextTickTest');
var clock;
var propertyReplacer = new goog.testing.PropertyReplacer();
function setUp() {
clock = null;
}
function tearDown() {
if (clock) {
clock.uninstall();
}
goog.async.nextTick.setImmediate_ = undefined;
propertyReplacer.reset();
}
function testNextTick() {
var c = 0;
var max = 100;
var async = true;
var counterStep = function(i) {
async = false;
assertEquals('Order correct', i, c);
c++;
if (c === max) {
asyncTestCase.continueTesting();
}
};
for (var i = 0; i < max; i++) {
goog.async.nextTick(goog.partial(counterStep, i));
}
assertTrue(async);
asyncTestCase.waitForAsync('Wait for callback');
}
function testNextTickSetImmediate() {
var c = 0;
var max = 100;
var async = true;
var counterStep = function(i) {
async = false;
assertEquals('Order correct', i, c);
c++;
if (c === max) {
asyncTestCase.continueTesting();
}
};
for (var i = 0; i < max; i++) {
goog.async.nextTick(goog.partial(counterStep, i), undefined,
/* opt_useSetImmediate */ true);
}
assertTrue(async);
asyncTestCase.waitForAsync('Wait for callback');
}
function testNextTickContext() {
var context = {};
var c = 0;
var max = 10;
var async = true;
var counterStep = function(i) {
async = false;
assertEquals('Order correct', i, c);
assertEquals(context, this);
c++;
if (c === max) {
asyncTestCase.continueTesting();
}
};
for (var i = 0; i < max; i++) {
goog.async.nextTick(goog.partial(counterStep, i), context);
}
assertTrue(async);
asyncTestCase.waitForAsync('Wait for callback');
}
function testNextTickMockClock() {
clock = new goog.testing.MockClock(true);
var result = '';
goog.async.nextTick(function() {
result += 'a';
});
goog.async.nextTick(function() {
result += 'b';
});
goog.async.nextTick(function() {
result += 'c';
});
assertEquals('', result);
clock.tick(0);
assertEquals('abc', result);
}
function testNextTickDoesntSwallowError() {
var before = window.onerror;
var sentinel = 'sentinel';
window.onerror = function(e) {
e = '' + e;
// Don't test for contents in IE7, which does not preserve the exception
// message.
if (e.indexOf('Exception thrown and not caught') == -1) {
assertContains(sentinel, e);
}
window.onerror = before;
asyncTestCase.continueTesting();
return false;
};
goog.async.nextTick(function() {
throw sentinel;
});
asyncTestCase.waitForAsync('Wait for error');
}
function testNextTickProtectEntryPoint() {
var errorHandlerCallbackCalled = false;
var errorHandler = new goog.debug.ErrorHandler(function() {
errorHandlerCallbackCalled = true;
});
// This is only testing wrapping the callback with the protected entry point,
// so it's okay to replace this function with a fake.
goog.async.nextTick.setImmediate_ = function(cb) {
try {
cb();
fail('The callback should have thrown an error.');
} catch (e) {
assertTrue(
e instanceof goog.debug.ErrorHandler.ProtectedFunctionError);
}
asyncTestCase.continueTesting();
};
var origSetImmediate;
if (window.setImmediate) {
origSetImmediate = window.setImmediate;
window.setImmediate = goog.async.nextTick.setImmediate_;
}
goog.debug.entryPointRegistry.monitorAll(errorHandler);
function thrower() {
throw Error('This should be caught by the protected function.');
}
asyncTestCase.waitForAsync('Wait for callback');
goog.async.nextTick(thrower);
window.setImmediate = origSetImmediate;
}
function testNextTick_notStarvedBySetTimeout() {
// This test will timeout when affected by
// http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/
// This test would fail without the fix introduced in cl/72472221
// It keeps scheduling 0 timeouts and a single nextTick. If the nextTick
// ever fires, the IE specific problem does not occur.
var timeout;
function busy() {
timeout = setTimeout(function() {
busy();
}, 0);
}
busy();
goog.async.nextTick(function() {
if (timeout) {
clearTimeout(timeout);
}
asyncTestCase.continueTesting();
});
asyncTestCase.waitForAsync('Waiting not to starve');
}
/**
* Test a scenario in which the iframe used by the postMessage polyfill gets a
* message that does not have match what is expected. In this case, the polyfill
* should not try to invoke a callback (which would result in an error because
* there would be no callbacks in the linked list).
*/
function testPostMessagePolyfillDoesNotPumpCallbackQueueIfMessageIsIncorrect() {
// IE does not use the postMessage polyfill.
if (goog.labs.userAgent.browser.isIE()) {
return;
}
// Force postMessage pollyfill for setImmediate.
propertyReplacer.set(window, 'setImmediate', undefined);
propertyReplacer.set(window, 'MessageChannel', undefined);
var callbackCalled = false;
goog.async.nextTick(function() {
callbackCalled = true;
});
var frame = document.getElementsByTagName('iframe')[0];
frame.contentWindow.postMessage('bogus message',
window.location.protocol + '//' + window.location.host);
var error = null;
frame.contentWindow.onerror = function(e) {
error = e;
};
setTimeout(function() {
assert('Callback should have been called.', callbackCalled);
assertNull('An unexpected error was thrown.', error);
goog.dom.removeNode(frame);
asyncTestCase.continueTesting();
}, 0);
asyncTestCase.waitForAsync('Waiting for callbacks to be invoked.');
}
@@ -0,0 +1,150 @@
// 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.
goog.provide('goog.async.run');
goog.require('goog.async.nextTick');
goog.require('goog.async.throwException');
goog.require('goog.testing.watchers');
/**
* Fires the provided callback just before the current callstack unwinds, or as
* soon as possible after the current JS execution context.
* @param {function(this:THIS)} callback
* @param {THIS=} opt_context Object to use as the "this value" when calling
* the provided function.
* @template THIS
*/
goog.async.run = function(callback, opt_context) {
if (!goog.async.run.schedule_) {
goog.async.run.initializeRunner_();
}
if (!goog.async.run.workQueueScheduled_) {
// Nothing is currently scheduled, schedule it now.
goog.async.run.schedule_();
goog.async.run.workQueueScheduled_ = true;
}
goog.async.run.workQueue_.push(
new goog.async.run.WorkItem_(callback, opt_context));
};
/**
* Initializes the function to use to process the work queue.
* @private
*/
goog.async.run.initializeRunner_ = function() {
// If native Promises are available in the browser, just schedule the callback
// on a fulfilled promise, which is specified to be async, but as fast as
// possible.
if (goog.global.Promise && goog.global.Promise.resolve) {
var promise = goog.global.Promise.resolve();
goog.async.run.schedule_ = function() {
promise.then(goog.async.run.processWorkQueue);
};
} else {
goog.async.run.schedule_ = function() {
goog.async.nextTick(goog.async.run.processWorkQueue);
};
}
};
/**
* Forces goog.async.run to use nextTick instead of Promise.
*
* This should only be done in unit tests. It's useful because MockClock
* replaces nextTick, but not the browser Promise implementation, so it allows
* Promise-based code to be tested with MockClock.
*/
goog.async.run.forceNextTick = function() {
goog.async.run.schedule_ = function() {
goog.async.nextTick(goog.async.run.processWorkQueue);
};
};
/**
* The function used to schedule work asynchronousely.
* @private {function()}
*/
goog.async.run.schedule_;
/** @private {boolean} */
goog.async.run.workQueueScheduled_ = false;
/** @private {!Array<!goog.async.run.WorkItem_>} */
goog.async.run.workQueue_ = [];
if (goog.DEBUG) {
/**
* Reset the event queue.
* @private
*/
goog.async.run.resetQueue_ = function() {
goog.async.run.workQueueScheduled_ = false;
goog.async.run.workQueue_ = [];
};
// If there is a clock implemenation in use for testing
// and it is reset, reset the queue.
goog.testing.watchers.watchClockReset(goog.async.run.resetQueue_);
}
/**
* Run any pending goog.async.run work items. This function is not intended
* for general use, but for use by entry point handlers to run items ahead of
* goog.async.nextTick.
*/
goog.async.run.processWorkQueue = function() {
// NOTE: additional work queue items may be pushed while processing.
while (goog.async.run.workQueue_.length) {
// Don't let the work queue grow indefinitely.
var workItems = goog.async.run.workQueue_;
goog.async.run.workQueue_ = [];
for (var i = 0; i < workItems.length; i++) {
var workItem = workItems[i];
try {
workItem.fn.call(workItem.scope);
} catch (e) {
goog.async.throwException(e);
}
}
}
// There are no more work items, reset the work queue.
goog.async.run.workQueueScheduled_ = false;
};
/**
* @constructor
* @final
* @struct
* @private
*
* @param {function()} fn
* @param {Object|null|undefined} scope
*/
goog.async.run.WorkItem_ = function(fn, scope) {
/** @const */ this.fn = fn;
/** @const */ this.scope = scope;
};
@@ -0,0 +1,147 @@
// 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.
goog.provide('goog.async.runTest');
goog.require('goog.async.run');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.jsunit');
goog.require('goog.testing.recordFunction');
goog.setTestOnly('goog.async.runTest');
var mockClock;
var futureCallback1, futureCallback2;
function setUpPage() {
mockClock = new goog.testing.MockClock();
mockClock.install();
}
function setUp() {
mockClock.reset();
futureCallback1 = new goog.testing.recordFunction();
futureCallback2 = new goog.testing.recordFunction();
}
function tearDown() {
futureCallback1 = null;
futureCallback2 = null;
}
function tearDownPage() {
mockClock.uninstall();
goog.dispose(mockClock);
}
function testCalledAsync() {
goog.async.run(futureCallback1);
goog.async.run(futureCallback2);
assertEquals(0, futureCallback1.getCallCount());
assertEquals(0, futureCallback2.getCallCount());
// but the callbacks are scheduled...
mockClock.tick();
// and called.
assertEquals(1, futureCallback1.getCallCount());
assertEquals(1, futureCallback2.getCallCount());
// and aren't called a second time.
assertEquals(1, futureCallback1.getCallCount());
assertEquals(1, futureCallback2.getCallCount());
}
function testSequenceCalledInOrder() {
futureCallback1 = new goog.testing.recordFunction(
function() {
// called before futureCallback2
assertEquals(0, futureCallback2.getCallCount());
});
futureCallback2 = new goog.testing.recordFunction(
function() {
// called after futureCallback1
assertEquals(1, futureCallback1.getCallCount());
});
goog.async.run(futureCallback1);
goog.async.run(futureCallback2);
// goog.async.run doesn't call the top callback immediately.
assertEquals(0, futureCallback1.getCallCount());
// but the callbacks are scheduled...
mockClock.tick();
// and called during the same "tick".
assertEquals(1, futureCallback1.getCallCount());
assertEquals(1, futureCallback2.getCallCount());
}
function testSequenceScheduledTwice() {
goog.async.run(futureCallback1);
goog.async.run(futureCallback1);
// goog.async.run doesn't call the top callback immediately.
assertEquals(0, futureCallback1.getCallCount());
// but the callbacks are scheduled...
mockClock.tick();
// and called twice during the same "tick".
assertEquals(2, futureCallback1.getCallCount());
}
function testSequenceCalledSync() {
futureCallback1 = new goog.testing.recordFunction(
function() {
goog.async.run(futureCallback2);
// goog.async.run doesn't call the inner callback immediately.
assertEquals(0, futureCallback2.getCallCount());
});
goog.async.run(futureCallback1);
// goog.async.run doesn't call the top callback immediately.
assertEquals(0, futureCallback1.getCallCount());
// but the callbacks are scheduled...
mockClock.tick();
// and called during the same "tick".
assertEquals(1, futureCallback1.getCallCount());
assertEquals(1, futureCallback2.getCallCount());
}
function testScope() {
var aScope = {};
goog.async.run(futureCallback1);
goog.async.run(futureCallback2, aScope);
// the callbacks are scheduled...
mockClock.tick();
// and called.
assertEquals(1, futureCallback1.getCallCount());
assertEquals(1, futureCallback2.getCallCount());
// and get the correct scope.
var last1 = futureCallback1.popLastCall();
assertEquals(0, last1.getArguments().length);
assertEquals(goog.global, last1.getThis());
var last2 = futureCallback2.popLastCall();
assertEquals(0, last2.getArguments().length);
assertEquals(aScope, last2.getThis());
}
@@ -0,0 +1,193 @@
// 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(this: T)} listener Function to callback when the action is
* triggered.
* @param {number} interval Interval over which to throttle. The listener can
* only be called once per interval.
* @param {T=} opt_handler Object in whose scope to call the listener.
* @constructor
* @extends {goog.Disposable}
* @final
* @template T
*/
goog.async.Throttle = function(listener, interval, opt_handler) {
goog.Disposable.call(this);
/**
* Function to callback
* @type {function(this: T)}
* @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
* @final
*/
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_);
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2006 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.async.Throttle
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.async.ThrottleTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,95 @@
// 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.
goog.provide('goog.async.ThrottleTest');
goog.setTestOnly('goog.async.ThrottleTest');
goog.require('goog.async.Throttle');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.jsunit');
function testThrottle() {
var clock = new goog.testing.MockClock(true);
var callBackCount = 0;
var callBackFunction = function() {
callBackCount++;
};
var throttle = new goog.async.Throttle(callBackFunction, 100);
assertEquals(0, callBackCount);
throttle.fire();
assertEquals(1, callBackCount);
throttle.fire();
assertEquals(1, callBackCount);
throttle.fire();
throttle.fire();
assertEquals(1, callBackCount);
clock.tick(101);
assertEquals(2, callBackCount);
clock.tick(101);
assertEquals(2, callBackCount);
throttle.fire();
assertEquals(3, callBackCount);
throttle.fire();
assertEquals(3, callBackCount);
throttle.stop();
clock.tick(101);
assertEquals(3, callBackCount);
throttle.fire();
assertEquals(4, callBackCount);
clock.tick(101);
assertEquals(4, callBackCount);
throttle.fire();
throttle.fire();
assertEquals(5, callBackCount);
throttle.pause();
throttle.resume();
assertEquals(5, callBackCount);
throttle.pause();
clock.tick(101);
assertEquals(5, callBackCount);
throttle.resume();
assertEquals(6, callBackCount);
clock.tick(101);
assertEquals(6, callBackCount);
throttle.pause();
throttle.fire();
assertEquals(6, callBackCount);
clock.tick(101);
assertEquals(6, callBackCount);
throttle.resume();
assertEquals(7, callBackCount);
throttle.pause();
throttle.pause();
clock.tick(101);
throttle.fire();
throttle.resume();
assertEquals(7, callBackCount);
throttle.resume();
assertEquals(8, callBackCount);
throttle.pause();
throttle.pause();
throttle.fire();
throttle.resume();
clock.tick(101);
assertEquals(8, callBackCount);
throttle.resume();
assertEquals(9, callBackCount);
clock.uninstall();
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2006 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Closure Unit Tests - goog.module</title>
<script src="base.js"></script>
</head>
<body>
<script>
goog.require('goog.baseModuleTest');
</script>
</body>
</html>
@@ -0,0 +1,113 @@
// 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 Unit tests for Closure's base.js's goog.module support.
*/
goog.module('goog.baseModuleTest');
goog.module.declareTestMethods();
goog.setTestOnly('goog.baseModuleTest');
// Used to test dynamic loading works, see testRequire*
var Timer = goog.require('goog.Timer');
var Replacer = goog.require('goog.testing.PropertyReplacer');
var jsunit = goog.require('goog.testing.jsunit');
var testModule = goog.require('goog.test_module');
var stubs = new Replacer();
function assertProvideFails(namespace) {
assertThrows('goog.provide(' + namespace + ') should have failed',
goog.partial(goog.provide, namespace));
}
function assertModuleFails(namespace) {
assertThrows('goog.module(' + namespace + ') should have failed',
goog.partial(goog.module, namespace));
}
exports = {
teardown: function() {
stubs.reset();
},
testModuleDecl: function() {
// assert that goog.module doesn't modify the global namespace
assertUndefined('module failed to protect global namespace: ' +
'goog.baseModuleTest', goog.baseModuleTest);
},
testModuleScoping: function() {
// assert test functions are exported to the global namespace
assertNotUndefined('module failed: testModule', testModule);
assertTrue('module failed: testModule',
goog.isFunction(goog.global.testModuleScoping));
},
testProvideStrictness1: function() {
assertModuleFails('goog.xy'); // not in goog.loadModule
assertProvideFails('goog.baseModuleTest'); // this file.
},
testProvideStrictness2: function() {
// goog.module "provides" a namespace
assertTrue(goog.isProvided_('goog.baseModuleTest'));
},
testExportSymbol: function() {
// Assert that export symbol works from within a goog.module.
var date = new Date();
assertTrue(typeof nodots == 'undefined');
goog.exportSymbol('nodots', date);
assertEquals(date, nodots); // globals are accessible from within a module.
nodots = undefined;
},
//=== tests for Require logic ===
testLegacyRequire: function() {
// goog.Timer is a legacy module loaded above
assertNotUndefined('goog.Timer should be available', goog.Timer);
// Verify that a legacy module can be aliases with goog.require
assertTrue('Timer should be the goog.Timer namespace object',
goog.Timer === Timer);
// and its dependencies
assertNotUndefined(
'goog.events.EventTarget should be available',
/** @suppress {missingRequire} */ goog.events.EventTarget);
},
testRequireModule: function() {
assertEquals('module failed to export legacy namespace: ' +
'goog.test_module', testModule, goog.test_module);
assertUndefined('module failed to protect global namespace: ' +
'goog.test_module_dep', goog.test_module_dep);
// The test module is available under its alias
assertNotUndefined('testModule is loaded', testModule);
assertTrue('module failed: testModule', goog.isFunction(testModule));
}
};
exports.testThisInModule = (function() {
assertEquals(this, goog.global);
}).bind(this);
@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2006 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Closure Unit Tests - goog.*</title>
<script>
var CLOSURE_DEFINES = {
'SOME_DEFINE': 456,
'ns.SOME_DEFINE': 456
};
</script>
<script src="base.js"></script>
</head>
<body>
<div id="elem">
<span>One</span>
<span id="text">Two</span>
<span>Three</span>
</div>
<iframe name="f1" id="f1" src=""></iframe>
<iframe name="f2" id="f2" src=""></iframe>
<script>
goog.require('goog.baseTest');
</script>
</body>
</html>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,92 @@
// 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');
var vm = require('vm');
/**
* 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) {
vm.Script.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;
@@ -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);
@@ -0,0 +1,472 @@
// 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 {{hex: string, type: string}} 'hex' is a string containing
* a hex representation of the color, and 'type' 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<number>} array containing [r, g, b, a].
* r, g, b are ints between 0
* and 255, and a is a value between 0 and 1.
*/
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. The alpha channel value is rounded to 3 decimal places to make
* sure the produced string is not too long.
* @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) {
var roundedRgba = rgba.slice(0);
roundedRgba[3] = Math.round(rgba[3] * 1000) / 1000;
return 'rgba(' + roundedRgba.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<number>} 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]);
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2006 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.color.alpha
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.color.alphaTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,321 @@
// 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.
goog.provide('goog.color.alphaTest');
goog.setTestOnly('goog.color.alphaTest');
goog.require('goog.array');
goog.require('goog.color');
goog.require('goog.color.alpha');
goog.require('goog.testing.jsunit');
function testIsValidAlphaHexColor() {
var goodAlphaHexColors = ['#ffffffff', '#ff781259', '#01234567', '#Ff003DaB',
'#3CAF', '#abcdefab', '#3CAB'];
var badAlphaHexColors = ['#xxxxxxxx', '88990077', 'not_color', '#123456789',
'fffffgfg'];
for (var i = 0; i < goodAlphaHexColors.length; i++) {
assertTrue(goodAlphaHexColors[i],
goog.color.alpha.isValidAlphaHexColor_(goodAlphaHexColors[i]));
}
for (var i = 0; i < badAlphaHexColors.length; i++) {
assertFalse(badAlphaHexColors[i],
goog.color.alpha.isValidAlphaHexColor_(badAlphaHexColors[i]));
}
}
function testIsValidRgbaColor() {
var goodRgbaColors = ['rgba(255, 0, 0, 1)', 'rgba(255,127,0,1)',
'rgba(0,0,255,0.5)', '(255, 26, 75, 0.2)',
'RGBA(0, 55, 0, 0.6)', 'rgba(0, 200, 0, 0.123456789)'];
var badRgbaColors = ['(255, 0, 0)', '(2555,0,0, 0)', '(1,2,3,4,5)',
'rgba(1,20,)', 'RGBA(20,20,20,)', 'RGBA',
'rgba(255, 0, 0, 1.1)'];
for (var i = 0; i < goodRgbaColors.length; i++) {
assertEquals(goodRgbaColors[i], 4,
goog.color.alpha.isValidRgbaColor_(goodRgbaColors[i]).length);
}
for (var i = 0; i < badRgbaColors.length; i++) {
assertEquals(badRgbaColors[i], 0,
goog.color.alpha.isValidRgbaColor_(badRgbaColors[i]).length);
}
}
function testIsValidHslaColor() {
var goodHslaColors = ['hsla(120, 0%, 0%, 1)', 'hsla(360,20%,0%,1)',
'hsla(0,0%,50%,0.5)', 'HSLA(0, 55%, 0%, 0.6)',
'hsla(0, 85%, 0%, 0.123456789)'];
var badHslaColors = ['(255, 0, 0, 0)', 'hsla(2555,0,0, 0)', 'hsla(1,2,3,4,5)',
'hsla(1,20,)', 'HSLA(20,20,20,)',
'hsla(255, 0, 0, 1.1)', 'HSLA'];
for (var i = 0; i < goodHslaColors.length; i++) {
assertEquals(goodHslaColors[i], 4,
goog.color.alpha.isValidHslaColor_(goodHslaColors[i]).length);
}
for (var i = 0; i < badHslaColors.length; i++) {
assertEquals(badHslaColors[i], 0,
goog.color.alpha.isValidHslaColor_(badHslaColors[i]).length);
}
}
function testParse() {
var colors = ['rgba(15, 250, 77, 0.5)', '(127, 127, 127, 0.8)', '#ffeeddaa',
'12345678', 'hsla(160, 50%, 90%, 0.2)'];
var parsed = goog.array.map(colors, goog.color.alpha.parse);
assertEquals('rgba', parsed[0].type);
assertEquals(goog.color.alpha.rgbaToHex(15, 250, 77, 0.5), parsed[0].hex);
assertEquals('rgba', parsed[1].type);
assertEquals(goog.color.alpha.rgbaToHex(127, 127, 127, 0.8), parsed[1].hex);
assertEquals('hex', parsed[2].type);
assertEquals('#ffeeddaa', parsed[2].hex);
assertEquals('hex', parsed[3].type);
assertEquals('#12345678', parsed[3].hex);
assertEquals('hsla', parsed[4].type);
assertEquals('#d9f2ea33', parsed[4].hex);
var badColors = ['rgb(01, 1, 23)', '(256, 256, 256)', '#ffeeddaa'];
for (var i = 0; i < badColors.length; i++) {
var e = assertThrows(badColors[i] + ' is not a valid color string',
goog.partial(goog.color.parse, badColors[i]));
assertContains('Error processing ' + badColors[i],
'is not a valid color string', e.message);
}
}
function testHexToRgba() {
var testColors = [['#B0FF2D66', [176, 255, 45, 0.4]],
['#b26e5fcc', [178, 110, 95, 0.8]],
['#66f3', [102, 102, 255, 0.2]]];
for (var i = 0; i < testColors.length; i++) {
var r = goog.color.alpha.hexToRgba(testColors[i][0]);
var t = testColors[i][1];
assertEquals('Red channel should match.', t[0], r[0]);
assertEquals('Green channel should match.', t[1], r[1]);
assertEquals('Blue channel should match.', t[2], r[2]);
assertEquals('Alpha channel should match.', t[3], r[3]);
}
var badColors = ['', '#g00', 'some words'];
for (var i = 0; i < badColors.length; i++) {
var e = assertThrows(
goog.partial(goog.color.alpha.hexToRgba, badColors[i]));
assertEquals("'" + badColors[i] + "' is not a valid alpha hex color",
e.message);
}
}
function testHexToRgbaStyle() {
assertEquals('rgba(255,0,0,1)',
goog.color.alpha.hexToRgbaStyle('#ff0000ff'));
assertEquals('rgba(206,206,206,0.8)',
goog.color.alpha.hexToRgbaStyle('#cecececc'));
assertEquals('rgba(51,204,170,0.2)',
goog.color.alpha.hexToRgbaStyle('#3CA3'));
assertEquals('rgba(1,2,3,0.016)',
goog.color.alpha.hexToRgbaStyle('#01020304'));
assertEquals('rgba(255,255,0,0.333)',
goog.color.alpha.hexToRgbaStyle('#FFFF0055'));
var badHexColors = ['#12345', null, undefined, '#.1234567890'];
for (var i = 0; i < badHexColors.length; ++i) {
var e = assertThrows(badHexColors[i] + ' is an invalid hex color',
goog.partial(goog.color.alpha.hexToRgbaStyle, badHexColors[i]));
assertEquals("'" + badHexColors[i] + "' is not a valid alpha hex color",
e.message);
}
}
function testRgbaToHex() {
assertEquals('#af13ffff', goog.color.alpha.rgbaToHex(175, 19, 255, 1));
assertEquals('#357cf099', goog.color.alpha.rgbaToHex(53, 124, 240, 0.6));
var badRgba = [[-1, -1, -1, -1], [0, 0, 0, 2], ['a', 'b', 'c', 'd'],
[undefined, 5, 5, 5]];
for (var i = 0; i < badRgba.length; ++i) {
var e = assertThrows(badRgba[i] + ' is not a valid rgba color',
goog.partial(goog.color.alpha.rgbaArrayToHex, badRgba[i]));
assertContains('is not a valid RGBA color', e.message);
}
}
function testRgbaToRgbaStyle() {
var testColors = [[[175, 19, 255, 1], 'rgba(175,19,255,1)'],
[[53, 124, 240, .6], 'rgba(53,124,240,0.6)'],
[[10, 20, 30, .1234567], 'rgba(10,20,30,0.123)'],
[[20, 30, 40, 1 / 3], 'rgba(20,30,40,0.333)']];
for (var i = 0; i < testColors.length; ++i) {
var r = goog.color.alpha.rgbaToRgbaStyle(testColors[i][0][0],
testColors[i][0][1],
testColors[i][0][2],
testColors[i][0][3]);
assertEquals(testColors[i][1], r);
}
var badColors = [[0, 0, 0, 2]];
for (var i = 0; i < badColors.length; ++i) {
var e = assertThrows(goog.partial(goog.color.alpha.rgbaToRgbaStyle,
badColors[i][0], badColors[i][1], badColors[i][2], badColors[i][3]));
assertContains('is not a valid RGBA color', e.message);
}
// Loop through all bad color values and ensure they fail in each channel.
var badValues = [-1, 300, 'a', undefined, null, NaN];
var color = [0, 0, 0, 0];
for (var i = 0; i < badValues.length; ++i) {
for (var channel = 0; channel < color.length; ++channel) {
color[channel] = badValues[i];
var e = assertThrows(color + ' is not a valid rgba color',
goog.partial(goog.color.alpha.rgbaToRgbaStyle, color));
assertContains('is not a valid RGBA color', e.message);
color[channel] = 0;
}
}
}
function testRgbaArrayToRgbaStyle() {
var testColors = [[[175, 19, 255, 1], 'rgba(175,19,255,1)'],
[[53, 124, 240, .6], 'rgba(53,124,240,0.6)']];
for (var i = 0; i < testColors.length; ++i) {
var r = goog.color.alpha.rgbaArrayToRgbaStyle(testColors[i][0]);
assertEquals(testColors[i][1], r);
}
var badColors = [[0, 0, 0, 2]];
for (var i = 0; i < badColors.length; ++i) {
var e = assertThrows(goog.partial(goog.color.alpha.rgbaArrayToRgbaStyle,
badColors[i]));
assertContains('is not a valid RGBA color', e.message);
}
// Loop through all bad color values and ensure they fail in each channel.
var badValues = [-1, 300, 'a', undefined, null, NaN];
var color = [0, 0, 0, 0];
for (var i = 0; i < badValues.length; ++i) {
for (var channel = 0; channel < color.length; ++channel) {
color[channel] = badValues[i];
var e = assertThrows(color + ' is not a valid rgba color',
goog.partial(goog.color.alpha.rgbaToRgbaStyle, color));
assertContains('is not a valid RGBA color', e.message);
color[channel] = 0;
}
}
}
function testRgbaArrayToHsla() {
var opaqueBlueRgb = [0, 0, 255, 1];
var opaqueBlueHsl = goog.color.alpha.rgbaArrayToHsla(opaqueBlueRgb);
assertArrayEquals('Conversion from RGBA to HSLA should be as expected',
[240, 1, 0.5, 1], opaqueBlueHsl);
var nearlyOpaqueYellowRgb = [255, 190, 0, 0.7];
var nearlyOpaqueYellowHsl =
goog.color.alpha.rgbaArrayToHsla(nearlyOpaqueYellowRgb);
assertArrayEquals('Conversion from RGBA to HSLA should be as expected',
[45, 1, 0.5, 0.7], nearlyOpaqueYellowHsl);
var transparentPurpleRgb = [180, 0, 255, 0];
var transparentPurpleHsl =
goog.color.alpha.rgbaArrayToHsla(transparentPurpleRgb);
assertArrayEquals('Conversion from RGBA to HSLA should be as expected',
[282, 1, 0.5, 0], transparentPurpleHsl);
}
function testNormalizeAlphaHex() {
var compactColor = '#abcd';
var normalizedCompactColor =
goog.color.alpha.normalizeAlphaHex_(compactColor);
assertEquals('The color should have been normalized to the right length',
'#aabbccdd', normalizedCompactColor);
var uppercaseColor = '#ABCDEF01';
var normalizedUppercaseColor =
goog.color.alpha.normalizeAlphaHex_(uppercaseColor);
assertEquals('The color should have been normalized to lowercase',
'#abcdef01', normalizedUppercaseColor);
}
function testHsvaArrayToHex() {
var opaqueSkyBlueHsv = [190, 1, 255, 1];
var opaqueSkyBlueHex = goog.color.alpha.hsvaArrayToHex(opaqueSkyBlueHsv);
assertEquals('The HSVA array should have been properly converted to hex',
'#00d4ffff', opaqueSkyBlueHex);
var halfTransparentPinkHsv = [300, 1, 255, 0.5];
var halfTransparentPinkHex =
goog.color.alpha.hsvaArrayToHex(halfTransparentPinkHsv);
assertEquals('The HSVA array should have been properly converted to hex',
'#ff00ff7f', halfTransparentPinkHex);
var transparentDarkTurquoiseHsv = [175, 1, 127, 0.5];
var transparentDarkTurquoiseHex =
goog.color.alpha.hsvaArrayToHex(transparentDarkTurquoiseHsv);
assertEquals('The HSVA array should have been properly converted to hex',
'#007f747f', transparentDarkTurquoiseHex);
}
function testExtractHexColor() {
var opaqueRed = '#ff0000ff';
var red = goog.color.alpha.extractHexColor(opaqueRed);
assertEquals('The hex part of the color should have been extracted correctly',
'#ff0000', red);
var halfOpaqueDarkGreenCompact = '#0507';
var darkGreen =
goog.color.alpha.extractHexColor(halfOpaqueDarkGreenCompact);
assertEquals('The hex part of the color should have been extracted correctly',
'#005500', darkGreen);
}
function testExtractAlpha() {
var colors = ['#ff0000ff', '#0507', '#ff000005'];
var expectedOpacities = ['ff', '77', '05'];
for (var i = 0; i < colors.length; i++) {
var opacity = goog.color.alpha.extractAlpha(colors[i]);
assertEquals('The alpha transparency should have been extracted correctly',
expectedOpacities[i], opacity);
}
}
function testHslaArrayToRgbaStyle() {
assertEquals('rgba(102,255,102,0.5)',
goog.color.alpha.hslaArrayToRgbaStyle([120, 100, 70, 0.5]));
assertEquals('rgba(28,23,23,0.9)',
goog.color.alpha.hslaArrayToRgbaStyle([0, 10, 10, 0.9]));
}
function testRgbaStyleParsableResult() {
var testColors = [[175, 19, 255, 1],
[53, 124, 240, .6],
[20, 30, 40, 0.3333333],
[255, 255, 255, 0.7071067811865476]];
for (var i = 0, testColor; testColor = testColors[i]; i++) {
var rgbaStyle = goog.color.alpha.rgbaStyle_(testColor);
var parsedColor = goog.color.alpha.hexToRgba(
goog.color.alpha.parse(rgbaStyle).hex);
assertEquals(testColor[0], parsedColor[0]);
assertEquals(testColor[1], parsedColor[1]);
assertEquals(testColor[2], parsedColor[2]);
// Parsing keeps a 1/255 accuracy on the alpha channel.
assertRoughlyEquals(testColor[3], parsedColor[3], 0.005);
}
}
@@ -0,0 +1,776 @@
// 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.provide('goog.color.Hsl');
goog.provide('goog.color.Hsv');
goog.provide('goog.color.Rgb');
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 {{hex: string, type: string}} 'hex' is a string containing a hex
* representation of the color, 'type' 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]);
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2006 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.color
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.colorTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,667 @@
// 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.
goog.provide('goog.colorTest');
goog.setTestOnly('goog.colorTest');
goog.require('goog.array');
goog.require('goog.color');
goog.require('goog.color.names');
goog.require('goog.testing.jsunit');
function testIsValidColor() {
var goodColors = ['#ffffff', '#ff7812', '#012345', '#Ff003D', '#3CA',
'(255, 26, 75)', 'RGB(2, 3, 4)', '(0,0,0)', 'white',
'blue'];
var badColors = ['#xxxxxx', '8899000', 'not_color', '#1234567', 'fffffg',
'(2555,0,0)', '(1,2,3,4)', 'rgb(1,20,)', 'RGB(20,20,20,)',
'omgwtfbbq'];
for (var i = 0; i < goodColors.length; i++) {
assertTrue(goodColors[i], goog.color.isValidColor(goodColors[i]));
}
for (var i = 0; i < badColors.length; i++) {
assertFalse(badColors[i], goog.color.isValidColor(badColors[i]));
}
}
function testIsValidHexColor() {
var goodHexColors = ['#ffffff', '#ff7812', '#012345', '#Ff003D', '#3CA'];
var badHexColors = ['#xxxxxx', '889900', 'not_color', '#1234567', 'fffffg'];
for (var i = 0; i < goodHexColors.length; i++) {
assertTrue(goodHexColors[i], goog.color.isValidHexColor_(goodHexColors[i]));
}
for (var i = 0; i < badHexColors.length; i++) {
assertFalse(badHexColors[i], goog.color.isValidHexColor_(badHexColors[i]));
}
}
function testIsValidRgbColor() {
var goodRgbColors = ['(255, 26, 75)', 'RGB(2, 3, 4)', '(0,0,0)',
'rgb(255,255,255)'];
var badRgbColors = ['(2555,0,0)', '(1,2,3,4)', 'rgb(1,20,)',
'RGB(20,20,20,)'];
for (var i = 0; i < goodRgbColors.length; i++) {
assertEquals(goodRgbColors[i],
goog.color.isValidRgbColor_(goodRgbColors[i]).length, 3);
}
for (var i = 0; i < badRgbColors.length; i++) {
assertEquals(badRgbColors[i],
goog.color.isValidRgbColor_(badRgbColors[i]).length, 0);
}
}
function testParse() {
var colors = ['rgb(15, 250, 77)', '(127, 127, 127)', '#ffeedd', '123456',
'magenta'];
var parsed = goog.array.map(colors, goog.color.parse);
assertEquals('rgb', parsed[0].type);
assertEquals(goog.color.rgbToHex(15, 250, 77), parsed[0].hex);
assertEquals('rgb', parsed[1].type);
assertEquals(goog.color.rgbToHex(127, 127, 127), parsed[1].hex);
assertEquals('hex', parsed[2].type);
assertEquals('#ffeedd', parsed[2].hex);
assertEquals('hex', parsed[3].type);
assertEquals('#123456', parsed[3].hex);
assertEquals('named', parsed[4].type);
assertEquals('#ff00ff', parsed[4].hex);
var badColors = ['rgb(01, 1, 23)', '(256, 256, 256)', '#ffeeddaa'];
for (var i = 0; i < badColors.length; i++) {
var e = assertThrows(goog.partial(goog.color.parse, badColors[i]));
assertContains('is not a valid color string', e.message);
}
}
function testHexToRgb() {
var testColors = [['#B0FF2D', [176, 255, 45]],
['#b26e5f', [178, 110, 95]],
['#66f', [102, 102, 255]]];
for (var i = 0; i < testColors.length; i++) {
var r = goog.color.hexToRgb(testColors[i][0]);
var t = testColors[i][1];
assertEquals('Red channel should match.', t[0], r[0]);
assertEquals('Green channel should match.', t[1], r[1]);
assertEquals('Blue channel should match.', t[2], r[2]);
}
var badColors = ['', '#g00', 'some words'];
for (var i = 0; i < badColors.length; i++) {
var e = assertThrows(goog.partial(goog.color.hexToRgb, badColors[i]));
assertEquals("'" + badColors[i] + "' is not a valid hex color", e.message);
}
}
function testHexToRgbStyle() {
assertEquals('rgb(255,0,0)', goog.color.hexToRgbStyle(goog.color.names.red));
assertEquals('rgb(206,206,206)', goog.color.hexToRgbStyle('#cecece'));
assertEquals('rgb(51,204,170)', goog.color.hexToRgbStyle('#3CA'));
var badHexColors = ['#1234', null, undefined, '#.1234567890'];
for (var i = 0; i < badHexColors.length; ++i) {
var badHexColor = badHexColors[i];
var e = assertThrows(goog.partial(goog.color.hexToRgbStyle, badHexColor));
assertEquals("'" + badHexColor + "' is not a valid hex color", e.message);
}
}
function testRgbToHex() {
assertEquals(goog.color.names.red, goog.color.rgbToHex(255, 0, 0));
assertEquals('#af13ff', goog.color.rgbToHex(175, 19, 255));
var badRgb = [[-1, -1, -1], [256, 0, 0], ['a', 'b', 'c'], [undefined, 5, 5]];
for (var i = 0; i < badRgb.length; ++i) {
var e = assertThrows(goog.partial(goog.color.rgbArrayToHex, badRgb[i]));
assertContains('is not a valid RGB color', e.message);
}
}
function testRgbToHsl() {
var rgb = [255, 171, 32];
var hsl = goog.color.rgbArrayToHsl(rgb);
assertEquals(37, hsl[0]);
assertTrue(1.0 - hsl[1] < 0.01);
assertTrue(hsl[2] - .5625 < 0.01);
}
function testHslToRgb() {
var hsl = [60, 0.5, 0.1];
var rgb = goog.color.hslArrayToRgb(hsl);
assertEquals(38, rgb[0]);
assertEquals(38, rgb[1]);
assertEquals(13, rgb[2]);
}
// Tests accuracy of HSL to RGB conversion
function testHSLBidiToRGB() {
var DELTA = 1;
var color = [[100, 56, 200],
[255, 0, 0],
[0, 0, 255],
[0, 255, 0],
[255, 255, 255],
[0, 0, 0]];
for (var i = 0; i < color.length; i++) {
colorConversionTestHelper(
function(color) {
return goog.color.rgbToHsl(color[0], color[1], color[2]);
},
function(color) {
return goog.color.hslToRgb(color[0], color[1], color[2]);
},
color[i], DELTA);
colorConversionTestHelper(
function(color) { return goog.color.rgbArrayToHsl(color); },
function(color) { return goog.color.hslArrayToRgb(color); },
color[i], DELTA);
}
}
// Tests HSV to RGB conversion
function testHSVToRGB() {
var DELTA = 1;
var color = [[100, 56, 200],
[255, 0, 0],
[0, 0, 255],
[0, 255, 0],
[255, 255, 255],
[0, 0, 0]];
for (var i = 0; i < color.length; i++) {
colorConversionTestHelper(
function(color) {
return goog.color.rgbToHsv(color[0], color[1], color[2]);
},
function(color) {
return goog.color.hsvToRgb(color[0], color[1], color[2]);
},
color[i], DELTA);
colorConversionTestHelper(
function(color) { return goog.color.rgbArrayToHsv(color); },
function(color) { return goog.color.hsvArrayToRgb(color); },
color[i], DELTA);
}
}
// Tests that HSV space is (0-360) for hue
function testHSVSpecRangeIsCorrect() {
var color = [0, 0, 255]; // Blue is in the middle of hue range
var hsv = goog.color.rgbToHsv(color[0], color[1], color[2]);
assertTrue("H in HSV space looks like it's not 0-360", hsv[0] > 1);
}
// Tests conversion between HSL and Hex
function testHslToHex() {
var DELTA = 1;
var color = [[0, 0, 0],
[20, 0.5, 0.5],
[0, 0, 1],
[255, .45, .76]];
for (var i = 0; i < color.length; i++) {
colorConversionTestHelper(
function(hsl) { return goog.color.hslToHex(hsl[0], hsl[1], hsl[2]); },
function(hex) { return goog.color.hexToHsl(hex); },
color[i], DELTA);
colorConversionTestHelper(
function(hsl) { return goog.color.hslArrayToHex(hsl); },
function(hex) { return goog.color.hexToHsl(hex); },
color[i], DELTA);
}
}
// Tests conversion between HSV and Hex
function testHsvToHex() {
var DELTA = 1;
var color = [[0, 0, 0],
[.5, 0.5, 155],
[0, 0, 255],
[.7, .45, 21]];
for (var i = 0; i < color.length; i++) {
colorConversionTestHelper(
function(hsl) { return goog.color.hsvToHex(hsl[0], hsl[1], hsl[2]); },
function(hex) { return goog.color.hexToHsv(hex); },
color[i], DELTA);
colorConversionTestHelper(
function(hsl) { return goog.color.hsvArrayToHex(hsl); },
function(hex) { return goog.color.hexToHsv(hex); },
color[i], DELTA);
}
}
/**
* This helper method compares two RGB colors, checking that each color
* component is the same.
* @param {Array<number>} rgb1 Color represented by a 3-element array with
* red, green, and blue values respectively, in the range [0, 255].
* @param {Array<number>} rgb2 Color represented by a 3-element array with
* red, green, and blue values respectively, in the range [0, 255].
* @return {boolean} True if the colors are the same, false otherwise.
*/
function rgbColorsAreEqual(rgb1, rgb2) {
return (rgb1[0] == rgb2[0] &&
rgb1[1] == rgb2[1] &&
rgb1[2] == rgb2[2]);
}
/**
* This method runs unit tests against goog.color.blend(). Test cases include:
* blending arbitrary colors with factors of 0 and 1, blending the same colors
* using arbitrary factors, blending different colors of varying factors,
* and blending colors using factors outside the expected range.
*/
function testColorBlend() {
// Define some RGB colors for our tests.
var black = [0, 0, 0];
var blue = [0, 0, 255];
var gray = [128, 128, 128];
var green = [0, 255, 0];
var purple = [128, 0, 128];
var red = [255, 0, 0];
var yellow = [255, 255, 0];
var white = [255, 255, 255];
// Blend arbitrary colors, using 0 and 1 for factors. Using 0 should return
// the first color, while using 1 should return the second color.
var redWithNoGreen = goog.color.blend(red, green, 1);
assertTrue('red + 0 * green = red',
rgbColorsAreEqual(red, redWithNoGreen));
var whiteWithAllBlue = goog.color.blend(white, blue, 0);
assertTrue('white + 1 * blue = blue',
rgbColorsAreEqual(blue, whiteWithAllBlue));
// Blend the same colors using arbitrary factors. This should return the
// same colors.
var greenWithGreen = goog.color.blend(green, green, .25);
assertTrue('green + .25 * green = green',
rgbColorsAreEqual(green, greenWithGreen));
// Blend different colors using varying factors.
var blackWithWhite = goog.color.blend(black, white, .5);
assertTrue('black + .5 * white = gray',
rgbColorsAreEqual(gray, blackWithWhite));
var redAndBlue = goog.color.blend(red, blue, .5);
assertTrue('red + .5 * blue = purple',
rgbColorsAreEqual(purple, redAndBlue));
var lightGreen = goog.color.blend(green, white, .75);
assertTrue('green + .25 * white = a lighter shade of white',
lightGreen[0] > 0 &&
lightGreen[1] == 255 &&
lightGreen[2] > 0);
// Blend arbitrary colors using factors outside the expected range.
var noGreenAllPurple = goog.color.blend(green, purple, -0.5);
assertTrue('green * -0.5 + purple = purple.',
rgbColorsAreEqual(purple, noGreenAllPurple));
var allBlueNoYellow = goog.color.blend(blue, yellow, 1.37);
assertTrue('blue * 1.37 + yellow = blue.',
rgbColorsAreEqual(blue, allBlueNoYellow));
}
/**
* This method runs unit tests against goog.color.darken(). Test cases
* include darkening black with arbitrary factors, edge cases (using 0 and 1),
* darkening colors using various factors, and darkening colors using factors
* outside the expected range.
*/
function testColorDarken() {
// Define some RGB colors
var black = [0, 0, 0];
var green = [0, 255, 0];
var darkGray = [68, 68, 68];
var olive = [128, 128, 0];
var purple = [128, 0, 128];
var white = [255, 255, 255];
// Darken black by an arbitrary factor, which should still return black.
var darkBlack = goog.color.darken(black, .63);
assertTrue('black darkened by .63 is still black.',
rgbColorsAreEqual(black, darkBlack));
// Call darken() with edge-case factors (0 and 1).
var greenNotDarkened = goog.color.darken(green, 0);
assertTrue('green darkened by 0 is still green.',
rgbColorsAreEqual(green, greenNotDarkened));
var whiteFullyDarkened = goog.color.darken(white, 1);
assertTrue('white darkened by 1 is black.',
rgbColorsAreEqual(black, whiteFullyDarkened));
// Call darken() with various colors and factors. The result should be
// a color with less luminance.
var whiteHsl = goog.color.rgbToHsl(white[0],
white[1],
white[2]);
var whiteDarkened = goog.color.darken(white, .43);
var whiteDarkenedHsl = goog.color.rgbToHsl(whiteDarkened[0],
whiteDarkened[1],
whiteDarkened[2]);
assertTrue('White that\'s darkened has less luminance than white.',
whiteDarkenedHsl[2] < whiteHsl[2]);
var purpleHsl = goog.color.rgbToHsl(purple[0],
purple[1],
purple[2]);
var purpleDarkened = goog.color.darken(purple, .1);
var purpleDarkenedHsl = goog.color.rgbToHsl(purpleDarkened[0],
purpleDarkened[1],
purpleDarkened[2]);
assertTrue('Purple that\'s darkened has less luminance than purple.',
purpleDarkenedHsl[2] < purpleHsl[2]);
// Call darken() with factors outside the expected range.
var darkGrayTurnedBlack = goog.color.darken(darkGray, 2.1);
assertTrue('Darkening dark gray by 2.1 returns black.',
rgbColorsAreEqual(black, darkGrayTurnedBlack));
var whiteNotDarkened = goog.color.darken(white, -0.62);
assertTrue('Darkening white by -0.62 returns white.',
rgbColorsAreEqual(white, whiteNotDarkened));
}
/**
* This method runs unit tests against goog.color.lighten(). Test cases
* include lightening white with arbitrary factors, edge cases (using 0 and 1),
* lightening colors using various factors, and lightening colors using factors
* outside the expected range.
*/
function testColorLighten() {
// Define some RGB colors
var black = [0, 0, 0];
var brown = [165, 42, 42];
var navy = [0, 0, 128];
var orange = [255, 165, 0];
var white = [255, 255, 255];
// Lighten white by an arbitrary factor, which should still return white.
var lightWhite = goog.color.lighten(white, .41);
assertTrue('white lightened by .41 is still white.',
rgbColorsAreEqual(white, lightWhite));
// Call lighten() with edge-case factors(0 and 1).
var navyNotLightened = goog.color.lighten(navy, 0);
assertTrue('navy lightened by 0 is still navy.',
rgbColorsAreEqual(navy, navyNotLightened));
var orangeFullyLightened = goog.color.lighten(orange, 1);
assertTrue('orange lightened by 1 is white.',
rgbColorsAreEqual(white, orangeFullyLightened));
// Call lighten() with various colors and factors. The result should be
// a color with greater luminance.
var blackHsl = goog.color.rgbToHsl(black[0],
black[1],
black[2]);
var blackLightened = goog.color.lighten(black, .33);
var blackLightenedHsl = goog.color.rgbToHsl(blackLightened[0],
blackLightened[1],
blackLightened[2]);
assertTrue('Black that\'s lightened has more luminance than black.',
blackLightenedHsl[2] >= blackHsl[2]);
var orangeHsl = goog.color.rgbToHsl(orange[0],
orange[1],
orange[2]);
var orangeLightened = goog.color.lighten(orange, .91);
var orangeLightenedHsl = goog.color.rgbToHsl(orangeLightened[0],
orangeLightened[1],
orangeLightened[2]);
assertTrue('Orange that\'s lightened has more luminance than orange.',
orangeLightenedHsl[2] >= orangeHsl[2]);
// Call lighten() with factors outside the expected range.
var navyTurnedWhite = goog.color.lighten(navy, 1.01);
assertTrue('Lightening navy by 1.01 returns white.',
rgbColorsAreEqual(white, navyTurnedWhite));
var brownNotLightened = goog.color.lighten(brown, -0.0000001);
assertTrue('Lightening brown by -0.0000001 returns brown.',
rgbColorsAreEqual(brown, brownNotLightened));
}
/**
* This method runs unit tests against goog.color.hslDistance().
*/
function testHslDistance() {
// Define some HSL colors
var aliceBlueHsl = goog.color.rgbToHsl(240, 248, 255);
var blackHsl = goog.color.rgbToHsl(0, 0, 0);
var ghostWhiteHsl = goog.color.rgbToHsl(248, 248, 255);
var navyHsl = goog.color.rgbToHsl(0, 0, 128);
var redHsl = goog.color.rgbToHsl(255, 0, 0);
var whiteHsl = goog.color.rgbToHsl(255, 255, 255);
// The distance between the same colors should be 0.
assertTrue('There is no HSL distance between white and white.',
goog.color.hslDistance(whiteHsl, whiteHsl) == 0);
assertTrue('There is no HSL distance between red and red.',
goog.color.hslDistance(redHsl, redHsl) == 0);
// The distance between various colors should be within certain thresholds.
var hslDistance = goog.color.hslDistance(whiteHsl, ghostWhiteHsl);
assertTrue('The HSL distance between white and ghost white is > 0.',
hslDistance > 0);
assertTrue('The HSL distance between white and ghost white is <= 0.02.',
hslDistance <= 0.02);
hslDistance = goog.color.hslDistance(whiteHsl, redHsl);
assertTrue('The HSL distance betwen white and red is > 0.02.',
hslDistance > 0.02);
hslDistance = goog.color.hslDistance(navyHsl, aliceBlueHsl);
assertTrue('The HSL distance between navy and alice blue is > 0.02.',
hslDistance > 0.02);
hslDistance = goog.color.hslDistance(blackHsl, whiteHsl);
assertTrue('The HSL distance between white and black is 1.',
hslDistance == 1);
}
/**
* This method runs unit tests against goog.color.yiqBrightness_().
*/
function testYiqBrightness() {
var white = [255, 255, 255];
var black = [0, 0, 0];
var coral = [255, 127, 80];
var lightgreen = [144, 238, 144];
var whiteBrightness = goog.color.yiqBrightness_(white);
var blackBrightness = goog.color.yiqBrightness_(black);
var coralBrightness = goog.color.yiqBrightness_(coral);
var lightgreenBrightness = goog.color.yiqBrightness_(lightgreen);
// brightness should be a number
assertTrue('White brightness is a number.',
typeof whiteBrightness == 'number');
assertTrue('Coral brightness is a number.',
typeof coralBrightness == 'number');
// brightness for known colors should match known values
assertEquals('White brightness is 255', whiteBrightness, 255);
assertEquals('Black brightness is 0', blackBrightness, 0);
assertEquals('Coral brightness is 160', coralBrightness, 160);
assertEquals('Lightgreen brightness is 199', lightgreenBrightness, 199);
}
/**
* This method runs unit tests against goog.color.yiqBrightnessDiff_().
*/
function testYiqBrightnessDiff() {
var colors = {
'deeppink': [255, 20, 147],
'indigo': [75, 0, 130],
'saddlebrown': [139, 69, 19]
};
var diffs = new Object();
for (name1 in colors) {
for (name2 in colors) {
diffs[name1 + '-' + name2] =
goog.color.yiqBrightnessDiff_(colors[name1], colors[name2]);
}
}
for (pair in diffs) {
// each brightness diff should be a number
assertTrue(pair + ' diff is a number.', typeof diffs[pair] == 'number');
// each brightness diff should be greater than or equal to 0
assertTrue(pair + ' diff is greater than or equal to 0.', diffs[pair] >= 0);
}
// brightness diff for same-color pairs should be 0
assertEquals('deeppink-deeppink is 0.', diffs['deeppink-deeppink'], 0);
assertEquals('indigo-indigo is 0.', diffs['indigo-indigo'], 0);
// brightness diff for known pairs should match known values
assertEquals('deeppink-indigo is 68.', diffs['deeppink-indigo'], 68);
assertEquals('saddlebrown-deeppink is 21.',
diffs['saddlebrown-deeppink'], 21);
// reversed pairs should have equal values
assertEquals('indigo-saddlebrown is 47.', diffs['indigo-saddlebrown'], 47);
assertEquals('saddlebrown-indigo is also 47.',
diffs['saddlebrown-indigo'], 47);
}
/**
* This method runs unit tests against goog.color.colorDiff_().
*/
function testColorDiff() {
var colors = {
'mediumblue': [0, 0, 205],
'oldlace': [253, 245, 230],
'orchid': [218, 112, 214]
};
var diffs = new Object();
for (name1 in colors) {
for (name2 in colors) {
diffs[name1 + '-' + name2] =
goog.color.colorDiff_(colors[name1], colors[name2]);
}
}
for (pair in diffs) {
// each color diff should be a number
assertTrue(pair + ' diff is a number.', typeof diffs[pair] == 'number');
// each color diff should be greater than or equal to 0
assertTrue(pair + ' diff is greater than or equal to 0.', diffs[pair] >= 0);
}
// color diff for same-color pairs should be 0
assertEquals('mediumblue-mediumblue is 0.',
diffs['mediumblue-mediumblue'], 0);
assertEquals('oldlace-oldlace is 0.', diffs['oldlace-oldlace'], 0);
// color diff for known pairs should match known values
assertEquals('mediumblue-oldlace is 523.', diffs['mediumblue-oldlace'], 523);
assertEquals('oldlace-orchid is 184.', diffs['oldlace-orchid'], 184);
// reversed pairs should have equal values
assertEquals('orchid-mediumblue is 339.', diffs['orchid-mediumblue'], 339);
assertEquals('mediumblue-orchid is also 339.',
diffs['mediumblue-orchid'], 339);
}
/**
* This method runs unit tests against goog.color.highContrast().
*/
function testHighContrast() {
white = [255, 255, 255];
black = [0, 0, 0];
lemonchiffron = [255, 250, 205];
sienna = [160, 82, 45];
var suggestion = goog.color.highContrast(
black, [white, black, sienna, lemonchiffron]);
// should return an array of three numbers
assertTrue('Return value is an array.', typeof suggestion == 'object');
assertTrue('Return value is 3 long.', suggestion.length == 3);
// known color combos should return a known (i.e. human-verified) suggestion
assertArrayEquals('White is best on sienna.',
goog.color.highContrast(
sienna, [white, black, sienna, lemonchiffron]), white);
assertArrayEquals('Black is best on lemonchiffron.',
goog.color.highContrast(
white, [white, black, sienna, lemonchiffron]), black);
}
/**
* Helper function for color conversion functions between two colorspaces.
* @param {Function} funcOne Function that converts from 1st colorspace to 2nd
* @param {Function} funcTwo Function that converts from 2nd colorspace to 2nd
* @param {Array<number>} color The color array passed to funcOne
* @param {number} DELTA Margin of error for each element in color
*/
function colorConversionTestHelper(funcOne, funcTwo, color, DELTA) {
var temp = funcOne(color);
if (!goog.color.isValidHexColor_(temp)) {
assertTrue('First conversion had a NaN: ' + temp, !isNaN(temp[0]));
assertTrue('First conversion had a NaN: ' + temp, !isNaN(temp[1]));
assertTrue('First conversion had a NaN: ' + temp, !isNaN(temp[2]));
}
var back = funcTwo(temp);
if (!goog.color.isValidHexColor_(temp)) {
assertTrue('Second conversion had a NaN: ' + back, !isNaN(back[0]));
assertTrue('Second conversion had a NaN: ' + back, !isNaN(back[1]));
assertTrue('Second conversion had a NaN: ' + back, !isNaN(back[2]));
}
assertColorFuzzyEquals('Color was off', color, back, DELTA);
}
/**
* Checks equivalence between two colors' respective values. Accepts +- delta
* for each pair of values
* @param {string} Str
* @param {Array<number>} expected
* @param {Array<number>} actual
* @param {number} delta Margin of error for each element in color array
*/
function assertColorFuzzyEquals(str, expected, actual, delta) {
assertTrue(str + ' Expected: ' + expected + ' and got: ' + actual +
' w/ delta: ' + delta,
(Math.abs(expected[0] - actual[0]) <= delta) &&
(Math.abs(expected[1] - actual[1]) <= delta) &&
(Math.abs(expected[2] - actual[2]) <= delta));
}
@@ -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
@@ -0,0 +1,24 @@
<!doctype html>
<html>
<!--
Copyright 2012 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<!--
Unit test for goog.crypt.Aes
-->
<head>
<title>
goog.crypt.Aes unit test
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.crypt.AesTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,586 @@
// 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.
goog.provide('goog.crypt.AesTest');
goog.setTestOnly('goog.crypt.AesTest');
goog.require('goog.crypt');
goog.require('goog.crypt.Aes');
goog.require('goog.testing.jsunit');
goog.crypt.Aes.ENABLE_TEST_MODE = true;
/*
* Unit test for goog.crypt.Aes using the test vectors from the spec:
* http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf
*/
var testData = null;
function test128() {
doTest('000102030405060708090a0b0c0d0e0f',
'00112233445566778899aabbccddeeff',
v128,
true /* encrypt */);
}
function test192() {
doTest('000102030405060708090a0b0c0d0e0f1011121314151617',
'00112233445566778899aabbccddeeff',
v192,
true /* encrypt */);
}
function test256() {
doTest('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
'00112233445566778899aabbccddeeff',
v256,
true /* encrypt */);
}
function test128d() {
doTest('000102030405060708090a0b0c0d0e0f',
'69c4e0d86a7b0430d8cdb78070b4c55a',
v128d,
false /* decrypt */);
}
function test192d() {
doTest('000102030405060708090a0b0c0d0e0f1011121314151617',
'dda97ca4864cdfe06eaf70a0ec0d7191',
v192d,
false /* decrypt */);
}
function test256d() {
doTest('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
'8ea2b7ca516745bfeafc49904b496089',
v256d,
false /* decrypt */);
}
function doTest(key, input, values, dir) {
testData = values;
var keyArray = goog.crypt.hexToByteArray(key);
var aes = new goog.crypt.Aes(keyArray);
aes.testKeySchedule_ = onTestKeySchedule;
aes.testStartRound_ = onTestStartRound;
aes.testAfterSubBytes_ = onTestAfterSubBytes;
aes.testAfterShiftRows_ = onTestAfterShiftRows;
aes.testAfterMixColumns_ = onTestAfterMixColumns;
aes.testAfterAddRoundKey_ = onTestAfterAddRoundKey;
var inputArr = goog.crypt.hexToByteArray(input);
var keyArr = goog.crypt.hexToByteArray(key);
var outputArr = [];
var outputArr;
if (dir) {
outputArr = aes.encrypt(inputArr);
} else {
outputArr = aes.decrypt(inputArr);
}
assertEquals('Incorrect output for test ' + testData.name,
testData[testData.length - 1].output,
encodeHex(outputArr));
}
function onTestKeySchedule(roundNum, keySchedule, keyScheduleIndex) {
assertEquals(
'Incorrect key for round ' + roundNum,
testData[roundNum].k_sch, encodeKey(keySchedule, keyScheduleIndex));
}
function onTestStartRound(roundNum, state) {
assertEquals('Incorrect state for test ' + testData.name +
' at start round ' + roundNum,
testData[roundNum].start, encodeState(state));
}
function onTestAfterSubBytes(roundNum, state) {
assertEquals('Incorrect state for test ' + testData.name +
' after sub bytes in round ' + roundNum,
testData[roundNum].s_box, encodeState(state));
}
function onTestAfterShiftRows(roundNum, state) {
assertEquals('Incorrect state for test ' + testData.name +
' after shift rows in round ' + roundNum,
testData[roundNum].s_row, encodeState(state));
}
function onTestAfterMixColumns(roundNum, state) {
assertEquals('Incorrect state for test ' + testData.name +
' after mix columns in round ' + roundNum,
testData[roundNum].m_col, encodeState(state));
}
function onTestAfterAddRoundKey(roundNum, state) {
assertEquals('Incorrect state for test ' + testData.name +
' after adding round key in round ' + roundNum,
testData[roundNum].k_add, encodeState(state));
}
function encodeHex(arr) {
var str = [];
for (var i = 0; i < arr.length; i++) {
str.push(encodeByte(arr[i]));
}
return str.join('');
}
function encodeState(state) {
var s = [];
for (var c = 0; c < 4; c++) {
for (var r = 0; r < 4; r++) {
s.push(encodeByte(state[r][c]));
}
}
return s.join('');
}
function encodeKey(key, round) {
var s = [];
for (var r = round * 4; r < (round * 4 + 4); r++) {
for (var c = 0; c < 4; c++) {
s.push(encodeByte(key[r][c]));
}
}
return s.join('');
}
function encodeByte(val) {
val = Number(val).toString(16);
if (val.length == 1) {
val = '0' + val;
}
return val;
}
var v128 = [];
(function v128_init() {
for (var i = 0; i <= 10; i++) v128[i] = {};
v128.name = '128';
v128[0].input = '00112233445566778899aabbccddeeff';
v128[0].k_sch = '000102030405060708090a0b0c0d0e0f';
v128[1].start = '00102030405060708090a0b0c0d0e0f0';
v128[1].s_box = '63cab7040953d051cd60e0e7ba70e18c';
v128[1].s_row = '6353e08c0960e104cd70b751bacad0e7';
v128[1].m_col = '5f72641557f5bc92f7be3b291db9f91a';
v128[1].k_sch = 'd6aa74fdd2af72fadaa678f1d6ab76fe';
v128[2].start = '89d810e8855ace682d1843d8cb128fe4';
v128[2].s_box = 'a761ca9b97be8b45d8ad1a611fc97369';
v128[2].s_row = 'a7be1a6997ad739bd8c9ca451f618b61';
v128[2].m_col = 'ff87968431d86a51645151fa773ad009';
v128[2].k_sch = 'b692cf0b643dbdf1be9bc5006830b3fe';
v128[3].start = '4915598f55e5d7a0daca94fa1f0a63f7';
v128[3].s_box = '3b59cb73fcd90ee05774222dc067fb68';
v128[3].s_row = '3bd92268fc74fb735767cbe0c0590e2d';
v128[3].m_col = '4c9c1e66f771f0762c3f868e534df256';
v128[3].k_sch = 'b6ff744ed2c2c9bf6c590cbf0469bf41';
v128[4].start = 'fa636a2825b339c940668a3157244d17';
v128[4].s_box = '2dfb02343f6d12dd09337ec75b36e3f0';
v128[4].s_row = '2d6d7ef03f33e334093602dd5bfb12c7';
v128[4].m_col = '6385b79ffc538df997be478e7547d691';
v128[4].k_sch = '47f7f7bc95353e03f96c32bcfd058dfd';
v128[5].start = '247240236966b3fa6ed2753288425b6c';
v128[5].s_box = '36400926f9336d2d9fb59d23c42c3950';
v128[5].s_row = '36339d50f9b539269f2c092dc4406d23';
v128[5].m_col = 'f4bcd45432e554d075f1d6c51dd03b3c';
v128[5].k_sch = '3caaa3e8a99f9deb50f3af57adf622aa';
v128[6].start = 'c81677bc9b7ac93b25027992b0261996';
v128[6].s_box = 'e847f56514dadde23f77b64fe7f7d490';
v128[6].s_row = 'e8dab6901477d4653ff7f5e2e747dd4f';
v128[6].m_col = '9816ee7400f87f556b2c049c8e5ad036';
v128[6].k_sch = '5e390f7df7a69296a7553dc10aa31f6b';
v128[7].start = 'c62fe109f75eedc3cc79395d84f9cf5d';
v128[7].s_box = 'b415f8016858552e4bb6124c5f998a4c';
v128[7].s_row = 'b458124c68b68a014b99f82e5f15554c';
v128[7].m_col = 'c57e1c159a9bd286f05f4be098c63439';
v128[7].k_sch = '14f9701ae35fe28c440adf4d4ea9c026';
v128[8].start = 'd1876c0f79c4300ab45594add66ff41f';
v128[8].s_box = '3e175076b61c04678dfc2295f6a8bfc0';
v128[8].s_row = '3e1c22c0b6fcbf768da85067f6170495';
v128[8].m_col = 'baa03de7a1f9b56ed5512cba5f414d23';
v128[8].k_sch = '47438735a41c65b9e016baf4aebf7ad2';
v128[9].start = 'fde3bad205e5d0d73547964ef1fe37f1';
v128[9].s_box = '5411f4b56bd9700e96a0902fa1bb9aa1';
v128[9].s_row = '54d990a16ba09ab596bbf40ea111702f';
v128[9].m_col = 'e9f74eec023020f61bf2ccf2353c21c7';
v128[9].k_sch = '549932d1f08557681093ed9cbe2c974e';
v128[10].start = 'bd6e7c3df2b5779e0b61216e8b10b689';
v128[10].s_box = '7a9f102789d5f50b2beffd9f3dca4ea7';
v128[10].s_row = '7ad5fda789ef4e272bca100b3d9ff59f';
v128[10].k_sch = '13111d7fe3944a17f307a78b4d2b30c5';
v128[10].output = '69c4e0d86a7b0430d8cdb78070b4c55a';
})();
var v128d = [];
(function v128d_init() {
for (var i = 0; i <= 10; i++) v128d[i] = {};
v128d.name = '128d';
v128d[0].input = '69c4e0d86a7b0430d8cdb78070b4c55a';
v128d[0].k_sch = '13111d7fe3944a17f307a78b4d2b30c5';
v128d[1].start = '7ad5fda789ef4e272bca100b3d9ff59f';
v128d[1].s_row = '7a9f102789d5f50b2beffd9f3dca4ea7';
v128d[1].s_box = 'bd6e7c3df2b5779e0b61216e8b10b689';
v128d[1].k_sch = '549932d1f08557681093ed9cbe2c974e';
v128d[1].k_add = 'e9f74eec023020f61bf2ccf2353c21c7';
v128d[2].start = '54d990a16ba09ab596bbf40ea111702f';
v128d[2].s_row = '5411f4b56bd9700e96a0902fa1bb9aa1';
v128d[2].s_box = 'fde3bad205e5d0d73547964ef1fe37f1';
v128d[2].k_sch = '47438735a41c65b9e016baf4aebf7ad2';
v128d[2].k_add = 'baa03de7a1f9b56ed5512cba5f414d23';
v128d[3].start = '3e1c22c0b6fcbf768da85067f6170495';
v128d[3].s_row = '3e175076b61c04678dfc2295f6a8bfc0';
v128d[3].s_box = 'd1876c0f79c4300ab45594add66ff41f';
v128d[3].k_sch = '14f9701ae35fe28c440adf4d4ea9c026';
v128d[3].k_add = 'c57e1c159a9bd286f05f4be098c63439';
v128d[4].start = 'b458124c68b68a014b99f82e5f15554c';
v128d[4].s_row = 'b415f8016858552e4bb6124c5f998a4c';
v128d[4].s_box = 'c62fe109f75eedc3cc79395d84f9cf5d';
v128d[4].k_sch = '5e390f7df7a69296a7553dc10aa31f6b';
v128d[4].k_add = '9816ee7400f87f556b2c049c8e5ad036';
v128d[5].start = 'e8dab6901477d4653ff7f5e2e747dd4f';
v128d[5].s_row = 'e847f56514dadde23f77b64fe7f7d490';
v128d[5].s_box = 'c81677bc9b7ac93b25027992b0261996';
v128d[5].k_sch = '3caaa3e8a99f9deb50f3af57adf622aa';
v128d[5].k_add = 'f4bcd45432e554d075f1d6c51dd03b3c';
v128d[6].start = '36339d50f9b539269f2c092dc4406d23';
v128d[6].s_row = '36400926f9336d2d9fb59d23c42c3950';
v128d[6].s_box = '247240236966b3fa6ed2753288425b6c';
v128d[6].k_sch = '47f7f7bc95353e03f96c32bcfd058dfd';
v128d[6].k_add = '6385b79ffc538df997be478e7547d691';
v128d[7].start = '2d6d7ef03f33e334093602dd5bfb12c7';
v128d[7].s_row = '2dfb02343f6d12dd09337ec75b36e3f0';
v128d[7].s_box = 'fa636a2825b339c940668a3157244d17';
v128d[7].k_sch = 'b6ff744ed2c2c9bf6c590cbf0469bf41';
v128d[7].k_add = '4c9c1e66f771f0762c3f868e534df256';
v128d[8].start = '3bd92268fc74fb735767cbe0c0590e2d';
v128d[8].s_row = '3b59cb73fcd90ee05774222dc067fb68';
v128d[8].s_box = '4915598f55e5d7a0daca94fa1f0a63f7';
v128d[8].k_sch = 'b692cf0b643dbdf1be9bc5006830b3fe';
v128d[8].k_add = 'ff87968431d86a51645151fa773ad009';
v128d[9].start = 'a7be1a6997ad739bd8c9ca451f618b61';
v128d[9].s_row = 'a761ca9b97be8b45d8ad1a611fc97369';
v128d[9].s_box = '89d810e8855ace682d1843d8cb128fe4';
v128d[9].k_sch = 'd6aa74fdd2af72fadaa678f1d6ab76fe';
v128d[9].k_add = '5f72641557f5bc92f7be3b291db9f91a';
v128d[10].start = '6353e08c0960e104cd70b751bacad0e7';
v128d[10].s_row = '63cab7040953d051cd60e0e7ba70e18c';
v128d[10].s_box = '00102030405060708090a0b0c0d0e0f0';
v128d[10].k_sch = '000102030405060708090a0b0c0d0e0f';
v128d[10].output = '00112233445566778899aabbccddeeff';
})();
var v192 = [];
(function v192_init() {
for (var i = 0; i <= 12; i++) v192[i] = {};
v192.name = '192';
v192[0].input = '00112233445566778899aabbccddeeff';
v192[0].k_sch = '000102030405060708090a0b0c0d0e0f';
v192[1].start = '00102030405060708090a0b0c0d0e0f0';
v192[1].s_box = '63cab7040953d051cd60e0e7ba70e18c';
v192[1].s_row = '6353e08c0960e104cd70b751bacad0e7';
v192[1].m_col = '5f72641557f5bc92f7be3b291db9f91a';
v192[1].k_sch = '10111213141516175846f2f95c43f4fe';
v192[2].start = '4f63760643e0aa85aff8c9d041fa0de4';
v192[2].s_box = '84fb386f1ae1ac977941dd70832dd769';
v192[2].s_row = '84e1dd691a41d76f792d389783fbac70';
v192[2].m_col = '9f487f794f955f662afc86abd7f1ab29';
v192[2].k_sch = '544afef55847f0fa4856e2e95c43f4fe';
v192[3].start = 'cb02818c17d2af9c62aa64428bb25fd7';
v192[3].s_box = '1f770c64f0b579deaaac432c3d37cf0e';
v192[3].s_row = '1fb5430ef0accf64aa370cde3d77792c';
v192[3].m_col = 'b7a53ecbbf9d75a0c40efc79b674cc11';
v192[3].k_sch = '40f949b31cbabd4d48f043b810b7b342';
v192[4].start = 'f75c7778a327c8ed8cfebfc1a6c37f53';
v192[4].s_box = '684af5bc0acce85564bb0878242ed2ed';
v192[4].s_row = '68cc08ed0abbd2bc642ef555244ae878';
v192[4].m_col = '7a1e98bdacb6d1141a6944dd06eb2d3e';
v192[4].k_sch = '58e151ab04a2a5557effb5416245080c';
v192[5].start = '22ffc916a81474416496f19c64ae2532';
v192[5].s_box = '9316dd47c2fa92834390a1de43e43f23';
v192[5].s_row = '93faa123c2903f4743e4dd83431692de';
v192[5].m_col = 'aaa755b34cffe57cef6f98e1f01c13e6';
v192[5].k_sch = '2ab54bb43a02f8f662e3a95d66410c08';
v192[6].start = '80121e0776fd1d8a8d8c31bc965d1fee';
v192[6].s_box = 'cdc972c53854a47e5d64c765904cc028';
v192[6].s_row = 'cd54c7283864c0c55d4c727e90c9a465';
v192[6].m_col = '921f748fd96e937d622d7725ba8ba50c';
v192[6].k_sch = 'f501857297448d7ebdf1c6ca87f33e3c';
v192[7].start = '671ef1fd4e2a1e03dfdcb1ef3d789b30';
v192[7].s_box = '8572a1542fe5727b9e86c8df27bc1404';
v192[7].s_row = '85e5c8042f8614549ebca17b277272df';
v192[7].m_col = 'e913e7b18f507d4b227ef652758acbcc';
v192[7].k_sch = 'e510976183519b6934157c9ea351f1e0';
v192[8].start = '0c0370d00c01e622166b8accd6db3a2c';
v192[8].s_box = 'fe7b5170fe7c8e93477f7e4bf6b98071';
v192[8].s_row = 'fe7c7e71fe7f807047b95193f67b8e4b';
v192[8].m_col = '6cf5edf996eb0a069c4ef21cbfc25762';
v192[8].k_sch = '1ea0372a995309167c439e77ff12051e';
v192[9].start = '7255dad30fb80310e00d6c6b40d0527c';
v192[9].s_box = '40fc5766766c7bcae1d7507f09700010';
v192[9].s_row = '406c501076d70066e17057ca09fc7b7f';
v192[9].m_col = '7478bcdce8a50b81d4327a9009188262';
v192[9].k_sch = 'dd7e0e887e2fff68608fc842f9dcc154';
v192[10].start = 'a906b254968af4e9b4bdb2d2f0c44336';
v192[10].s_box = 'd36f3720907ebf1e8d7a37b58c1c1a05';
v192[10].s_row = 'd37e3705907a1a208d1c371e8c6fbfb5';
v192[10].m_col = '0d73cc2d8f6abe8b0cf2dd9bb83d422e';
v192[10].k_sch = '859f5f237a8d5a3dc0c02952beefd63a';
v192[11].start = '88ec930ef5e7e4b6cc32f4c906d29414';
v192[11].s_box = 'c4cedcabe694694e4b23bfdd6fb522fa';
v192[11].s_row = 'c494bffae62322ab4bb5dc4e6fce69dd';
v192[11].m_col = '71d720933b6d677dc00b8f28238e0fb7';
v192[11].k_sch = 'de601e7827bcdf2ca223800fd8aeda32';
v192[12].start = 'afb73eeb1cd1b85162280f27fb20d585';
v192[12].s_box = '79a9b2e99c3e6cd1aa3476cc0fb70397';
v192[12].s_row = '793e76979c3403e9aab7b2d10fa96ccc';
v192[12].k_sch = 'a4970a331a78dc09c418c271e3a41d5d';
v192[12].output = 'dda97ca4864cdfe06eaf70a0ec0d7191';
})();
var v192d = [];
(function v192d_init() {
for (var i = 0; i <= 12; i++) v192d[i] = {};
v192d.name = '192d';
v192d[0].input = 'dda97ca4864cdfe06eaf70a0ec0d7191';
v192d[0].k_sch = 'a4970a331a78dc09c418c271e3a41d5d';
v192d[1].start = '793e76979c3403e9aab7b2d10fa96ccc';
v192d[1].s_row = '79a9b2e99c3e6cd1aa3476cc0fb70397';
v192d[1].s_box = 'afb73eeb1cd1b85162280f27fb20d585';
v192d[1].k_sch = 'de601e7827bcdf2ca223800fd8aeda32';
v192d[1].k_add = '71d720933b6d677dc00b8f28238e0fb7';
v192d[2].start = 'c494bffae62322ab4bb5dc4e6fce69dd';
v192d[2].s_row = 'c4cedcabe694694e4b23bfdd6fb522fa';
v192d[2].s_box = '88ec930ef5e7e4b6cc32f4c906d29414';
v192d[2].k_sch = '859f5f237a8d5a3dc0c02952beefd63a';
v192d[2].k_add = '0d73cc2d8f6abe8b0cf2dd9bb83d422e';
v192d[3].start = 'd37e3705907a1a208d1c371e8c6fbfb5';
v192d[3].s_row = 'd36f3720907ebf1e8d7a37b58c1c1a05';
v192d[3].s_box = 'a906b254968af4e9b4bdb2d2f0c44336';
v192d[3].k_sch = 'dd7e0e887e2fff68608fc842f9dcc154';
v192d[3].k_add = '7478bcdce8a50b81d4327a9009188262';
v192d[4].start = '406c501076d70066e17057ca09fc7b7f';
v192d[4].s_row = '40fc5766766c7bcae1d7507f09700010';
v192d[4].s_box = '7255dad30fb80310e00d6c6b40d0527c';
v192d[4].k_sch = '1ea0372a995309167c439e77ff12051e';
v192d[4].k_add = '6cf5edf996eb0a069c4ef21cbfc25762';
v192d[5].start = 'fe7c7e71fe7f807047b95193f67b8e4b';
v192d[5].s_row = 'fe7b5170fe7c8e93477f7e4bf6b98071';
v192d[5].s_box = '0c0370d00c01e622166b8accd6db3a2c';
v192d[5].k_sch = 'e510976183519b6934157c9ea351f1e0';
v192d[5].k_add = 'e913e7b18f507d4b227ef652758acbcc';
v192d[6].start = '85e5c8042f8614549ebca17b277272df';
v192d[6].s_row = '8572a1542fe5727b9e86c8df27bc1404';
v192d[6].s_box = '671ef1fd4e2a1e03dfdcb1ef3d789b30';
v192d[6].k_sch = 'f501857297448d7ebdf1c6ca87f33e3c';
v192d[6].k_add = '921f748fd96e937d622d7725ba8ba50c';
v192d[7].start = 'cd54c7283864c0c55d4c727e90c9a465';
v192d[7].s_row = 'cdc972c53854a47e5d64c765904cc028';
v192d[7].s_box = '80121e0776fd1d8a8d8c31bc965d1fee';
v192d[7].k_sch = '2ab54bb43a02f8f662e3a95d66410c08';
v192d[7].k_add = 'aaa755b34cffe57cef6f98e1f01c13e6';
v192d[8].start = '93faa123c2903f4743e4dd83431692de';
v192d[8].s_row = '9316dd47c2fa92834390a1de43e43f23';
v192d[8].s_box = '22ffc916a81474416496f19c64ae2532';
v192d[8].k_sch = '58e151ab04a2a5557effb5416245080c';
v192d[8].k_add = '7a1e98bdacb6d1141a6944dd06eb2d3e';
v192d[9].start = '68cc08ed0abbd2bc642ef555244ae878';
v192d[9].s_row = '684af5bc0acce85564bb0878242ed2ed';
v192d[9].s_box = 'f75c7778a327c8ed8cfebfc1a6c37f53';
v192d[9].k_sch = '40f949b31cbabd4d48f043b810b7b342';
v192d[9].k_add = 'b7a53ecbbf9d75a0c40efc79b674cc11';
v192d[10].start = '1fb5430ef0accf64aa370cde3d77792c';
v192d[10].s_row = '1f770c64f0b579deaaac432c3d37cf0e';
v192d[10].s_box = 'cb02818c17d2af9c62aa64428bb25fd7';
v192d[10].k_sch = '544afef55847f0fa4856e2e95c43f4fe';
v192d[10].k_add = '9f487f794f955f662afc86abd7f1ab29';
v192d[11].start = '84e1dd691a41d76f792d389783fbac70';
v192d[11].s_row = '84fb386f1ae1ac977941dd70832dd769';
v192d[11].s_box = '4f63760643e0aa85aff8c9d041fa0de4';
v192d[11].k_sch = '10111213141516175846f2f95c43f4fe';
v192d[11].k_add = '5f72641557f5bc92f7be3b291db9f91a';
v192d[12].start = '6353e08c0960e104cd70b751bacad0e7';
v192d[12].s_row = '63cab7040953d051cd60e0e7ba70e18c';
v192d[12].s_box = '00102030405060708090a0b0c0d0e0f0';
v192d[12].k_sch = '000102030405060708090a0b0c0d0e0f';
v192d[12].output = '00112233445566778899aabbccddeeff';
})();
var v256 = [];
(function v256_init() {
for (var i = 0; i <= 14; i++) v256[i] = {};
v256.name = '256';
v256[0].input = '00112233445566778899aabbccddeeff';
v256[0].k_sch = '000102030405060708090a0b0c0d0e0f';
v256[1].start = '00102030405060708090a0b0c0d0e0f0';
v256[1].s_box = '63cab7040953d051cd60e0e7ba70e18c';
v256[1].s_row = '6353e08c0960e104cd70b751bacad0e7';
v256[1].m_col = '5f72641557f5bc92f7be3b291db9f91a';
v256[1].k_sch = '101112131415161718191a1b1c1d1e1f';
v256[2].start = '4f63760643e0aa85efa7213201a4e705';
v256[2].s_box = '84fb386f1ae1ac97df5cfd237c49946b';
v256[2].s_row = '84e1fd6b1a5c946fdf4938977cfbac23';
v256[2].m_col = 'bd2a395d2b6ac438d192443e615da195';
v256[2].k_sch = 'a573c29fa176c498a97fce93a572c09c';
v256[3].start = '1859fbc28a1c00a078ed8aadc42f6109';
v256[3].s_box = 'adcb0f257e9c63e0bc557e951c15ef01';
v256[3].s_row = 'ad9c7e017e55ef25bc150fe01ccb6395';
v256[3].m_col = '810dce0cc9db8172b3678c1e88a1b5bd';
v256[3].k_sch = '1651a8cd0244beda1a5da4c10640bade';
v256[4].start = '975c66c1cb9f3fa8a93a28df8ee10f63';
v256[4].s_box = '884a33781fdb75c2d380349e19f876fb';
v256[4].s_row = '88db34fb1f807678d3f833c2194a759e';
v256[4].m_col = 'b2822d81abe6fb275faf103a078c0033';
v256[4].k_sch = 'ae87dff00ff11b68a68ed5fb03fc1567';
v256[5].start = '1c05f271a417e04ff921c5c104701554';
v256[5].s_box = '9c6b89a349f0e18499fda678f2515920';
v256[5].s_row = '9cf0a62049fd59a399518984f26be178';
v256[5].m_col = 'aeb65ba974e0f822d73f567bdb64c877';
v256[5].k_sch = '6de1f1486fa54f9275f8eb5373b8518d';
v256[6].start = 'c357aae11b45b7b0a2c7bd28a8dc99fa';
v256[6].s_box = '2e5bacf8af6ea9e73ac67a34c286ee2d';
v256[6].s_row = '2e6e7a2dafc6eef83a86ace7c25ba934';
v256[6].m_col = 'b951c33c02e9bd29ae25cdb1efa08cc7';
v256[6].k_sch = 'c656827fc9a799176f294cec6cd5598b';
v256[7].start = '7f074143cb4e243ec10c815d8375d54c';
v256[7].s_box = 'd2c5831a1f2f36b278fe0c4cec9d0329';
v256[7].s_row = 'd22f0c291ffe031a789d83b2ecc5364c';
v256[7].m_col = 'ebb19e1c3ee7c9e87d7535e9ed6b9144';
v256[7].k_sch = '3de23a75524775e727bf9eb45407cf39';
v256[8].start = 'd653a4696ca0bc0f5acaab5db96c5e7d';
v256[8].s_box = 'f6ed49f950e06576be74624c565058ff';
v256[8].s_row = 'f6e062ff507458f9be50497656ed654c';
v256[8].m_col = '5174c8669da98435a8b3e62ca974a5ea';
v256[8].k_sch = '0bdc905fc27b0948ad5245a4c1871c2f';
v256[9].start = '5aa858395fd28d7d05e1a38868f3b9c5';
v256[9].s_box = 'bec26a12cfb55dff6bf80ac4450d56a6';
v256[9].s_row = 'beb50aa6cff856126b0d6aff45c25dc4';
v256[9].m_col = '0f77ee31d2ccadc05430a83f4ef96ac3';
v256[9].k_sch = '45f5a66017b2d387300d4d33640a820a';
v256[10].start = '4a824851c57e7e47643de50c2af3e8c9';
v256[10].s_box = 'd61352d1a6f3f3a04327d9fee50d9bdd';
v256[10].s_row = 'd6f3d9dda6279bd1430d52a0e513f3fe';
v256[10].m_col = 'bd86f0ea748fc4f4630f11c1e9331233';
v256[10].k_sch = '7ccff71cbeb4fe5413e6bbf0d261a7df';
v256[11].start = 'c14907f6ca3b3aa070e9aa313b52b5ec';
v256[11].s_box = '783bc54274e280e0511eacc7e200d5ce';
v256[11].s_row = '78e2acce741ed5425100c5e0e23b80c7';
v256[11].m_col = 'af8690415d6e1dd387e5fbedd5c89013';
v256[11].k_sch = 'f01afafee7a82979d7a5644ab3afe640';
v256[12].start = '5f9c6abfbac634aa50409fa766677653';
v256[12].s_box = 'cfde0208f4b418ac5309db5c338538ed';
v256[12].s_row = 'cfb4dbedf4093808538502ac33de185c';
v256[12].m_col = '7427fae4d8a695269ce83d315be0392b';
v256[12].k_sch = '2541fe719bf500258813bbd55a721c0a';
v256[13].start = '516604954353950314fb86e401922521';
v256[13].s_box = 'd133f22a1aed2a7bfa0f44697c4f3ffd';
v256[13].s_row = 'd1ed44fd1a0f3f2afa4ff27b7c332a69';
v256[13].m_col = '2c21a820306f154ab712c75eee0da04f';
v256[13].k_sch = '4e5a6699a9f24fe07e572baacdf8cdea';
v256[14].start = '627bceb9999d5aaac945ecf423f56da5';
v256[14].s_box = 'aa218b56ee5ebeacdd6ecebf26e63c06';
v256[14].s_row = 'aa5ece06ee6e3c56dde68bac2621bebf';
v256[14].k_sch = '24fc79ccbf0979e9371ac23c6d68de36';
v256[14].output = '8ea2b7ca516745bfeafc49904b496089';
})();
var v256d = [];
(function v256d_init() {
for (var i = 0; i <= 14; i++) v256d[i] = {};
v256d.name = '256d';
v256d[0].input = '8ea2b7ca516745bfeafc49904b496089';
v256d[0].k_sch = '24fc79ccbf0979e9371ac23c6d68de36';
v256d[1].start = 'aa5ece06ee6e3c56dde68bac2621bebf';
v256d[1].s_row = 'aa218b56ee5ebeacdd6ecebf26e63c06';
v256d[1].s_box = '627bceb9999d5aaac945ecf423f56da5';
v256d[1].k_sch = '4e5a6699a9f24fe07e572baacdf8cdea';
v256d[1].k_add = '2c21a820306f154ab712c75eee0da04f';
v256d[2].start = 'd1ed44fd1a0f3f2afa4ff27b7c332a69';
v256d[2].s_row = 'd133f22a1aed2a7bfa0f44697c4f3ffd';
v256d[2].s_box = '516604954353950314fb86e401922521';
v256d[2].k_sch = '2541fe719bf500258813bbd55a721c0a';
v256d[2].k_add = '7427fae4d8a695269ce83d315be0392b';
v256d[3].start = 'cfb4dbedf4093808538502ac33de185c';
v256d[3].s_row = 'cfde0208f4b418ac5309db5c338538ed';
v256d[3].s_box = '5f9c6abfbac634aa50409fa766677653';
v256d[3].k_sch = 'f01afafee7a82979d7a5644ab3afe640';
v256d[3].k_add = 'af8690415d6e1dd387e5fbedd5c89013';
v256d[4].start = '78e2acce741ed5425100c5e0e23b80c7';
v256d[4].s_row = '783bc54274e280e0511eacc7e200d5ce';
v256d[4].s_box = 'c14907f6ca3b3aa070e9aa313b52b5ec';
v256d[4].k_sch = '7ccff71cbeb4fe5413e6bbf0d261a7df';
v256d[4].k_add = 'bd86f0ea748fc4f4630f11c1e9331233';
v256d[5].start = 'd6f3d9dda6279bd1430d52a0e513f3fe';
v256d[5].s_row = 'd61352d1a6f3f3a04327d9fee50d9bdd';
v256d[5].s_box = '4a824851c57e7e47643de50c2af3e8c9';
v256d[5].k_sch = '45f5a66017b2d387300d4d33640a820a';
v256d[5].k_add = '0f77ee31d2ccadc05430a83f4ef96ac3';
v256d[6].start = 'beb50aa6cff856126b0d6aff45c25dc4';
v256d[6].s_row = 'bec26a12cfb55dff6bf80ac4450d56a6';
v256d[6].s_box = '5aa858395fd28d7d05e1a38868f3b9c5';
v256d[6].k_sch = '0bdc905fc27b0948ad5245a4c1871c2f';
v256d[6].k_add = '5174c8669da98435a8b3e62ca974a5ea';
v256d[7].start = 'f6e062ff507458f9be50497656ed654c';
v256d[7].s_row = 'f6ed49f950e06576be74624c565058ff';
v256d[7].s_box = 'd653a4696ca0bc0f5acaab5db96c5e7d';
v256d[7].k_sch = '3de23a75524775e727bf9eb45407cf39';
v256d[7].k_add = 'ebb19e1c3ee7c9e87d7535e9ed6b9144';
v256d[8].start = 'd22f0c291ffe031a789d83b2ecc5364c';
v256d[8].s_row = 'd2c5831a1f2f36b278fe0c4cec9d0329';
v256d[8].s_box = '7f074143cb4e243ec10c815d8375d54c';
v256d[8].k_sch = 'c656827fc9a799176f294cec6cd5598b';
v256d[8].k_add = 'b951c33c02e9bd29ae25cdb1efa08cc7';
v256d[9].start = '2e6e7a2dafc6eef83a86ace7c25ba934';
v256d[9].s_row = '2e5bacf8af6ea9e73ac67a34c286ee2d';
v256d[9].s_box = 'c357aae11b45b7b0a2c7bd28a8dc99fa';
v256d[9].k_sch = '6de1f1486fa54f9275f8eb5373b8518d';
v256d[9].k_add = 'aeb65ba974e0f822d73f567bdb64c877';
v256d[10].start = '9cf0a62049fd59a399518984f26be178';
v256d[10].s_row = '9c6b89a349f0e18499fda678f2515920';
v256d[10].s_box = '1c05f271a417e04ff921c5c104701554';
v256d[10].k_sch = 'ae87dff00ff11b68a68ed5fb03fc1567';
v256d[10].k_add = 'b2822d81abe6fb275faf103a078c0033';
v256d[11].start = '88db34fb1f807678d3f833c2194a759e';
v256d[11].s_row = '884a33781fdb75c2d380349e19f876fb';
v256d[11].s_box = '975c66c1cb9f3fa8a93a28df8ee10f63';
v256d[11].k_sch = '1651a8cd0244beda1a5da4c10640bade';
v256d[11].k_add = '810dce0cc9db8172b3678c1e88a1b5bd';
v256d[12].start = 'ad9c7e017e55ef25bc150fe01ccb6395';
v256d[12].s_row = 'adcb0f257e9c63e0bc557e951c15ef01';
v256d[12].s_box = '1859fbc28a1c00a078ed8aadc42f6109';
v256d[12].k_sch = 'a573c29fa176c498a97fce93a572c09c';
v256d[12].k_add = 'bd2a395d2b6ac438d192443e615da195';
v256d[13].start = '84e1fd6b1a5c946fdf4938977cfbac23';
v256d[13].s_row = '84fb386f1ae1ac97df5cfd237c49946b';
v256d[13].s_box = '4f63760643e0aa85efa7213201a4e705';
v256d[13].k_sch = '101112131415161718191a1b1c1d1e1f';
v256d[13].k_add = '5f72641557f5bc92f7be3b291db9f91a';
v256d[14].start = '6353e08c0960e104cd70b751bacad0e7';
v256d[14].s_row = '63cab7040953d051cd60e0e7ba70e18c';
v256d[14].s_box = '00102030405060708090a0b0c0d0e0f0';
v256d[14].k_sch = '000102030405060708090a0b0c0d0e0f';
v256d[14].output = '00112233445566778899aabbccddeeff';
})();
@@ -0,0 +1,164 @@
// 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
* @final
* @struct
*/
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;
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2010 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.crypt.arc4
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.crypt.Arc4Test');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,59 @@
// 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.
goog.provide('goog.crypt.Arc4Test');
goog.setTestOnly('goog.crypt.Arc4Test');
goog.require('goog.array');
goog.require('goog.crypt.Arc4');
goog.require('goog.testing.jsunit');
function testEncryptionDecryption() {
var key = [0x25, 0x26, 0x27, 0x28];
var startArray = [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67];
var byteArray = [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67];
var arc4 = new goog.crypt.Arc4();
arc4.setKey(key);
arc4.crypt(byteArray);
assertArrayEquals(byteArray, [0x51, 0xBB, 0xDD, 0x95, 0x9B, 0x42, 0x34]);
// The same key and crypt call should unencrypt the data back to its original
// state
arc4 = new goog.crypt.Arc4();
arc4.setKey(key);
arc4.crypt(byteArray);
assertArrayEquals(byteArray, startArray);
}
function testDiscard() {
var key = [0x25, 0x26, 0x27, 0x28];
var data = [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67];
var arc4 = new goog.crypt.Arc4();
arc4.setKey(key);
arc4.discard(256);
var withDiscard = goog.array.clone(data);
arc4.crypt(withDiscard);
// First encrypting a dummy array should give the same result as
// discarding.
arc4 = new goog.crypt.Arc4();
arc4.setKey(key);
var withCrypt = goog.array.clone(data);
arc4.crypt(new Array(256));
arc4.crypt(withCrypt);
assertArrayEquals(withDiscard, withCrypt);
}
@@ -0,0 +1,286 @@
// 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)
*/
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.
*
* In base-64 decoding, groups of four characters are converted into three
* bytes. If the encoder did not apply padding, the input length may not
* be a multiple of 4.
*
* In this case, the last group will have fewer than 4 characters, and
* padding will be inferred. If the group has one or two characters, it decodes
* to one byte. If the group has three characters, it decodes to two bytes.
*
* @param {string} input Input to decode.
* @param {boolean=} opt_webSafe True if we should use the web-safe alphabet.
* @return {!Array<number>} 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)] : 64;
++i;
var haveByte4 = i < input.length;
var byte4 = haveByte4 ? charToByteMap[input.charAt(i)] : 64;
++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;
// Be forgiving when decoding and correctly decode both encodings.
if (i >= goog.crypt.base64.ENCODED_VALS_BASE.length) {
goog.crypt.base64.charToByteMap_[
goog.crypt.base64.ENCODED_VALS_WEBSAFE.charAt(i)] = i;
goog.crypt.base64.charToByteMapWebSafe_[
goog.crypt.base64.ENCODED_VALS.charAt(i)] = i;
}
}
}
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2007 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.crypt.base64
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.crypt.base64Test');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,156 @@
// 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.
goog.provide('goog.crypt.base64Test');
goog.setTestOnly('goog.crypt.base64Test');
goog.require('goog.crypt');
goog.require('goog.crypt.base64');
goog.require('goog.testing.jsunit');
// Static test data
var tests = [
'', '',
'f', 'Zg==',
'fo', 'Zm8=',
'foo', 'Zm9v',
'foob', 'Zm9vYg==',
'fooba', 'Zm9vYmE=',
'foobar', 'Zm9vYmFy',
// Testing non-ascii characters (1-10 in chinese)
'\xe4\xb8\x80\xe4\xba\x8c\xe4\xb8\x89\xe5\x9b\x9b\xe4\xba\x94\xe5' +
'\x85\xad\xe4\xb8\x83\xe5\x85\xab\xe4\xb9\x9d\xe5\x8d\x81',
'5LiA5LqM5LiJ5Zub5LqU5YWt5LiD5YWr5Lmd5Y2B'];
function testByteArrayEncoding() {
// Let's see if it's sane by feeding it some well-known values. Index i
// has the input and index i+1 has the expected value.
for (var i = 0; i < tests.length; i += 2) {
var enc = goog.crypt.base64.encodeByteArray(
goog.crypt.stringToByteArray(tests[i]));
assertEquals(tests[i + 1], enc);
var dec = goog.crypt.byteArrayToString(
goog.crypt.base64.decodeStringToByteArray(enc));
assertEquals(tests[i], dec);
// Check that websafe decoding accepts non-websafe codes.
dec = goog.crypt.byteArrayToString(
goog.crypt.base64.decodeStringToByteArray(enc, true /* websafe */));
assertEquals(tests[i], dec);
// Re-encode as websafe.
enc = goog.crypt.base64.encodeByteArray(
goog.crypt.stringToByteArray(tests[i], true /* websafe */));
// Check that non-websafe decoding accepts websafe codes.
dec = goog.crypt.byteArrayToString(
goog.crypt.base64.decodeStringToByteArray(enc));
assertEquals(tests[i], dec);
// Check that websafe decoding accepts websafe codes.
dec = goog.crypt.byteArrayToString(
goog.crypt.base64.decodeStringToByteArray(enc, true /* websafe */));
assertEquals(tests[i], dec);
}
}
function testOddLengthByteArrayEncoding() {
var buffer = [0, 0, 0];
var encodedBuffer = goog.crypt.base64.encodeByteArray(buffer);
assertEquals('AAAA', encodedBuffer);
var decodedBuffer = goog.crypt.base64.decodeStringToByteArray(encodedBuffer);
assertEquals(decodedBuffer.length, buffer.length);
for (i = 0; i < buffer.length; i++) {
assertEquals(buffer[i], decodedBuffer[i]);
}
}
// Tests that decoding a string where the length is not a multiple of 4 does
// not produce spurious trailing zeroes. This is a regression test for
// cl/65120705, which fixes a bug that was introduced when support for
// non-padded base64 encoding was added in cl/20209336.
function testOddLengthByteArrayDecoding() {
// The base-64 encoding of the bytes [97, 98, 99, 100], with no padding.
// The padded version would be "YWJjZA==" (length 8), or "YWJjZA.." if
// web-safe.
var encodedBuffer = 'YWJjZA';
var decodedBuffer1 = goog.crypt.base64.decodeStringToByteArray(encodedBuffer);
assertEquals(4, decodedBuffer1.length);
// Note that byteArrayToString ignores any trailing zeroes because
// String.fromCharCode(0) is ''.
assertEquals('abcd', goog.crypt.byteArrayToString(decodedBuffer1));
// Repeat the test in web-safe decoding mode.
var decodedBuffer2 = goog.crypt.base64.decodeStringToByteArray(encodedBuffer,
true /* web-safe */);
assertEquals(4, decodedBuffer2.length);
assertEquals('abcd', goog.crypt.byteArrayToString(decodedBuffer2));
}
function testShortcutPathEncoding() {
// Test the higher-level API (tests the btoa/atob shortcut path)
for (var i = 0; i < tests.length; i += 2) {
var enc = goog.crypt.base64.encodeString(tests[i]);
assertEquals(tests[i + 1], enc);
var dec = goog.crypt.base64.decodeString(enc);
assertEquals(tests[i], dec);
}
}
function testMultipleIterations() {
// Now run it through its paces
var numIterations = 100;
for (var i = 0; i < numIterations; i++) {
var input = [];
for (var j = 0; j < i; j++)
input[j] = j % 256;
var encoded = goog.crypt.base64.encodeByteArray(input);
var decoded = goog.crypt.base64.decodeStringToByteArray(encoded);
assertEquals('Decoded length not equal to input length?',
input.length, decoded.length);
for (var j = 0; j < i; j++)
assertEquals('Values differ at position ' + j, input[j], decoded[j]);
}
}
function testWebSafeEncoding() {
// Test non-websafe / websafe difference
var test = '>>>???>>>???=/+';
var enc = goog.crypt.base64.encodeByteArray(
goog.crypt.stringToByteArray(test));
assertEquals('Non-websafe broken?', 'Pj4+Pz8/Pj4+Pz8/PS8r', enc);
enc = goog.crypt.base64.encodeString(test);
assertEquals('Non-websafe broken?', 'Pj4+Pz8/Pj4+Pz8/PS8r', enc);
enc = goog.crypt.base64.encodeByteArray(
goog.crypt.stringToByteArray(test), true /* websafe */);
assertEquals('Websafe encoding broken', 'Pj4-Pz8_Pj4-Pz8_PS8r', enc);
enc = goog.crypt.base64.encodeString(test, true);
assertEquals('Non-websafe broken?', 'Pj4-Pz8_Pj4-Pz8_PS8r', enc);
var dec = goog.crypt.byteArrayToString(
goog.crypt.base64.decodeStringToByteArray(enc, true /* websafe */));
assertEquals('Websafe decoding broken', test, dec);
dec = goog.crypt.base64.decodeString(enc, true /* websafe */);
assertEquals('Websafe decoding broken', test, dec);
// Test parsing malformed characters
assertThrows('Didn\'t throw on malformed input', function() {
goog.crypt.base64.decodeStringToByteArray('foooooo)oooo', true /*websafe*/);
});
}
@@ -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('');
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2007 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.crypt.baseN
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.crypt.baseNTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,165 @@
// 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 Tests for arbitrary base conversion library baseconversion.js.
*/
goog.provide('goog.crypt.baseNTest');
goog.setTestOnly('goog.crypt.baseNTest');
goog.require('goog.crypt.baseN');
goog.require('goog.testing.jsunit');
function testDecToHex() {
verifyConversion(goog.crypt.baseN.BASE_DECIMAL, '0',
goog.crypt.baseN.BASE_LOWERCASE_HEXADECIMAL, '0');
verifyConversion(goog.crypt.baseN.BASE_DECIMAL, '9',
goog.crypt.baseN.BASE_UPPERCASE_HEXADECIMAL, '9');
verifyConversion(goog.crypt.baseN.BASE_DECIMAL, '13',
goog.crypt.baseN.BASE_LOWERCASE_HEXADECIMAL, 'd');
verifyConversion(goog.crypt.baseN.BASE_DECIMAL, '255',
goog.crypt.baseN.BASE_UPPERCASE_HEXADECIMAL, 'FF');
verifyConversion(goog.crypt.baseN.BASE_DECIMAL,
'53425987345897',
goog.crypt.baseN.BASE_LOWERCASE_HEXADECIMAL,
'309734ff5de9');
verifyConversion(goog.crypt.baseN.BASE_DECIMAL,
'987080888',
goog.crypt.baseN.BASE_UPPERCASE_HEXADECIMAL,
'3AD5A8B8');
verifyConversion(goog.crypt.baseN.BASE_DECIMAL,
'009341587237',
goog.crypt.baseN.BASE_LOWERCASE_HEXADECIMAL,
'22ccd4f25');
}
function testBinToDec() {
verifyConversion(
goog.crypt.baseN.BASE_BINARY,
'11101010101000100010010000010010010000111101000100110111000000100001' +
'01100100111110110010000010110100111101000010010100001011111011111100' +
'00000010000010000101010101000000000101100000000100011111011101111001' +
'10000001000000000100101110001001001101101001101111010101111100010001' +
'11011100000110111000000100111011100100010010011001111011001111001011' +
'10001000101111001010011101101100110110011110010000011100101011110010' +
'11010001001111110011000000001001011011111011010000110011010000010111' +
'10111100000001100010111100000100000000110001011101011110100000011010' +
'0110000100011111',
goog.crypt.baseN.BASE_DECIMAL,
'34589745906769047354795784390596748934723904739085568907689045723489' +
'05745789789078907890789023447892365623589745678902348976234598723459' +
'087523496723486089723459078349087');
}
function testDecToBin() {
verifyConversion(
goog.crypt.baseN.BASE_DECIMAL,
'00342589674590347859734908573490568347534805468907960579056785605496' +
'83475873465859072390486756098742380573908572390463805745656623475234' +
'82345670247851902784123897349486238502378940637925807378946358964328' +
'57906148572346857346409823758034763928401296023947234784623765456367' +
'764623627623574',
goog.crypt.baseN.BASE_BINARY,
'10010011011100101010001111100111001100110000110111111110010110101000' +
'01010110110010000111000001100110100101010000101001100001011000101111' +
'01011101111100101101010010000111011110011110010101111001110010100100' +
'10111110000101111011010000000111111011110010011110101011100101000001' +
'00011000101010011001101000011101001010001101011110101001011011100101' +
'11100000101000010010101001011001100100101110111101010000011010001010' +
'01011100100111110001100111100100011001001001100011011100100111011111' +
'01000100101001000100110001011010010000011010111101111111111111110100' +
'01100101001111001111100110101000001100100000111111100101110010111011' +
'10110110001100100011101010110110100001001000101011001001100011010110' +
'10110100000110000110010111110100000100110110010010010101111001001111' +
'11100100000010101111110100011010011101011010001101110011100110111111' +
'11000100001111010000000101011011000010010000000100111111010110111100' +
'00101111010011011010011010010001000101100001111001110010010110');
}
function test7To9() {
verifyConversion(
'0123456', // Base 7.
'60625646056660665666066534602566346056634560665606666656465634265434' +
'66563465664566346406366534664656650660665623456663456654360665',
'012345678', // Base 9.
'11451222686557606458341381287142358175337801548087003804852781764284' +
'273762357630423116743334671762638240652740158536');
}
function testZeros() {
verifyConversion(goog.crypt.baseN.BASE_DECIMAL, '0',
goog.crypt.baseN.BASE_LOWERCASE_HEXADECIMAL, '0');
verifyConversion(goog.crypt.baseN.BASE_DECIMAL, '000',
goog.crypt.baseN.BASE_LOWERCASE_HEXADECIMAL, '0');
verifyConversion(goog.crypt.baseN.BASE_DECIMAL, '0000007',
goog.crypt.baseN.BASE_LOWERCASE_HEXADECIMAL, '7');
}
function testArbitraryBases() {
verifyConversion('X9(', // Base 3.
'9(XX((9X(XX9(9X9(X9(',
'a:*o9', // Base 5.
':oa**:9o9**9oo');
}
function testEmptyBases() {
var e = assertThrows(function() {
goog.crypt.baseN.recodeString('1230', '', '0123');
});
assertEquals('Exception message', 'Number 1230 contains a character ' +
'not found in base , which is 0', e.message);
e = assertThrows(function() {
goog.crypt.baseN.recodeString('1230', '0123', '');
});
assertEquals('Exception message', 'Empty output base', e.message);
}
function testInvalidDigits() {
var e = assertThrows(function() {
goog.crypt.baseN.recodeString('123x456', '01234567', '01234567');
});
assertEquals('Exception message', 'Number 123x456 contains a character ' +
'not found in base 01234567, which is x', e.message);
}
function makeHugeBase() {
// Number of digits in the base.
// Tests break if this is set to 200'000. The reason for that is
// String.fromCharCode(196609) == String.fromCharCode(1).
var baseSize = 20000;
var tab = [];
for (var i = 0; i < baseSize; i++) {
tab.push(String.fromCharCode(i));
}
return tab.join('');
}
function testHugeInputBase() {
verifyConversion(makeHugeBase(), String.fromCharCode(12345),
goog.crypt.baseN.BASE_DECIMAL, '12345');
}
function testHugeOutputBase() {
verifyConversion(goog.crypt.baseN.BASE_DECIMAL, '12345',
makeHugeBase(), String.fromCharCode(12345));
}
function verifyConversion(inputBase, inputNumber, outputBase, outputNumber) {
assertEquals(outputNumber,
goog.crypt.baseN.recodeString(inputNumber,
inputBase,
outputBase));
}
@@ -0,0 +1,283 @@
// 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.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}
* @final
*/
goog.crypt.BlobHasher = function(hashFn, opt_blockSize) {
goog.crypt.BlobHasher.base(this, 'constructor');
/**
* 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);
};
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2011 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.crypt.BlobHasher
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.crypt.BlobHasherTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,382 @@
// 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.
goog.provide('goog.crypt.BlobHasherTest');
goog.setTestOnly('goog.crypt.BlobHasherTest');
goog.require('goog.crypt');
goog.require('goog.crypt.BlobHasher');
goog.require('goog.crypt.Md5');
goog.require('goog.events');
goog.require('goog.testing.PropertyReplacer');
goog.require('goog.testing.jsunit');
// A browser-independent mock of goog.fs.sliceBlob. The actual implementation
// calls the underlying slice method differently based on browser version.
// This mock does not support negative opt_end.
var fsSliceBlobMock = function(blob, start, opt_end) {
if (!goog.isNumber(opt_end)) {
opt_end = blob.size;
}
return blob.slice(start, opt_end);
};
// Mock out the Blob using a string.
BlobMock = function(string) {
this.data = string;
this.size = this.data.length;
};
BlobMock.prototype.slice = function(start, end) {
return new BlobMock(this.data.substr(start, end - start));
};
// Mock out the FileReader to have control over the flow.
FileReaderMock = function() {
this.array_ = [];
this.result = null;
this.readyState = this.EMPTY;
this.onload = null;
this.onabort = null;
this.onerror = null;
};
FileReaderMock.prototype.EMPTY = 0;
FileReaderMock.prototype.LOADING = 1;
FileReaderMock.prototype.DONE = 2;
FileReaderMock.prototype.mockLoad = function() {
this.readyState = this.DONE;
this.result = this.array_;
if (this.onload) {
this.onload.call();
}
};
FileReaderMock.prototype.abort = function() {
this.readyState = this.DONE;
if (this.onabort) {
this.onabort.call();
}
};
FileReaderMock.prototype.mockError = function() {
this.readyState = this.DONE;
if (this.onerror) {
this.onerror.call();
}
};
FileReaderMock.prototype.readAsArrayBuffer = function(blobMock) {
this.readyState = this.LOADING;
this.array_ = [];
for (var i = 0; i < blobMock.size; ++i) {
this.array_[i] = blobMock.data.charCodeAt(i);
}
};
FileReaderMock.prototype.isLoading = function() {
return this.readyState == this.LOADING;
};
var stubs = new goog.testing.PropertyReplacer();
function setUp() {
stubs.set(goog.global, 'FileReader', FileReaderMock);
stubs.set(goog.fs, 'sliceBlob', fsSliceBlobMock);
}
function tearDown() {
stubs.reset();
}
/**
* Makes the blobHasher read chunks from the blob and hash it. The number of
* reads shall not exceed a pre-determined number (typically blob size / chunk
* size) for computing hash. This function fails fast (after maxReads is
* reached), assuming that the hasher failed to generate hashes. This prevents
* the test suite from going into infinite loop.
* @param {!goog.crypt.BlobHasher} blobHasher Hasher in action.
* @param {number} maxReads Max number of read attempts.
*/
function readFromBlob(blobHasher, maxReads) {
var counter = 0;
while (blobHasher.fileReader_ && blobHasher.fileReader_.isLoading() &&
counter <= maxReads) {
blobHasher.fileReader_.mockLoad();
counter++;
}
assertTrue(counter <= maxReads);
return counter;
}
function testBasicOperations() {
if (!window.Blob) {
return;
}
// Test hashing with one chunk.
var hashFn = new goog.crypt.Md5();
var blobHasher = new goog.crypt.BlobHasher(hashFn);
var blob = new BlobMock('The quick brown fox jumps over the lazy dog');
blobHasher.hash(blob);
readFromBlob(blobHasher, 1);
assertEquals('9e107d9d372bb6826bd81d3542a419d6',
goog.crypt.byteArrayToHex(blobHasher.getHash()));
// Test hashing with multiple chunks.
blobHasher = new goog.crypt.BlobHasher(hashFn, 7);
blobHasher.hash(blob);
readFromBlob(blobHasher, Math.ceil(blob.size / 7));
assertEquals('9e107d9d372bb6826bd81d3542a419d6',
goog.crypt.byteArrayToHex(blobHasher.getHash()));
// Test hashing with no chunks.
blob = new BlobMock('');
blobHasher.hash(blob);
readFromBlob(blobHasher, 1);
assertEquals('d41d8cd98f00b204e9800998ecf8427e',
goog.crypt.byteArrayToHex(blobHasher.getHash()));
}
function testNormalFlow() {
if (!window.Blob) {
return;
}
// Test the flow with one chunk.
var hashFn = new goog.crypt.Md5();
var blobHasher = new goog.crypt.BlobHasher(hashFn, 13);
var blob = new BlobMock('short');
var startedEvents = 0;
var progressEvents = 0;
var completeEvents = 0;
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.STARTED,
function() { ++startedEvents; });
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.PROGRESS,
function() { ++progressEvents; });
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.COMPLETE,
function() { ++completeEvents; });
blobHasher.hash(blob);
assertEquals(1, startedEvents);
assertEquals(0, progressEvents);
assertEquals(0, completeEvents);
readFromBlob(blobHasher, 1);
assertEquals(1, startedEvents);
assertEquals(1, progressEvents);
assertEquals(1, completeEvents);
// Test the flow with multiple chunks.
blob = new BlobMock('The quick brown fox jumps over the lazy dog');
startedEvents = 0;
progressEvents = 0;
completeEvents = 0;
var progressLoops = 0;
blobHasher.hash(blob);
assertEquals(1, startedEvents);
assertEquals(0, progressEvents);
assertEquals(0, completeEvents);
progressLoops = readFromBlob(blobHasher, Math.ceil(blob.size / 13));
assertEquals(1, startedEvents);
assertEquals(progressLoops, progressEvents);
assertEquals(1, completeEvents);
}
function testAbortsAndErrors() {
if (!window.Blob) {
return;
}
var hashFn = new goog.crypt.Md5();
var blobHasher = new goog.crypt.BlobHasher(hashFn, 13);
var blob = new BlobMock('The quick brown fox jumps over the lazy dog');
var abortEvents = 0;
var errorEvents = 0;
var completeEvents = 0;
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.ABORT,
function() { ++abortEvents; });
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.ERROR,
function() { ++errorEvents; });
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.COMPLETE,
function() { ++completeEvents; });
// Immediate abort.
blobHasher.hash(blob);
assertEquals(0, abortEvents);
assertEquals(0, errorEvents);
assertEquals(0, completeEvents);
blobHasher.abort();
blobHasher.abort();
assertEquals(1, abortEvents);
assertEquals(0, errorEvents);
assertEquals(0, completeEvents);
abortEvents = 0;
// Delayed abort.
blobHasher.hash(blob);
blobHasher.fileReader_.mockLoad();
assertEquals(0, abortEvents);
assertEquals(0, errorEvents);
assertEquals(0, completeEvents);
blobHasher.abort();
blobHasher.abort();
assertEquals(1, abortEvents);
assertEquals(0, errorEvents);
assertEquals(0, completeEvents);
abortEvents = 0;
// Immediate error.
blobHasher.hash(blob);
blobHasher.fileReader_.mockError();
assertEquals(0, abortEvents);
assertEquals(1, errorEvents);
assertEquals(0, completeEvents);
errorEvents = 0;
// Delayed error.
blobHasher.hash(blob);
blobHasher.fileReader_.mockLoad();
blobHasher.fileReader_.mockError();
assertEquals(0, abortEvents);
assertEquals(1, errorEvents);
assertEquals(0, completeEvents);
abortEvents = 0;
}
function testBasicThrottling() {
if (!window.Blob) {
return;
}
var hashFn = new goog.crypt.Md5();
var blobHasher = new goog.crypt.BlobHasher(hashFn, 5);
var blob = new BlobMock('The quick brown fox jumps over the lazy dog');
var throttledEvents = 0;
var completeEvents = 0;
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.THROTTLED,
function() { ++throttledEvents; });
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.COMPLETE,
function() { ++completeEvents; });
// Start a throttled hash. No chunks should be processed yet.
blobHasher.setHashingLimit(0);
assertEquals(0, throttledEvents);
blobHasher.hash(blob);
assertEquals(1, throttledEvents);
assertEquals(0, blobHasher.getBytesProcessed());
assertNull(blobHasher.fileReader_);
// One chunk should be processed.
blobHasher.setHashingLimit(4);
assertEquals(1, throttledEvents);
assertEquals(1, readFromBlob(blobHasher, 1));
assertEquals(2, throttledEvents);
assertEquals(4, blobHasher.getBytesProcessed());
// One more chunk should be processed.
blobHasher.setHashingLimit(5);
assertEquals(2, throttledEvents);
assertEquals(1, readFromBlob(blobHasher, 1));
assertEquals(3, throttledEvents);
assertEquals(5, blobHasher.getBytesProcessed());
// Two more chunks should be processed.
blobHasher.setHashingLimit(15);
assertEquals(3, throttledEvents);
assertEquals(2, readFromBlob(blobHasher, 2));
assertEquals(4, throttledEvents);
assertEquals(15, blobHasher.getBytesProcessed());
// The entire blob should be processed.
blobHasher.setHashingLimit(Infinity);
var expectedChunks = Math.ceil(blob.size / 5) - 3;
assertEquals(expectedChunks, readFromBlob(blobHasher, expectedChunks));
assertEquals(4, throttledEvents);
assertEquals(1, completeEvents);
assertEquals('9e107d9d372bb6826bd81d3542a419d6',
goog.crypt.byteArrayToHex(blobHasher.getHash()));
}
function testLengthZeroThrottling() {
if (!window.Blob) {
return;
}
var hashFn = new goog.crypt.Md5();
var blobHasher = new goog.crypt.BlobHasher(hashFn);
var throttledEvents = 0;
var completeEvents = 0;
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.THROTTLED,
function() { ++throttledEvents; });
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.COMPLETE,
function() { ++completeEvents; });
// Test throttling with length 0 blob.
var blob = new BlobMock('');
blobHasher.setHashingLimit(0);
blobHasher.hash(blob);
assertEquals(0, throttledEvents);
assertEquals(1, completeEvents);
assertEquals('d41d8cd98f00b204e9800998ecf8427e',
goog.crypt.byteArrayToHex(blobHasher.getHash()));
}
function testAbortsAndErrorsWhileThrottling() {
if (!window.Blob) {
return;
}
var hashFn = new goog.crypt.Md5();
var blobHasher = new goog.crypt.BlobHasher(hashFn, 5);
var blob = new BlobMock('The quick brown fox jumps over the lazy dog');
var abortEvents = 0;
var errorEvents = 0;
var throttledEvents = 0;
var completeEvents = 0;
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.ABORT,
function() { ++abortEvents; });
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.ERROR,
function() { ++errorEvents; });
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.THROTTLED,
function() { ++throttledEvents; });
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.COMPLETE,
function() { ++completeEvents; });
// Test that processing cannot be continued after abort.
blobHasher.setHashingLimit(0);
blobHasher.hash(blob);
assertEquals(1, throttledEvents);
blobHasher.abort();
assertEquals(1, abortEvents);
blobHasher.setHashingLimit(10);
assertNull(blobHasher.fileReader_);
assertEquals(1, throttledEvents);
assertEquals(0, completeEvents);
assertNull(blobHasher.getHash());
// Test that processing cannot be continued after error.
blobHasher.hash(blob);
assertEquals(1, throttledEvents);
blobHasher.fileReader_.mockError();
assertEquals(1, errorEvents);
blobHasher.setHashingLimit(100);
assertNull(blobHasher.fileReader_);
assertEquals(1, throttledEvents);
assertEquals(0, completeEvents);
assertNull(blobHasher.getHash());
}
@@ -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;
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2014 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Closure Performance Tests - byteArrayToString</title>
<link rel="stylesheet" type="text/css"
href="../testing/performancetable.css"/>
<script src="../base.js"></script>
</head>
<body>
<h1>Closure Performance Tests - byteArrayToString</h1>
<div id="perfTable"></div>
<hr>
<script>
goog.require('goog.crypt.byteArrayToStringPerf');
</script>
</body>
</html>
@@ -0,0 +1,124 @@
// Copyright 2014 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 Performance test for different implementations of
* byteArrayToString.
*/
goog.provide('goog.crypt.byteArrayToStringPerf');
goog.require('goog.array');
goog.require('goog.dom');
goog.require('goog.testing.PerformanceTable');
goog.setTestOnly('goog.crypt.byteArrayToStringPerf');
var table = new goog.testing.PerformanceTable(
goog.dom.getElement('perfTable'));
var BYTES_LENGTH = Math.pow(2, 20);
var CHUNK_SIZE = 8192;
function getBytes() {
var bytes = [];
for (var i = 0; i < BYTES_LENGTH; i++) {
bytes.push('A'.charCodeAt(0));
}
return bytes;
}
function copyAndSpliceByteArray(bytes) {
// Copy the passed byte array since we're going to destroy it.
var remainingBytes = goog.array.clone(bytes);
var strings = [];
// Convert each chunk to a string.
while (remainingBytes.length) {
var chunk = goog.array.splice(remainingBytes, 0, CHUNK_SIZE);
strings.push(String.fromCharCode.apply(null, chunk));
}
return strings.join('');
}
function sliceByteArrayConcat(bytes) {
var str = '';
for (var i = 0; i < bytes.length; i += CHUNK_SIZE) {
var chunk = goog.array.slice(bytes, i, i + CHUNK_SIZE);
str += String.fromCharCode.apply(null, chunk);
}
return str;
}
function sliceByteArrayJoin(bytes) {
var strings = [];
for (var i = 0; i < bytes.length; i += CHUNK_SIZE) {
var chunk = goog.array.slice(bytes, i, i + CHUNK_SIZE);
strings.push(String.fromCharCode.apply(null, chunk));
}
return strings.join('');
}
function mapByteArray(bytes) {
var strings = goog.array.map(bytes, String.fromCharCode);
return strings.join('');
}
function forLoopByteArrayConcat(bytes) {
var str = '';
for (var i = 0; i < bytes.length; i++) {
str += String.fromCharCode(bytes[i]);
}
return str;
}
function forLoopByteArrayJoin(bytes) {
var strs = [];
for (var i = 0; i < bytes.length; i++) {
strs.push(String.fromCharCode(bytes[i]));
}
return strs.join('');
}
function run() {
var bytes = getBytes();
table.run(goog.partial(copyAndSpliceByteArray, getBytes()),
'Copy array and splice out chunks.');
table.run(goog.partial(sliceByteArrayConcat, getBytes()),
'Slice out copies of the byte array, concatenating results');
table.run(goog.partial(sliceByteArrayJoin, getBytes()),
'Slice out copies of the byte array, joining results');
table.run(goog.partial(forLoopByteArrayConcat, getBytes()),
'Use for loop with concat.');
table.run(goog.partial(forLoopByteArrayJoin, getBytes()),
'Use for loop with join.');
// Purposefully commented out. This ends up being tremendously expensive.
// table.run(goog.partial(mapByteArray, getBytes()),
// 'Use goog.array.map and fromCharCode.');
}
run();
@@ -0,0 +1,153 @@
// 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.asserts');
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
* @final
* @struct
*/
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;
};
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2012 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<title>Closure Unit Tests - goog.crypt.Cbc</title>
<script src="../base.js"></script>
<script>
goog.require('goog.crypt.CbcTest');
</script>
</head>
<body>
</body>
</html>
@@ -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'));
}
@@ -0,0 +1,173 @@
// 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');
goog.require('goog.asserts');
/**
* 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<number>} bytes Array of numbers representing characters.
* @return {string} Stringification of the array.
*/
goog.crypt.byteArrayToString = function(bytes) {
var CHUNK_SIZE = 8192;
// Special-case the simple case for speed's sake.
if (bytes.length < CHUNK_SIZE) {
return String.fromCharCode.apply(null, bytes);
}
// The remaining logic splits conversion by chunks since
// Function#apply() has a maximum parameter count.
// See discussion: http://goo.gl/LrWmZ9
var str = '';
for (var i = 0; i < bytes.length; i += CHUNK_SIZE) {
var chunk = goog.array.slice(bytes, i, i + CHUNK_SIZE);
str += String.fromCharCode.apply(null, chunk);
}
return str;
};
/**
* Turns an array of numbers into the hex string given by the concatenation of
* the hex values to which the numbers correspond.
* @param {Uint8Array|Array<number>} 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 {Uint8Array|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 {!ArrayBufferView|!Array<number>} bytes1 Byte array 1.
* @param {!ArrayBufferView|!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;
};
@@ -0,0 +1,85 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2009 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<!--
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Closure Performance Tests - UTF8 encoding and decoding</title>
<link rel="stylesheet" type="text/css"
href="../testing/performancetable.css"/>
<script src="../base.js"></script>
<script>
goog.require('goog.crypt');
goog.require('goog.string');
goog.require('goog.testing.PerformanceTable');
goog.require('goog.testing.jsunit');
</script>
</head>
<body>
<h1>Closure Performance Tests - UTF8 encoding and decoding</h1>
<p>
<strong>User-agent:</strong>
<script>document.write(navigator.userAgent);</script>
</p>
<div id="perfTable"></div>
<hr>
<script>
var table = new goog.testing.PerformanceTable(
goog.dom.getElement('perfTable'));
var STRING_LENGTH = 100000;
function testDecodeAscii() {
var arr = [];
for (var i = 0; i < STRING_LENGTH; i++) {
arr.push(120);
}
table.run(goog.partial(goog.crypt.utf8ByteArrayToString, arr),
'Decode UTF8 byte array with ASCII characters (1 byte / character)');
}
function testDecodeLatin() {
var arr = [];
for (var i = 0; i < STRING_LENGTH; i++) {
arr.push(195, 182);
}
table.run(goog.partial(goog.crypt.utf8ByteArrayToString, arr),
'Decode UTF8 byte array with Latin characters (2 bytes / character)');
}
function testDecodeLeftArrow() {
var arr = [];
for (var i = 0; i < STRING_LENGTH; i++) {
arr.push(226, 134, 144);
}
table.run(goog.partial(goog.crypt.utf8ByteArrayToString, arr),
'Decode UTF8 byte array with left arrows (3 bytes / character)');
}
function testEncodeAscii() {
var str = goog.string.repeat('x', STRING_LENGTH);
table.run(goog.partial(goog.crypt.stringToUtf8ByteArray, str),
'Encode ASCII string (1 byte / character)');
}
function testEncodeLatin() {
var str = goog.string.repeat('\u00F6', STRING_LENGTH);
table.run(goog.partial(goog.crypt.stringToUtf8ByteArray, str),
'Encode Latin string (2 bytes / character)');
}
function testEncodeLeftArrow() {
var str = goog.string.repeat('\u2190', STRING_LENGTH);
table.run(goog.partial(goog.crypt.stringToUtf8ByteArray, str),
'Encode left arrows (3 bytes / character)');
}
</script>
</body>
</html>
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2008 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.crypt
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.cryptTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,169 @@
// 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.
goog.provide('goog.cryptTest');
goog.setTestOnly('goog.cryptTest');
goog.require('goog.crypt');
goog.require('goog.string');
goog.require('goog.testing.jsunit');
var UTF8_RANGES_BYTE_ARRAY = [
0x00,
0x7F,
0xC2, 0x80,
0xDF, 0xBF,
0xE0, 0xA0, 0x80,
0xEF, 0xBF, 0xBF];
var UTF8_RANGES_STRING = '\u0000\u007F\u0080\u07FF\u0800\uFFFF';
function testStringToUtf8ByteArray() {
// Known encodings taken from Java's String.getBytes("UTF8")
assertArrayEquals('ASCII',
[72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100],
goog.crypt.stringToUtf8ByteArray('Hello, world'));
assertArrayEquals('Latin',
[83, 99, 104, 195, 182, 110],
goog.crypt.stringToUtf8ByteArray('Sch\u00f6n'));
assertArrayEquals('limits of the first 3 UTF-8 character ranges',
UTF8_RANGES_BYTE_ARRAY,
goog.crypt.stringToUtf8ByteArray(UTF8_RANGES_STRING));
}
function testUtf8ByteArrayToString() {
// Known encodings taken from Java's String.getBytes("UTF8")
assertEquals('ASCII', 'Hello, world', goog.crypt.utf8ByteArrayToString(
[72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100]));
assertEquals('Latin', 'Sch\u00f6n', goog.crypt.utf8ByteArrayToString(
[83, 99, 104, 195, 182, 110]));
assertEquals('limits of the first 3 UTF-8 character ranges',
UTF8_RANGES_STRING,
goog.crypt.utf8ByteArrayToString(UTF8_RANGES_BYTE_ARRAY));
}
/**
* Same as testUtf8ByteArrayToString but with Uint8Array instead of
* Array<number>.
*/
function testUint8ArrayToString() {
if (!goog.global.Uint8Array) {
// Uint8Array not supported.
return;
}
var arr = new Uint8Array(
[72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100]);
assertEquals('ASCII', 'Hello, world', goog.crypt.utf8ByteArrayToString(arr));
arr = new Uint8Array([83, 99, 104, 195, 182, 110]);
assertEquals('Latin', 'Sch\u00f6n', goog.crypt.utf8ByteArrayToString(arr));
arr = new Uint8Array(UTF8_RANGES_BYTE_ARRAY);
assertEquals('limits of the first 3 UTF-8 character ranges',
UTF8_RANGES_STRING,
goog.crypt.utf8ByteArrayToString(arr));
}
function testByteArrayToString() {
assertEquals('', goog.crypt.byteArrayToString([]));
assertEquals('abc', goog.crypt.byteArrayToString([97, 98, 99]));
}
function testHexToByteArray() {
assertElementsEquals(
[202, 254, 222, 173],
// Java magic number
goog.crypt.hexToByteArray('cafedead'));
assertElementsEquals(
[222, 173, 190, 239],
// IBM magic number
goog.crypt.hexToByteArray('DEADBEEF'));
}
function testByteArrayToHex() {
assertEquals(
// Java magic number
'cafedead',
goog.crypt.byteArrayToHex([202, 254, 222, 173]));
assertEquals(
// IBM magic number
'deadbeef',
goog.crypt.byteArrayToHex([222, 173, 190, 239]));
}
/** Same as testByteArrayToHex but with Uint8Array instead of Array<number>. */
function testUint8ArrayToHex() {
if (!goog.isDef(goog.global.Uint8Array)) {
// Uint8Array not supported.
return;
}
assertEquals(
// Java magic number
'cafedead',
goog.crypt.byteArrayToHex(new Uint8Array([202, 254, 222, 173])));
assertEquals(
// IBM magic number
'deadbeef',
goog.crypt.byteArrayToHex(new Uint8Array([222, 173, 190, 239])));
}
function testXorByteArray() {
assertElementsEquals(
[20, 83, 96, 66],
goog.crypt.xorByteArray([202, 254, 222, 173], [222, 173, 190, 239]));
}
/** Same as testXorByteArray but with Uint8Array instead of Array<number>. */
function testXorUint8Array() {
if (!goog.isDef(goog.global.Uint8Array)) {
// Uint8Array not supported.
return;
}
assertElementsEquals(
[20, 83, 96, 66],
goog.crypt.xorByteArray(
new Uint8Array([202, 254, 222, 173]),
new Uint8Array([222, 173, 190, 239])));
}
// Tests a one-megabyte byte array conversion to string.
// This would break on many JS implementations unless byteArrayToString
// split the input up.
// See discussion and bug report: http://goo.gl/LrWmZ9
function testByteArrayToStringCallStack() {
// One megabyte is 2 to the 20th.
var count = Math.pow(2, 20);
var bytes = [];
for (var i = 0; i < count; i++) {
bytes.push('A'.charCodeAt(0));
}
var str = goog.crypt.byteArrayToString(bytes);
assertEquals(goog.string.repeat('A', count), str);
}
@@ -0,0 +1,69 @@
// 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
* @struct
*/
goog.crypt.Hash = function() {
/**
* The block size for the hasher.
* @type {number}
*/
this.blockSize = -1;
};
/**
* 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;
@@ -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;
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2008 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.crypt.hash32
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.crypt.hash32Test');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,284 @@
// 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.
goog.provide('goog.crypt.hash32Test');
goog.setTestOnly('goog.crypt.hash32Test');
goog.require('goog.crypt.hash32');
goog.require('goog.testing.TestCase');
goog.require('goog.testing.jsunit');
// NOTE: This test uses a custom test case, see end of script block
// Test data based on known input/output pairs generated using
// http://go/hash.java
function testEncodeInteger() {
assertEquals(898813988, goog.crypt.hash32.encodeInteger(305419896));
}
function testEncodeByteArray() {
assertEquals(-1497024495,
goog.crypt.hash32.encodeByteArray([10, 20, 30, 40]));
assertEquals(-961586214,
goog.crypt.hash32.encodeByteArray([3, 1, 4, 1, 5, 9]));
assertEquals(-1482202299,
goog.crypt.hash32.encodeByteArray([127, 0, 0, 0, 123, 45]));
assertEquals(170907881,
goog.crypt.hash32.encodeByteArray([9, 1, 1]));
}
function testKnownByteArrays() {
for (var i = 0; i < byteArrays.length; i++) {
assertEquals(byteArrays[i],
goog.crypt.hash32.encodeByteArray(createByteArray(i)));
}
}
function testEncodeString() {
assertEquals(-937588052, goog.crypt.hash32.encodeString('Hello, world'));
assertEquals(62382810, goog.crypt.hash32.encodeString('Sch\xF6n'));
}
function testEncodeStringUtf8() {
assertEquals(-937588052,
goog.crypt.hash32.encodeStringUtf8('Hello, world'));
assertEquals(-833263351, goog.crypt.hash32.encodeStringUtf8('Sch\xF6n'));
assertEquals(-1771620293, goog.crypt.hash32.encodeStringUtf8(
'\u043A\u0440'));
}
function testEncodeString_ascii() {
assertEquals('For ascii characters UTF8 should be the same',
goog.crypt.hash32.encodeStringUtf8('abc123'),
goog.crypt.hash32.encodeString('abc123'));
assertEquals('For ascii characters UTF8 should be the same',
goog.crypt.hash32.encodeStringUtf8('The,quick.brown-fox'),
goog.crypt.hash32.encodeString('The,quick.brown-fox'));
assertNotEquals('For non-ascii characters UTF-8 encoding is different',
goog.crypt.hash32.encodeStringUtf8('Sch\xF6n'),
goog.crypt.hash32.encodeString('Sch\xF6n'));
}
function testEncodeString_poe() {
var poe = "Once upon a midnight dreary, while I pondered weak and weary," +
"Over many a quaint and curious volume of forgotten lore," +
"While I nodded, nearly napping, suddenly there came a tapping," +
"As of some one gently rapping, rapping at my chamber door." +
"`'Tis some visitor,' I muttered, `tapping at my chamber door -" +
"Only this, and nothing more.'" +
"Ah, distinctly I remember it was in the bleak December," +
"And each separate dying ember wrought its ghost upon the floor." +
"Eagerly I wished the morrow; - vainly I had sought to borrow" +
"From my books surcease of sorrow - sorrow for the lost Lenore -" +
"For the rare and radiant maiden whom the angels named Lenore -" +
"Nameless here for evermore." +
"And the silken sad uncertain rustling of each purple curtain" +
"Thrilled me - filled me with fantastic terrors never felt before;" +
"So that now, to still the beating of my heart, I stood repeating" +
"`'Tis some visitor entreating entrance at my chamber door -" +
"Some late visitor entreating entrance at my chamber door; -" +
"This it is, and nothing more,'" +
"Presently my soul grew stronger; hesitating then no longer," +
"`Sir,' said I, `or Madam, truly your forgiveness I implore;" +
"But the fact is I was napping, and so gently you came rapping," +
"And so faintly you came tapping, tapping at my chamber door," +
"That I scarce was sure I heard you' - here I opened wide the door; -" +
"Darkness there, and nothing more." +
"Deep into that darkness peering, long I stood there wondering, " +
"fearing," +
"Doubting, dreaming dreams no mortal ever dared to dream before" +
"But the silence was unbroken, and the darkness gave no token," +
"And the only word there spoken was the whispered word, `Lenore!'" +
"This I whispered, and an echo murmured back the word, `Lenore!'" +
"Merely this and nothing more." +
"Back into the chamber turning, all my soul within me burning," +
"Soon again I heard a tapping somewhat louder than before." +
"`Surely,\' said I, `surely that is something at my window lattice;" +
"Let me see then, what thereat is, and this mystery explore -" +
"Let my heart be still a moment and this mystery explore; -" +
"'Tis the wind and nothing more!'";
assertEquals(147608747, goog.crypt.hash32.encodeString(poe));
assertEquals(147608747, goog.crypt.hash32.encodeStringUtf8(poe));
}
function testBenchmarking() {
if (!testCase) return;
// Not a real test, just outputs some timing
function makeString(n) {
var str = [];
for (var i = 0; i < n; i++) {
str.push(String.fromCharCode(Math.round(Math.random() * 500)));
}
return str.join('');
}
for (var i = 0; i < 50000; i += 10000) {
var str = makeString(i);
var start = goog.now();
var hash = goog.crypt.hash32.encodeString(str);
var diff = goog.now() - start;
testCase.saveMessage(
'testBenchmarking : hashing ' + i + ' chars in ' + diff + 'ms');
}
}
function createByteArray(n) {
var arr = [];
for (var i = 0; i < n; i++) {
arr.push(i);
}
return arr;
}
var byteArrays = {
0: 1539411136,
1: 1773524747,
2: -254958930,
3: 1532114172,
4: 1923165449,
5: 1611874589,
6: 1502126780,
7: -751745251,
8: -292491321,
9: 1106193218,
10: -722791438,
11: -2130666060,
12: -259304553,
13: 871461192,
14: 865773084,
15: 1615738330,
16: -1836636447,
17: -485722519,
18: -120832227,
19: 1954449704,
20: 491312921,
21: -1955462668,
22: 168565425,
23: -105893922,
24: 620486614,
25: -1789602428,
26: 1765793554,
27: 1723370948,
28: -1275405721,
29: 140421019,
30: -1438726307,
31: 538438903,
32: -729123980,
33: 1213490939,
34: -1814248478,
35: 1943703398,
36: 1603073219,
37: -2139639543,
38: -694153941,
39: 137511516,
40: -249943726,
41: -1166126060,
42: 53464833,
43: -915350862,
44: 1306585409,
45: 1064798289,
46: 335555913,
47: 224485496,
48: 275599760,
49: 409559869,
50: 673770580,
51: -2113819879,
52: -791338727,
53: -1716479479,
54: 1795018816,
55: 2020139343,
56: -1652827750,
57: -1509632558,
58: 751641995,
59: -217881377,
60: -476546900,
61: -1893349644,
62: -729290332,
63: 1359899321,
64: 1811814306,
65: 2100363086,
66: -794920327,
67: -1667555017,
68: -549980099,
69: -21170740,
70: -1324143722,
71: 1406730195,
72: 2111381574,
73: -1667480052,
74: 1071811178,
75: -1080194099,
76: -181186882,
77: 268677507,
78: -546766334,
79: 555953522,
80: -981311675,
81: 1988867392,
82: 773172547,
83: 1160806722,
84: -1455460187,
85: 83493600,
86: 155365142,
87: 1714618071,
88: 1487712615,
89: -810670278,
90: 2031655097,
91: 1286349470,
92: -1873594211,
93: 1875867480,
94: -1096259787,
95: -1054968610,
96: -1723043458,
97: 1278708307,
98: -601104085,
99: 1497928579,
100: 1329732615,
101: -1281696190,
102: 1471511953,
103: -62666299,
104: 807569747,
105: -1927974759,
106: 1462243717,
107: -862975602,
108: 824369927,
109: -1448816781,
110: 1434162022,
111: -881501413,
112: -1554381107,
113: -1730883204,
114: 431236217,
115: 1877278608,
116: -673864625,
117: 143000665,
118: -596902829,
119: 1038860559,
120: 805884326,
121: -1536181710,
122: -1357373256,
123: 1405134250,
124: -860816481,
125: 1393578269,
126: -810682545,
127: -635515639
};
var testCase;
if (G_testRunner) {
testCase = new goog.testing.TestCase(document.title);
testCase.autoDiscoverTests();
G_testRunner.initialize(testCase);
}
@@ -0,0 +1,244 @@
// 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.dom');
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;
};
@@ -0,0 +1,160 @@
// 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);
*
* @author benyu@google.com (Jige Yu) - port to closure
*/
goog.provide('goog.crypt.Hmac');
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, uses the block size from the hasher, or 16 if it is
* not specified.
* @extends {goog.crypt.Hash}
* @final
* @struct
*/
goog.crypt.Hmac = function(hasher, key, opt_blockSize) {
goog.crypt.Hmac.base(this, 'constructor');
/**
* The underlying hasher to calculate hash.
*
* @type {!goog.crypt.Hash}
* @private
*/
this.hasher_ = hasher;
this.blockSize = opt_blockSize || hasher.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();
this.hasher_.reset();
}
// 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>|Uint8Array|string} message Data to Hmac.
* @return {!Array<number>} the digest of the given message.
*/
goog.crypt.Hmac.prototype.getHmac = function(message) {
this.reset();
this.update(message);
return this.digest();
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2011 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.crypt.sha1
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.crypt.HmacTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,142 @@
// 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.
goog.provide('goog.crypt.HmacTest');
goog.setTestOnly('goog.crypt.HmacTest');
goog.require('goog.crypt.Hmac');
goog.require('goog.crypt.Sha1');
goog.require('goog.crypt.hashTester');
goog.require('goog.testing.jsunit');
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 hexToBytes(str) {
var arr = [];
for (var i = 0; i < str.length; i += 2) {
arr.push(parseInt(str.substring(i, i + 2), 16));
}
return arr;
}
function bytesToHex(b) {
var hexchars = '0123456789abcdef';
var hexrep = new Array(b.length * 2);
for (var i = 0; i < b.length; ++i) {
hexrep[i * 2] = hexchars.charAt((b[i] >> 4) & 15);
hexrep[i * 2 + 1] = hexchars.charAt(b[i] & 15);
}
return hexrep.join('');
}
/**
* helper to get an hmac of the given message with the given key.
*/
function getHmac(key, message, opt_blockSize) {
var hasher = new goog.crypt.Sha1();
var hmacer = new goog.crypt.Hmac(hasher, key, opt_blockSize);
return bytesToHex(hmacer.getHmac(message));
}
function testBasicOperations() {
var hmac = new goog.crypt.Hmac(new goog.crypt.Sha1(), 'key', 64);
goog.crypt.hashTester.runBasicTests(hmac);
}
function testBasicOperationsWithNoBlockSize() {
var hmac = new goog.crypt.Hmac(new goog.crypt.Sha1(), 'key');
goog.crypt.hashTester.runBasicTests(hmac);
}
function testHmac() {
// HMAC test vectors from:
// http://tools.ietf.org/html/2202
assertEquals('test 1 failed',
'b617318655057264e28bc0b6fb378c8ef146be00',
getHmac(hexToBytes('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'),
stringToBytes('Hi There')));
assertEquals('test 2 failed',
'effcdf6ae5eb2fa2d27416d5f184df9c259a7c79',
getHmac(stringToBytes('Jefe'),
stringToBytes('what do ya want for nothing?')));
assertEquals('test 3 failed',
'125d7342b9ac11cd91a39af48aa17b4f63f175d3',
getHmac(hexToBytes('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),
hexToBytes('dddddddddddddddddddddddddddddddddddddddd' +
'dddddddddddddddddddddddddddddddddddddddd' +
'dddddddddddddddddddd')));
assertEquals('test 4 failed',
'4c9007f4026250c6bc8414f9bf50c86c2d7235da',
getHmac(hexToBytes('0102030405060708090a0b0c0d0e0f10111213141516171819'),
hexToBytes('cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd' +
'cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd' +
'cdcdcdcdcdcdcdcdcdcd')));
assertEquals('test 5 failed',
'4c1a03424b55e07fe7f27be1d58bb9324a9a5a04',
getHmac(hexToBytes('0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c'),
stringToBytes('Test With Truncation')));
assertEquals('test 6 failed',
'aa4ae5e15272d00e95705637ce8a3b55ed402112',
getHmac(hexToBytes('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),
stringToBytes(
'Test Using Larger Than Block-Size Key - Hash Key First')));
assertEquals('test 7 failed',
'b617318655057264e28bc0b6fb378c8ef146be00',
getHmac(hexToBytes('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'),
stringToBytes('Hi There'), 64));
assertEquals('test 8 failed',
'941f806707826395dc510add6a45ce9933db976e',
getHmac(hexToBytes('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'),
stringToBytes('Hi There'), 32));
}
/** Regression test for Bug 12863104 */
function testUpdateWithLongKey() {
// Calling update() then digest() should give the same result as just
// calling getHmac()
var key = hexToBytes('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
var message = 'Secret Message';
var hmac = new goog.crypt.Hmac(new goog.crypt.Sha1(), key);
hmac.update(message);
var result1 = bytesToHex(hmac.digest());
hmac.reset();
var result2 = bytesToHex(hmac.getHmac(message));
assertEquals('Results must be the same', result1, result2);
}
@@ -0,0 +1,435 @@
// 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}
* @final
* @struct
*/
goog.crypt.Md5 = function() {
goog.crypt.Md5.base(this, 'constructor');
this.blockSize = 512 / 8;
/**
* 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(this.blockSize);
/**
* 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 - this.blockSize;
// 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 += this.blockSize;
}
}
if (goog.isString(bytes)) {
while (i < opt_length) {
block[blockLength++] = bytes.charCodeAt(i++);
if (blockLength == this.blockSize) {
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 == this.blockSize) {
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 ?
this.blockSize :
this.blockSize * 2) - 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;
};
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2011 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Closure Performance Tests - goog.crypt.Md5</title>
<link rel="stylesheet" type="text/css" href="../testing/performancetable.css"/>
<script src="../base.js"></script>
<script>
goog.require('goog.crypt.Md5');
goog.require('goog.crypt.hashTester')
goog.require('goog.testing.PerformanceTable');
goog.require('goog.testing.PseudoRandom');
goog.require('goog.testing.jsunit');
</script>
</head>
<body>
<h1>Closure Performance Tests - goog.crypt.Md5</h1>
<p>
<strong>User-agent:</strong>
<script>document.write(navigator.userAgent);</script>
</p>
<script>
function testHashing() {
goog.crypt.hashTester.runPerfTests(function() {
return new goog.crypt.Md5();
}, 'MD5');
}
</script>
</body>
</html>
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2011 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.crypt.Md5
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.crypt.Md5Test');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,147 @@
// 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.
goog.provide('goog.crypt.Md5Test');
goog.setTestOnly('goog.crypt.Md5Test');
goog.require('goog.crypt');
goog.require('goog.crypt.Md5');
goog.require('goog.crypt.hashTester');
goog.require('goog.testing.jsunit');
var sixty = '123456789012345678901234567890123456789012345678901234567890';
function testBasicOperations() {
var md5 = new goog.crypt.Md5();
goog.crypt.hashTester.runBasicTests(md5);
}
function testBlockOperations() {
var md5 = new goog.crypt.Md5();
goog.crypt.hashTester.runBlockTests(md5, 64);
}
function testHashing() {
// Empty stream.
var md5 = new goog.crypt.Md5();
assertEquals('d41d8cd98f00b204e9800998ecf8427e',
goog.crypt.byteArrayToHex(md5.digest()));
// Simple stream.
md5.reset();
md5.update([97]);
assertEquals('0cc175b9c0f1b6a831c399e269772661',
goog.crypt.byteArrayToHex(md5.digest()));
// Simple stream with two updates.
md5.reset();
md5.update([97]);
md5.update('bc');
assertEquals('900150983cd24fb0d6963f7d28e17f72',
goog.crypt.byteArrayToHex(md5.digest()));
// RFC 1321 standard test.
md5.reset();
md5.update('abcdefghijklmnopqrstuvwxyz');
assertEquals('c3fcd3d76192e4007dfb496cca67e13b',
goog.crypt.byteArrayToHex(md5.digest()));
// RFC 1321 standard test with two updates.
md5.reset();
md5.update('message ');
md5.update('digest');
assertEquals('f96b697d7cb7938d525a2f31aaf161d0',
goog.crypt.byteArrayToHex(md5.digest()));
// RFC 1321 standard test with three updates.
md5.reset();
md5.update('ABCDEFGHIJKLMNOPQRSTUVWXYZ');
md5.update('abcdefghijklmnopqrstuvwxyz');
md5.update('0123456789');
assertEquals('d174ab98d277d9f5a5611c2c9f419d9f',
goog.crypt.byteArrayToHex(md5.digest()));
}
function testPadding() {
// Message + padding fits in two 64-byte blocks.
var md5 = new goog.crypt.Md5();
md5.update(sixty);
md5.update(sixty.substr(0, 59));
assertEquals('6261005311809757906e04c0d670492d',
goog.crypt.byteArrayToHex(md5.digest()));
// Message + padding does not fit in two 64-byte blocks.
md5.reset();
md5.update(sixty);
md5.update(sixty);
assertEquals('1d453b96d48d5e0cec4a20a71fecaa81',
goog.crypt.byteArrayToHex(md5.digest()));
}
function testTwoAccumulators() {
// Two accumulators in parallel.
var md5_A = new goog.crypt.Md5();
var md5_B = new goog.crypt.Md5();
md5_A.update(sixty);
md5_B.update(sixty);
md5_A.update(sixty + '1');
md5_B.update(sixty + '2');
assertEquals('0801d688cc107d4789ec8b9a4519f01f',
goog.crypt.byteArrayToHex(md5_A.digest()));
assertEquals('6e1a35ffc185d1e684d6ed281c0d4bd2',
goog.crypt.byteArrayToHex(md5_B.digest()));
}
function testCollision() {
// Check a known collision.
var A = [0xd1, 0x31, 0xdd, 0x02, 0xc5, 0xe6, 0xee, 0xc4,
0x69, 0x3d, 0x9a, 0x06, 0x98, 0xaf, 0xf9, 0x5c,
0x2f, 0xca, 0xb5, 0x87, 0x12, 0x46, 0x7e, 0xab,
0x40, 0x04, 0x58, 0x3e, 0xb8, 0xfb, 0x7f, 0x89,
0x55, 0xad, 0x34, 0x06, 0x09, 0xf4, 0xb3, 0x02,
0x83, 0xe4, 0x88, 0x83, 0x25, 0x71, 0x41, 0x5a,
0x08, 0x51, 0x25, 0xe8, 0xf7, 0xcd, 0xc9, 0x9f,
0xd9, 0x1d, 0xbd, 0xf2, 0x80, 0x37, 0x3c, 0x5b,
0xd8, 0x82, 0x3e, 0x31, 0x56, 0x34, 0x8f, 0x5b,
0xae, 0x6d, 0xac, 0xd4, 0x36, 0xc9, 0x19, 0xc6,
0xdd, 0x53, 0xe2, 0xb4, 0x87, 0xda, 0x03, 0xfd,
0x02, 0x39, 0x63, 0x06, 0xd2, 0x48, 0xcd, 0xa0,
0xe9, 0x9f, 0x33, 0x42, 0x0f, 0x57, 0x7e, 0xe8,
0xce, 0x54, 0xb6, 0x70, 0x80, 0xa8, 0x0d, 0x1e,
0xc6, 0x98, 0x21, 0xbc, 0xb6, 0xa8, 0x83, 0x93,
0x96, 0xf9, 0x65, 0x2b, 0x6f, 0xf7, 0x2a, 0x70];
var B = [0xd1, 0x31, 0xdd, 0x02, 0xc5, 0xe6, 0xee, 0xc4,
0x69, 0x3d, 0x9a, 0x06, 0x98, 0xaf, 0xf9, 0x5c,
0x2f, 0xca, 0xb5, 0x07, 0x12, 0x46, 0x7e, 0xab,
0x40, 0x04, 0x58, 0x3e, 0xb8, 0xfb, 0x7f, 0x89,
0x55, 0xad, 0x34, 0x06, 0x09, 0xf4, 0xb3, 0x02,
0x83, 0xe4, 0x88, 0x83, 0x25, 0xf1, 0x41, 0x5a,
0x08, 0x51, 0x25, 0xe8, 0xf7, 0xcd, 0xc9, 0x9f,
0xd9, 0x1d, 0xbd, 0x72, 0x80, 0x37, 0x3c, 0x5b,
0xd8, 0x82, 0x3e, 0x31, 0x56, 0x34, 0x8f, 0x5b,
0xae, 0x6d, 0xac, 0xd4, 0x36, 0xc9, 0x19, 0xc6,
0xdd, 0x53, 0xe2, 0x34, 0x87, 0xda, 0x03, 0xfd,
0x02, 0x39, 0x63, 0x06, 0xd2, 0x48, 0xcd, 0xa0,
0xe9, 0x9f, 0x33, 0x42, 0x0f, 0x57, 0x7e, 0xe8,
0xce, 0x54, 0xb6, 0x70, 0x80, 0x28, 0x0d, 0x1e,
0xc6, 0x98, 0x21, 0xbc, 0xb6, 0xa8, 0x83, 0x93,
0x96, 0xf9, 0x65, 0xab, 0x6f, 0xf7, 0x2a, 0x70];
var digest = '79054025255fb1a26e4bc422aef54eb4';
var md5_A = new goog.crypt.Md5();
var md5_B = new goog.crypt.Md5();
md5_A.update(A);
md5_B.update(B);
assertEquals(digest, goog.crypt.byteArrayToHex(md5_A.digest()));
assertEquals(digest, goog.crypt.byteArrayToHex(md5_B.digest()));
}
@@ -0,0 +1,128 @@
// 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.array');
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;
};
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2012 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<!--
Unit test for goog.crypt.pbkdf2
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.crypt.pbkdf2
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.crypt.pbkdf2Test');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,61 @@
// 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.
goog.provide('goog.crypt.pbkdf2Test');
goog.setTestOnly('goog.crypt.pbkdf2Test');
goog.require('goog.crypt');
goog.require('goog.crypt.pbkdf2');
goog.require('goog.testing.jsunit');
goog.require('goog.userAgent');
function testPBKDF2() {
// PBKDF2 test vectors from:
// http://tools.ietf.org/html/rfc6070
if (goog.userAgent.IE && goog.userAgent.isVersionOrHigher('7')) {
return;
}
var testPassword = goog.crypt.stringToByteArray('password');
var testSalt = goog.crypt.stringToByteArray('salt');
assertElementsEquals(
goog.crypt.hexToByteArray('0c60c80f961f0e71f3a9b524af6012062fe037a6'),
goog.crypt.pbkdf2.deriveKeySha1(testPassword, testSalt, 1, 160));
assertElementsEquals(
goog.crypt.hexToByteArray('ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957'),
goog.crypt.pbkdf2.deriveKeySha1(testPassword, testSalt, 2, 160));
assertElementsEquals(
goog.crypt.hexToByteArray('4b007901b765489abead49d926f721d065a429c1'),
goog.crypt.pbkdf2.deriveKeySha1(testPassword, testSalt, 4096, 160));
testPassword = goog.crypt.stringToByteArray('passwordPASSWORDpassword');
testSalt =
goog.crypt.stringToByteArray('saltSALTsaltSALTsaltSALTsaltSALTsalt');
assertElementsEquals(
goog.crypt.hexToByteArray(
'3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038'),
goog.crypt.pbkdf2.deriveKeySha1(testPassword, testSalt, 4096, 200));
testPassword = goog.crypt.stringToByteArray('pass\0word');
testSalt = goog.crypt.stringToByteArray('sa\0lt');
assertElementsEquals(
goog.crypt.hexToByteArray('56fa6aa75548099dcc37d7f03425e0c3'),
goog.crypt.pbkdf2.deriveKeySha1(testPassword, testSalt, 4096, 128));
}
@@ -0,0 +1,311 @@
// 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
*
* Note: The idiom expr|0 is used to provide a type-hint to the VM, in order
* to avoid unnecessary uint32-double-uint32 roundtripping.
*/
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}
* @final
* @struct
*/
goog.crypt.Sha1 = function() {
goog.crypt.Sha1.base(this, 'constructor');
this.blockSize = 512 / 8;
/**
* 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 < this.blockSize; ++i) {
this.pad_[i] = 0;
}
/**
* @private {number}
*/
this.inbuf_ = 0;
/**
* @private {number}
*/
this.total_ = 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_;
var i;
// get 16 big endian words
if (goog.isString(buf)) {
for (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))) & 0xffffffff) | 0;
opt_offset += 4;
}
} else {
for (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])) & 0xffffffff) | 0;
opt_offset += 4;
}
}
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, t;
// Steps 0-16.
for (i = 0; i < 16; i++) {
f = d ^ (b & (c ^ d));
k = 0x5a827999;
t = ((((a << 5) | (a >>> 27)) + f + e + k + W[i]) & 0xffffffff) | 0;
e = d;
d = c;
c = (((b << 30) | (b >>> 2)) & 0xffffffff) | 0;
b = a;
a = t;
}
// Steps 16-80. W is formally described as an 80-word array, and usually
// computed that way. However, only 16 elements are needed for any iteration
// so we compute W on the fly and keep only the last 16 values. This improves
// performance by about 10% on Chrome 35.
for (i = 16; i < 80; i++) {
t = W[(i - 3) & 15] ^ W[(i - 8) & 15] ^ W[(i - 14) & 15] ^ W[i & 15];
W[i & 15] = (((t << 1) | (t >>> 31)) & 0xfffffff) | 0;
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;
}
}
t = ((((a << 5) | (a >>> 27)) + f + e + k + W[i & 15]) & 0xffffffff) | 0;
e = d;
d = c;
c = (((b << 30) | (b >>> 2)) & 0xffffffff) | 0;
b = a;
a = t;
}
this.chain_[0] = ((this.chain_[0] + a) & 0xfffffff) | 0;
this.chain_[1] = ((this.chain_[1] + b) & 0xfffffff) | 0;
this.chain_[2] = ((this.chain_[2] + c) & 0xfffffff) | 0;
this.chain_[3] = ((this.chain_[3] + d) & 0xfffffff) | 0;
this.chain_[4] = ((this.chain_[4] + e) & 0xfffffff) | 0;
};
/** @override */
goog.crypt.Sha1.prototype.update = function(bytes, opt_length) {
// TODO(johnlenz): tighten the function signature and remove this check
if (bytes === null) {
return;
}
if (!goog.isDef(opt_length)) {
opt_length = bytes.length;
}
opt_length = (bytes.length < opt_length) ? bytes.length : opt_length;
var lengthMinusBlock = opt_length - this.blockSize;
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 += this.blockSize;
}
}
if (goog.isString(bytes)) {
while (n < opt_length) {
buf[inbuf] = bytes.charCodeAt(n);
++inbuf;
++n;
if (inbuf == this.blockSize) {
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 == this.blockSize) {
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;
var i;
// Add pad 0x80 0x00*.
if (this.inbuf_ < 56) {
this.update(this.pad_, 56 - this.inbuf_);
} else {
this.update(this.pad_, this.blockSize - (this.inbuf_ - 56));
}
// Add # bits.
for (i = this.blockSize - 1; i >= 56; i--) {
this.buf_[i] = totalBits & 255;
totalBits /= 256; // Don't use bit-shifting here!
}
this.compress_(this.buf_);
var n = 0;
for (i = 0; i < 5; i++) {
for (var j = 24; j >= 0; j -= 8) {
digest[n] = (this.chain_[i] >> j) & 255;
++n;
}
}
return digest;
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2010 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.crypt.shaX Monte Carlo KATs
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.crypt.ShaMcTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,130 @@
/* Warning: These tests take about 8 minutes to run. */
goog.provide('goog.crypt.ShaMcTest');
goog.setTestOnly('goog.crypt.ShaMcTest');
goog.require('goog.crypt');
goog.require('goog.crypt.Sha1');
goog.require('goog.crypt.Sha224');
goog.require('goog.crypt.Sha256');
goog.require('goog.crypt.Sha384');
goog.require('goog.crypt.Sha512');
goog.require('goog.testing.jsunit');
goog.require('goog.userAgent');
function testSha1() {
var sha = new goog.crypt.Sha1();
var initial_state = 'Sha1';
var count = 1638;
var state = goog.crypt.stringToByteArray(initial_state);
var digest;
for (var i = 0; i < count; i++) {
sha.reset();
sha.update(state);
digest = sha.digest();
state = goog.array.concat(digest, state);
}
assertEquals(32764, state.length);
assertEquals('9da05831d6441141b62545eb4bf3bcc92b8c8276',
goog.crypt.byteArrayToHex(digest));
sha.reset();
for (var i = 0; i < (32764 + 10); i++) {
sha.update(state, i);
}
assertEquals('70a4f1a7b523d9989a1cfd0f512a906abbfefe5f',
goog.crypt.byteArrayToHex(sha.digest()));
}
function testSha224() {
var sha = new goog.crypt.Sha224();
var initial_state = 'Sha224';
var count = 1170;
var state = goog.crypt.stringToByteArray(initial_state);
var digest;
for (var i = 0; i < count; i++) {
sha.reset();
sha.update(state);
digest = sha.digest();
state = goog.array.concat(digest, state);
}
assertEquals(32766, state.length);
assertEquals('c7636f5369a057fde53f3a70cb2880795a35af53db38ed8a04cbcbfe',
goog.crypt.byteArrayToHex(digest));
sha.reset();
for (var i = 0; i < (32766 + 10); i++) {
sha.update(state, i);
}
assertEquals('69f7e71bbf9a7bd15832fa77e09cbe458dcea284ddb00a69eb3ed78a',
goog.crypt.byteArrayToHex(sha.digest()));
}
function testSha256() {
var sha = new goog.crypt.Sha256();
var initial_state = 'Sha256';
var count = 1024;
var state = goog.crypt.stringToByteArray(initial_state);
var digest;
for (var i = 0; i < count; i++) {
sha.reset();
sha.update(state);
digest = sha.digest();
state = goog.array.concat(digest, state);
}
assertEquals(32774, state.length);
assertEquals('bdc98db7476b58c33161211099b02c27da6bed3959a8b1d4e600f4d628ba0200',
goog.crypt.byteArrayToHex(digest));
sha.reset();
for (var i = 0; i < (32774 + 10); i++) {
sha.update(state, i);
}
assertEquals('26cc26f6429ce20c1b537d67cc288231a18de2a258d8bf529751439cd7c71d37',
goog.crypt.byteArrayToHex(sha.digest()));
}
function testSha384() {
var sha = new goog.crypt.Sha384();
var initial_state = 'Sha384';
var count = 682;
var state = goog.crypt.stringToByteArray(initial_state);
var digest;
for (var i = 0; i < count; i++) {
sha.reset();
sha.update(state);
digest = sha.digest();
state = goog.array.concat(digest, state);
}
assertEquals(32742, state.length);
assertEquals('1e7017a365fd31f6c439efb3eabef783a1e09ebcbb357bc4c9aac5fa9d731a167ee8105cd1c76159a1c27c56c5d1bc8c',
goog.crypt.byteArrayToHex(digest));
sha.reset();
for (var i = 0; i < (32742 + 10); i++) {
sha.update(state, i);
}
assertEquals('0cd4695e15f9089e767b2866e1728588d5cece4ad13e4943aa5bd5f9debbe133e2fac302851a2e90e13c318ace25fbb8',
goog.crypt.byteArrayToHex(sha.digest()));
}
function testSha512() {
var sha = new goog.crypt.Sha512();
var initial_state = 'Sha512';
var count = 512;
var state = goog.crypt.stringToByteArray(initial_state);
var digest;
for (var i = 0; i < count; i++) {
sha.reset();
sha.update(state);
digest = sha.digest();
state = goog.array.concat(digest, state);
}
assertEquals(32774, state.length);
assertEquals('5c9c25961a9171e2a79b9be65e05ce238752e7bfbaf3696c6ed63b8ee2735315d2cb58bf70a5f08dd70ecab029bd0725dcdd84dacd063ea9148cb3e5d7fa948a',
goog.crypt.byteArrayToHex(digest));
sha.reset();
for (var i = 0; i < (32774 + 10); i++) {
sha.update(state, i);
}
assertEquals('10a310e050cf9b2e9d4fc3c0a8f8e183c158ff28c23a42fd5b7777f449cfbe92655eee2bb42fb47c6900e001153b74a5db777b9b2d1543dc30fe98face94f106',
goog.crypt.byteArrayToHex(sha.digest()));
}
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2011 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Closure Performance Tests - goog.crypt.Sha1</title>
<link rel="stylesheet" type="text/css" href="../testing/performancetable.css"/>
<script src="../base.js"></script>
<script>
goog.require('goog.crypt.Sha1');
goog.require('goog.crypt.hashTester');
goog.require('goog.testing.PerformanceTable');
goog.require('goog.testing.PseudoRandom');
goog.require('goog.testing.jsunit');
</script>
</head>
<body>
<h1>Closure Performance Tests - goog.crypt.Sha1</h1>
<p>
<strong>User-agent:</strong>
<script>document.write(navigator.userAgent);</script>
</p>
<script>
function testHashing() {
goog.crypt.hashTester.runPerfTests(function() {
return new goog.crypt.Sha1();
}, 'SHA1');
}
</script>
</body>
</html>
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2010 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.crypt.sha1
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.crypt.Sha1Test');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,118 @@
// 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.
goog.provide('goog.crypt.Sha1Test');
goog.setTestOnly('goog.crypt.Sha1Test');
goog.require('goog.crypt');
goog.require('goog.crypt.Sha1');
goog.require('goog.crypt.hashTester');
goog.require('goog.testing.jsunit');
goog.require('goog.userAgent');
function testBasicOperations() {
var sha1 = new goog.crypt.Sha1();
goog.crypt.hashTester.runBasicTests(sha1);
}
function testBlockOperations() {
var sha1 = new goog.crypt.Sha1();
goog.crypt.hashTester.runBlockTests(sha1, 64);
}
function testHashing() {
// Test vectors from:
// csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf
// Empty stream.
var sha1 = new goog.crypt.Sha1();
assertEquals('da39a3ee5e6b4b0d3255bfef95601890afd80709',
goog.crypt.byteArrayToHex(sha1.digest()));
// Test one-block message.
sha1.reset();
sha1.update([0x61, 0x62, 0x63]);
assertEquals('a9993e364706816aba3e25717850c26c9cd0d89d',
goog.crypt.byteArrayToHex(sha1.digest()));
// Test multi-block message.
sha1.reset();
sha1.update('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq');
assertEquals('84983e441c3bd26ebaae4aa1f95129e5e54670f1',
goog.crypt.byteArrayToHex(sha1.digest()));
// The following test might cause timeouts on IE7.
if (!goog.userAgent.IE || goog.userAgent.isVersionOrHigher('8')) {
// Test long message.
var thousandAs = [];
for (var i = 0; i < 1000; ++i) {
thousandAs[i] = 0x61;
}
sha1.reset();
for (var i = 0; i < 1000; ++i) {
sha1.update(thousandAs);
}
assertEquals('34aa973cd4c4daa4f61eeb2bdbad27316534016f',
goog.crypt.byteArrayToHex(sha1.digest()));
}
// Test standard message.
sha1.reset();
sha1.update('The quick brown fox jumps over the lazy dog');
assertEquals('2fd4e1c67a2d28fced849ee1bb76e7391b93eb12',
goog.crypt.byteArrayToHex(sha1.digest()));
sha1.reset();
sha1.update(goog.string.repeat('a', 1024));
assertEquals('8eca554631df9ead14510e1a70ae48c70f9b9384',
goog.crypt.byteArrayToHex(sha1.digest()));
}
function testLength() {
var sha = new goog.crypt.Sha1();
// Test that truncating a message works.
sha.reset();
sha.update('abc');
assertEquals('a9993e364706816aba3e25717850c26c9cd0d89d',
goog.crypt.byteArrayToHex(sha.digest()));
sha.reset();
sha.update('abcde', 3);
assertEquals('a9993e364706816aba3e25717850c26c9cd0d89d',
goog.crypt.byteArrayToHex(sha.digest()));
// Test that lengths work correctly.
var message = goog.crypt.hexToByteArray(
'd9b28b643d16efc8a17a532c05deb79069421bf4cda67f58310ae3bc956e4720' +
'f9d2ab845d360fe8c19a734c25fed7b089623b14edc69f78512a03dcb58e6740');
// Lengths from 0 to 64.
sha.reset();
for (var i = 0; i < 64; i++) {
sha.update(message, i);
}
assertElementsEquals(goog.crypt.hexToByteArray(
'04df2fcf7b12b7735e0a3d05d9723702aa70de30'),
sha.digest());
// Lengths from 0 to 71 to include message overrun cases.
sha.reset();
for (var i = 0; i < 71; i++) {
sha.update(message, i);
}
assertElementsEquals(goog.crypt.hexToByteArray(
'f62df7546b7f70351a5c25bfd9e77ba90ca697f4'),
sha.digest());
}
@@ -0,0 +1,515 @@
// 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.
*
* 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.
* @param {number} numHashBlocks The size of output in 16-byte blocks.
* @param {!Array<number>} initHashBlocks The hash-specific initialization
* @constructor
* @extends {goog.crypt.Hash}
* @struct
*/
goog.crypt.Sha2 = function(numHashBlocks, initHashBlocks) {
goog.crypt.Sha2.base(this, 'constructor');
this.blockSize = goog.crypt.Sha2.BLOCKSIZE_;
/**
* A chunk holding the currently processed message bytes. Once the chunk has
* 64 bytes, we feed it into computeChunk_ function and reset this.chunk_.
* @private {!Array<number>|!Uint8Array}
*/
this.chunk_ = goog.global['Uint8Array'] ?
new Uint8Array(this.blockSize) : new Array(this.blockSize);
/**
* Current number of bytes in this.chunk_.
* @private {number}
*/
this.inChunk_ = 0;
/**
* Total number of bytes in currently processed message.
* @private {number}
*/
this.total_ = 0;
/**
* Holds the previous values of accumulated hash a-h in the computeChunk_
* function.
* @private {!Array<number>|!Int32Array}
*/
this.hash_ = [];
/**
* The number of output hash blocks (each block is 4 bytes long).
* @private {number}
*/
this.numHashBlocks_ = numHashBlocks;
/**
* @private {!Array<number>} initHashBlocks
*/
this.initHashBlocks_ = initHashBlocks;
/**
* Temporary array used in chunk computation. Allocate here as a
* member rather than as a local within computeChunk_() as a
* performance optimization to reduce the number of allocations and
* reduce garbage collection.
* @private {!Int32Array|!Array<number>}
*/
this.w_ = goog.global['Int32Array'] ? new Int32Array(64) : new Array(64);
if (!goog.isDef(goog.crypt.Sha2.Kx_)) {
// This is the first time this constructor has been called.
if (goog.global['Int32Array']) {
// Typed arrays exist
goog.crypt.Sha2.Kx_ = new Int32Array(goog.crypt.Sha2.K_);
} else {
// Typed arrays do not exist
goog.crypt.Sha2.Kx_ = goog.crypt.Sha2.K_;
}
}
this.reset();
};
goog.inherits(goog.crypt.Sha2, goog.crypt.Hash);
/**
* The block size
* @private {number}
*/
goog.crypt.Sha2.BLOCKSIZE_ = 512 / 8;
/**
* Contains data needed to pad messages less than BLOCK_SIZE_ bytes.
* @private {!Array<number>}
*/
goog.crypt.Sha2.PADDING_ = goog.array.concat(128,
goog.array.repeat(0, goog.crypt.Sha2.BLOCKSIZE_ - 1));
/** @override */
goog.crypt.Sha2.prototype.reset = function() {
this.inChunk_ = 0;
this.total_ = 0;
this.hash_ = goog.global['Int32Array'] ?
new Int32Array(this.initHashBlocks_) :
goog.array.clone(this.initHashBlocks_);
};
/** Helper function to precompute a message schedule.
*
* TODO(dlg): This is essentially static...
*
* @param {!Array|Uint8Array|string} buf Data used for the update.
* @param {number=} opt_offset Optional offset into the data.
*
* @return {!Array|Int32Array} w A 64-element array of uint32s, representing the
* prescheduled message, with round constants already added.
*/
goog.crypt.Sha2.prototype.preschedule = function(buf, opt_offset) {
if (!opt_offset) {
opt_offset = 0;
}
// Divide the chunk into 16 32-bit-words.
var w = goog.global['Int32Array'] ? new Int32Array(64) : new Array(64);
var i;
// get 16 big endian words
if (goog.isString(buf)) {
for (i = 0; i < 16; i++) {
w[i] = (((buf.charCodeAt(opt_offset) << 24) |
(buf.charCodeAt(opt_offset + 1) << 16) |
(buf.charCodeAt(opt_offset + 2) << 8) |
(buf.charCodeAt(opt_offset + 3))) & 0xffffffff) | 0;
opt_offset += 4;
}
} else {
for (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])) & 0xffffffff) | 0;
opt_offset += 4;
}
}
for (i = 16; i < 64; i++) {
var w_15 = w[i - 15] | 0;
var s0 = ((((w_15 >>> 7) | (w_15 << 25)) ^
((w_15 >>> 18) | (w_15 << 14)) ^
(w_15 >>> 3)) & 0xffffffff) | 0;
var w_2 = w[i - 2] | 0;
var s1 = ((((w_2 >>> 17) | (w_2 << 15)) ^
((w_2 >>> 19) | (w_2 << 13)) ^
(w_2 >>> 10)) & 0xffffffff) | 0;
// As a performance optimization, construct the sum a pair at a time
// with casting to integer (bitwise OR) to eliminate unnecessary
// double<->integer conversions.
var partialSum1 = (((w[i - 16] | 0) + s0) & 0xffffffff) | 0;
var partialSum2 = (((w[i - 7] | 0) + s1) & 0xffffffff) | 0;
w[i] = (partialSum1 + partialSum2) | 0;
}
for (i = 0; i < 64; i++) {
w[i] = (w[i] + (goog.crypt.Sha2.Kx_[i] | 0) | 0);
}
return w;
};
/**
* Update this goog.crypt.Sha2 instance's chaining value using a precomputed
* schedule. Increment the total digested bytes by 64.
*
* @param {Array.<Uint32Array[80]>} precomputed_schedule An SHA2-32b schedule
* precomputed by goog.crypt.Sha2.preschedule.
*/
goog.crypt.Sha2.prototype.scheduledUpdate = function(precomputed_schedule) {
var w = precomputed_schedule;
var a = this.hash_[0] | 0;
var b = this.hash_[1] | 0;
var c = this.hash_[2] | 0;
var d = this.hash_[3] | 0;
var e = this.hash_[4] | 0;
var f = this.hash_[5] | 0;
var g = this.hash_[6] | 0;
var h = this.hash_[7] | 0;
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) | 0;
var S1 = ((e >>> 6) | (e << 26)) ^
((e >>> 11) | (e << 21)) ^
((e >>> 25) | (e << 7));
var ch = ((e & f) ^ ((~ e) & g));
var partialSum1 = (h + S1) | 0;
var partialSum3 = (ch + (w[i] | 0)) | 0;
var t1 = (partialSum1 + partialSum3) | 0;
h = g;
g = f;
f = e;
e = (d + t1) | 0;
d = c;
c = b;
b = a;
a = (t1 + t2) | 0;
}
this.total_ += this.blockSize;
this.hash_[0] = (this.hash_[0] + a) | 0;
this.hash_[1] = (this.hash_[1] + b) | 0;
this.hash_[2] = (this.hash_[2] + c) | 0;
this.hash_[3] = (this.hash_[3] + d) | 0;
this.hash_[4] = (this.hash_[4] + e) | 0;
this.hash_[5] = (this.hash_[5] + f) | 0;
this.hash_[6] = (this.hash_[6] + g) | 0;
this.hash_[7] = (this.hash_[7] + h) | 0;
};
/**
* Helper function to compute the hashes for a given 512-bit message chunk.
* @param {!Array|Uint8Array|string} buf Data used for the update.
* @param {number=} opt_offset Optional offset into the data.
*
* @private
*/
goog.crypt.Sha2.prototype.computeChunk_ = function(buf, opt_offset) {
// Performance notes:
// - Sum numbers pairwise and cast to integer using | after to avoid
// unnecessary double<->integer conversions.
// - The message schedule is updated "in-place" to reduce the amount
// of garbage that has to be collected.
if (!opt_offset) {
opt_offset = 0;
}
// The message schedule.
var w = this.w_;
var i;
// Divide the chunk into 16 32-bit-words.
if (goog.isString(buf)) {
for (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.
// TODO(someone): Bug resolved 2013-02-27 at:
// https://bugs.webkit.org/show_bug.cgi?id=109036,
// https://bugs.webkit.org/show_bug.cgi?id=110991
// (Is this still needed? If there's no significant performance advantage,
// current code is clearer.)
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 (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;
}
}
var a = this.hash_[0] | 0;
var b = this.hash_[1] | 0;
var c = this.hash_[2] | 0;
var d = this.hash_[3] | 0;
var e = this.hash_[4] | 0;
var f = this.hash_[5] | 0;
var g = this.hash_[6] | 0;
var h = this.hash_[7] | 0;
// Do steps 0-16.
for (i = 0; i < 16; 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) | 0;
var S1 = ((e >>> 6) | (e << 26)) ^
((e >>> 11) | (e << 21)) ^
((e >>> 25) | (e << 7));
var ch = ((e & f) ^ ((~ e) & g));
var partialSum1 = (h + S1) | 0;
var partialSum2 = (ch + (goog.crypt.Sha2.Kx_[i] | 0)) | 0;
var partialSum3 = (partialSum2 + (w[i] | 0)) | 0;
var t1 = (partialSum1 + partialSum3) | 0;
h = g;
g = f;
f = e;
e = (d + t1) | 0;
d = c;
c = b;
b = a;
a = (t1 + t2) | 0;
}
// Do steps 16-64.
for (i = 16; i < 64; i++) {
var w_15 = w[(i - 15) & 15] | 0;
var s0 = ((w_15 >>> 7) | (w_15 << 25)) ^
((w_15 >>> 18) | (w_15 << 14)) ^
(w_15 >>> 3);
var w_2 = w[(i - 2) & 15] | 0;
var s1 = ((w_2 >>> 17) | (w_2 << 15)) ^
((w_2 >>> 19) | (w_2 << 13)) ^
(w_2 >>> 10);
var partialSum1 = ((w[(i - 16) & 15] | 0) + s0) | 0;
var partialSum2 = ((w[(i - 7) & 15] | 0) + s1) | 0;
w[i & 15] = (partialSum1 + partialSum2) | 0;
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) | 0;
var S1 = ((e >>> 6) | (e << 26)) ^
((e >>> 11) | (e << 21)) ^
((e >>> 25) | (e << 7));
var ch = ((e & f) ^ ((~ e) & g));
var partialSum1 = (h + S1) | 0;
var partialSum2 = (ch + (goog.crypt.Sha2.Kx_[i] | 0)) | 0;
var partialSum3 = (partialSum2 + (w[i & 15] | 0)) | 0;
var t1 = (partialSum1 + partialSum3) | 0;
h = g;
g = f;
f = e;
e = (d + t1) | 0;
d = c;
c = b;
b = a;
a = (t1 + t2) | 0;
}
this.hash_[0] = (this.hash_[0] + a) | 0;
this.hash_[1] = (this.hash_[1] + b) | 0;
this.hash_[2] = (this.hash_[2] + c) | 0;
this.hash_[3] = (this.hash_[3] + d) | 0;
this.hash_[4] = (this.hash_[4] + e) | 0;
this.hash_[5] = (this.hash_[5] + f) | 0;
this.hash_[6] = (this.hash_[6] + g) | 0;
this.hash_[7] = (this.hash_[7] + h) | 0;
};
/** @override */
goog.crypt.Sha2.prototype.update = function(bytes, opt_length) {
if (!goog.isDef(opt_length)) {
opt_length = bytes.length;
}
opt_length = (bytes.length < opt_length) ? bytes.length : opt_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_. If the message isn't a multiple of 64 bytes (512 bits)
// we store the partial chunk in this.chunk_, and the index of the end
// of the partial chunk into this.inChunk_.
var n = 0;
var inbuf = this.inChunk_;
var buf = this.chunk_;
var lengthMinusBlock = opt_length - this.blockSize;
// 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.computeChunk_(bytes, n);
n += this.blockSize;
}
}
if (goog.isString(bytes)) {
while (n < opt_length) {
buf[inbuf] = bytes.charCodeAt(n);
++inbuf;
++n;
if (inbuf == this.blockSize) {
this.computeChunk_(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 == this.blockSize) {
this.computeChunk_(buf);
inbuf = 0;
// Jump to the outer loop so we use the full-block optimization.
break;
}
}
}
}
// Save the current position in this.chunk_ to support partial updates.
this.inChunk_ = inbuf;
// Update the total message bytes processed.
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(goog.crypt.Sha2.PADDING_, 56 - this.inChunk_);
} else {
this.update(goog.crypt.Sha2.PADDING_,
this.blockSize - (this.inChunk_ - 56));
}
// Append number of bits as a big-endian 64-bit uint.
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's 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 {!Array<number>}
*/
goog.crypt.Sha2.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
];
/**
* Sha2.K as an Int32Array if this JS supports typed arrays; otherwise,
* the same array as Sha2.K.
*
* The compiler cannot remove an Int32Array, even if it is not needed
* (There are certain cases where creating an Int32Array is not
* side-effect free). Instead, the first time we construct a Sha2
* instance, we convert or assign Sha2.K as appropriate.
* @private {undefined|!Array<number>|!Int32Array}
*/
goog.crypt.Sha2.Kx_;
@@ -0,0 +1,50 @@
// 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}
* @final
* @struct
*/
goog.crypt.Sha224 = function() {
goog.crypt.Sha224.base(this, 'constructor',
7, goog.crypt.Sha224.INIT_HASH_BLOCK_);
};
goog.inherits(goog.crypt.Sha224, goog.crypt.Sha2);
/** @private {!Array<number>} */
goog.crypt.Sha224.INIT_HASH_BLOCK_ = [
0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939,
0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4];
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2012 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Closure Performance Tests - goog.crypt.Sha224</title>
<link rel="stylesheet" type="text/css" href="../testing/performancetable.css"/>
<script src="../base.js"></script>
<script>
goog.require('goog.crypt.Sha224');
goog.require('goog.crypt.hashTester');
goog.require('goog.testing.PerformanceTable');
goog.require('goog.testing.PseudoRandom');
goog.require('goog.testing.jsunit');
</script>
</head>
<body>
<h1>Closure Performance Tests - goog.crypt.Sha224</h1>
<p>
<strong>User-agent:</strong>
<script>document.write(navigator.userAgent);</script>
</p>
<script>
function testHashing() {
goog.crypt.hashTester.runPerfTests(function() {
return new goog.crypt.Sha224();
}, 'SHA224');
}
</script>
</body>
</html>
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2012 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.crypt.sha224
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.crypt.Sha224Test');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,75 @@
// 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.
goog.provide('goog.crypt.Sha224Test');
goog.setTestOnly('goog.crypt.Sha224Test');
goog.require('goog.crypt');
goog.require('goog.crypt.Sha224');
goog.require('goog.crypt.hashTester');
goog.require('goog.testing.jsunit');
function testBasicOperations() {
var sha224 = new goog.crypt.Sha224();
goog.crypt.hashTester.runBasicTests(sha224);
}
function testHashing() {
// Some test vectors from:
// csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf
var sha224 = new goog.crypt.Sha224();
// NIST one block test vector.
sha224.update(goog.crypt.stringToByteArray('abc'));
assertEquals(
'23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7',
goog.crypt.byteArrayToHex(sha224.digest()));
// NIST multi-block test vector.
sha224.reset();
sha224.update(
goog.crypt.stringToByteArray(
'abcdbcdecdefdefgefghfghighij' +
'hijkijkljklmklmnlmnomnopnopq'));
assertEquals(
'75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525',
goog.crypt.byteArrayToHex(sha224.digest()));
// Message larger than one block (but less than two).
sha224.reset();
var biggerThanOneBlock = 'abcdbcdecdefdefgefghfghighij' +
'hijkijkljklmklmnlmnomnopnopq' +
'asdfljhr78yasdfljh45opa78sdf' +
'120839414104897aavnasdfafasd';
assertTrue(biggerThanOneBlock.length > goog.crypt.Sha2.BLOCKSIZE_ &&
biggerThanOneBlock.length < 2 * goog.crypt.Sha2.BLOCKSIZE_);
sha224.update(goog.crypt.stringToByteArray(biggerThanOneBlock));
assertEquals(
'27c9b678012becd6891bac653f355b2d26f63132e840644d565f5dac',
goog.crypt.byteArrayToHex(sha224.digest()));
// Message larger than two blocks.
sha224.reset();
var biggerThanTwoBlocks = 'abcdbcdecdefdefgefghfghighij' +
'hijkijkljklmklmnlmnomnopnopq' +
'asdfljhr78yasdfljh45opa78sdf' +
'120839414104897aavnasdfafasd' +
'laasdouvhalacbnalalseryalcla';
assertTrue(biggerThanTwoBlocks.length > 2 * goog.crypt.Sha2.BLOCKSIZE_);
sha224.update(goog.crypt.stringToByteArray(biggerThanTwoBlocks));
assertEquals(
'1c2c1455cc984eef6f25ec9d79b1c661b3794887c3d0b24111ed9803',
goog.crypt.byteArrayToHex(sha224.digest()));
}
@@ -0,0 +1,49 @@
// 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}
* @final
* @struct
*/
goog.crypt.Sha256 = function() {
goog.crypt.Sha256.base(this, 'constructor',
8, goog.crypt.Sha256.INIT_HASH_BLOCK_);
};
goog.inherits(goog.crypt.Sha256, goog.crypt.Sha2);
/** @private {!Array<number>} */
goog.crypt.Sha256.INIT_HASH_BLOCK_ = [
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2012 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Closure Performance Tests - goog.crypt.Sha256</title>
<link rel="stylesheet" type="text/css" href="../testing/performancetable.css"/>
<script src="../base.js"></script>
<script>
goog.require('goog.crypt.Sha256');
goog.require('goog.crypt.hashTester');
goog.require('goog.testing.PerformanceTable');
goog.require('goog.testing.PseudoRandom');
goog.require('goog.testing.jsunit');
</script>
</head>
<body>
<h1>Closure Performance Tests - goog.crypt.Sha256</h1>
<p>
<strong>User-agent:</strong>
<script>document.write(navigator.userAgent);</script>
</p>
<script>
function testHashing() {
goog.crypt.hashTester.runPerfTests(function() {
return new goog.crypt.Sha256();
}, 'SHA256');
}
</script>
</body>
</html>
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2012 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.crypt.sha256
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.crypt.Sha256Test');
</script>
</head>
<body>
</body>
</html>

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