Update wmts-hidpi, add nicer-api-docs
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
// 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 Restricted class definitions for Closure.
|
||||
*
|
||||
* @author johnlenz@google.com (John Lenz)
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.classdef');
|
||||
|
||||
|
||||
/** @typedef {
|
||||
{constructor:!Function}|
|
||||
{constructor:!Function, statics:(Object|function(Function):void)}} */
|
||||
goog.labs.classdef.ClassDescriptor;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a restricted form of a Closure "class":
|
||||
* - from the compiler's perspective, the instance returned from the
|
||||
* constructor is sealed (no new properties may be added). This enables
|
||||
* better type checking.
|
||||
* - the compiler will rewrite this definition to a form that is optimal
|
||||
* for type checking and optimization (initially this will be a more
|
||||
* traditional form).
|
||||
*
|
||||
* @param {Function} superClass The superclass or null.
|
||||
* @param {goog.labs.classdef.ClassDescriptor} def
|
||||
* An object literal describing the
|
||||
* the class. It may have the following properties:
|
||||
* "constructor": the constructor function
|
||||
* "statics": an object literal containing methods to add to the constructor
|
||||
* as "static" methods or a function that will recieve the constructor
|
||||
* function as its only parameter to which static properties can
|
||||
* be added.
|
||||
* all other properties are added to the prototype.
|
||||
* @return {!Function} The class constructor.
|
||||
*/
|
||||
goog.labs.classdef.defineClass = function(superClass, def) {
|
||||
// TODO(johnlenz): consider making the superClass an optional parameter.
|
||||
var constructor = def.constructor;
|
||||
var statics = def.statics;
|
||||
// Wrap the constructor prior to setting up the prototype and static methods.
|
||||
if (!constructor || constructor == Object.prototype.constructor) {
|
||||
throw Error('constructor property is required.');
|
||||
}
|
||||
|
||||
var cls = goog.labs.classdef.createSealingConstructor_(constructor);
|
||||
if (superClass) {
|
||||
goog.inherits(cls, superClass);
|
||||
}
|
||||
|
||||
// Remove all the properties that should not be copied to the prototype.
|
||||
delete def.constructor;
|
||||
delete def.statics;
|
||||
|
||||
goog.labs.classdef.applyProperties_(cls.prototype, def);
|
||||
if (statics != null) {
|
||||
if (statics instanceof Function) {
|
||||
statics(cls);
|
||||
} else {
|
||||
goog.labs.classdef.applyProperties_(cls, statics);
|
||||
}
|
||||
}
|
||||
|
||||
return cls;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} Whether the instances returned by
|
||||
* goog.labs.classdef.defineClass should be sealed when possible.
|
||||
*/
|
||||
goog.define('goog.labs.classdef.SEAL_CLASS_INSTANCES', goog.DEBUG);
|
||||
|
||||
|
||||
/**
|
||||
* If goog.labs.classdef.SEAL_CLASS_INSTANCES is enabled and Object.seal is
|
||||
* defined, this function will wrap the constructor in a function that seals the
|
||||
* results of the provided constructor function.
|
||||
*
|
||||
* @param {!Function} ctr The constructor whose results maybe be sealed.
|
||||
* @return {!Function} The replacement constructor.
|
||||
* @private
|
||||
*/
|
||||
goog.labs.classdef.createSealingConstructor_ = function(ctr) {
|
||||
if (goog.labs.classdef.SEAL_CLASS_INSTANCES &&
|
||||
Object.seal instanceof Function) {
|
||||
/** @this {*} */
|
||||
var wrappedCtr = function() {
|
||||
// Don't seal an instance of a subclass when it calls the constructor of
|
||||
// its super class as there is most likely still setup to do.
|
||||
var instance = ctr.apply(this, arguments) || this;
|
||||
if (this.constructor === wrappedCtr) {
|
||||
Object.seal(instance);
|
||||
}
|
||||
return instance;
|
||||
};
|
||||
return wrappedCtr;
|
||||
}
|
||||
return ctr;
|
||||
};
|
||||
|
||||
|
||||
// TODO(johnlenz): share these values with the goog.object
|
||||
/**
|
||||
* The names of the fields that are defined on Object.prototype.
|
||||
* @type {!Array.<string>}
|
||||
* @private
|
||||
* @const
|
||||
*/
|
||||
goog.labs.classdef.OBJECT_PROTOTYPE_FIELDS_ = [
|
||||
'constructor',
|
||||
'hasOwnProperty',
|
||||
'isPrototypeOf',
|
||||
'propertyIsEnumerable',
|
||||
'toLocaleString',
|
||||
'toString',
|
||||
'valueOf'
|
||||
];
|
||||
|
||||
|
||||
// TODO(johnlenz): share this function with the goog.object
|
||||
/**
|
||||
* @param {!Object} target The object to add properties to.
|
||||
* @param {!Object} source The object to copy properites from.
|
||||
* @private
|
||||
*/
|
||||
goog.labs.classdef.applyProperties_ = function(target, source) {
|
||||
// TODO(johnlenz): update this to support ES5 getters/setters
|
||||
|
||||
var key;
|
||||
for (key in source) {
|
||||
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
|
||||
// For IE the for-in-loop does not contain any properties that are not
|
||||
// enumerable on the prototype object (for example isPrototypeOf from
|
||||
// Object.prototype) and it will also not include 'replace' on objects that
|
||||
// extend String and change 'replace' (not that it is common for anyone to
|
||||
// extend anything except Object).
|
||||
for (var i = 0; i < goog.labs.classdef.OBJECT_PROTOTYPE_FIELDS_.length; i++) {
|
||||
key = goog.labs.classdef.OBJECT_PROTOTYPE_FIELDS_[i];
|
||||
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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 Utilities to abstract mouse and touch events.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.labs.events.touch');
|
||||
goog.provide('goog.labs.events.touch.TouchData');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
/**
|
||||
* Description the geometry and target of an event.
|
||||
*
|
||||
* @typedef {{
|
||||
* clientX: number,
|
||||
* clientY: number,
|
||||
* screenX: number,
|
||||
* screenY: number,
|
||||
* target: EventTarget
|
||||
* }}
|
||||
*/
|
||||
goog.labs.events.touch.TouchData;
|
||||
|
||||
|
||||
/**
|
||||
* Takes a mouse or touch event and returns the relevent geometry and target
|
||||
* data.
|
||||
* @param {!Event} e A mouse or touch event.
|
||||
* @return {!goog.labs.events.touch.TouchData}
|
||||
*/
|
||||
goog.labs.events.touch.getTouchData = function(e) {
|
||||
|
||||
var source = e;
|
||||
goog.asserts.assert(
|
||||
goog.string.startsWith(e.type, 'touch') ||
|
||||
goog.string.startsWith(e.type, 'mouse'),
|
||||
'Event must be mouse or touch event.');
|
||||
|
||||
if (goog.string.startsWith(e.type, 'touch')) {
|
||||
goog.asserts.assert(
|
||||
goog.array.contains([
|
||||
goog.events.EventType.TOUCHCANCEL,
|
||||
goog.events.EventType.TOUCHEND,
|
||||
goog.events.EventType.TOUCHMOVE,
|
||||
goog.events.EventType.TOUCHSTART
|
||||
], e.type),
|
||||
'Touch event not of valid type.');
|
||||
|
||||
// If the event is end or cancel, take the first changed touch,
|
||||
// otherwise the first target touch.
|
||||
source = (e.type == goog.events.EventType.TOUCHEND ||
|
||||
e.type == goog.events.EventType.TOUCHCANCEL) ?
|
||||
e.changedTouches[0] : e.targetTouches[0];
|
||||
}
|
||||
|
||||
return {
|
||||
clientX: source['clientX'],
|
||||
clientY: source['clientY'],
|
||||
screenX: source['screenX'],
|
||||
screenY: source['screenY'],
|
||||
target: source['target']
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
// 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 Unit tests for goog.labs.events.touch.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.labs.events.touchTest');
|
||||
|
||||
goog.require('goog.labs.events.touch');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
goog.setTestOnly('goog.labs.events.touchTest');
|
||||
|
||||
function testMouseEvent() {
|
||||
var fakeTarget = {};
|
||||
|
||||
var fakeMouseMove = {
|
||||
'clientX': 1,
|
||||
'clientY': 2,
|
||||
'screenX': 3,
|
||||
'screenY': 4,
|
||||
'target': fakeTarget,
|
||||
'type': 'mousemove'
|
||||
};
|
||||
|
||||
var data = goog.labs.events.touch.getTouchData(fakeMouseMove);
|
||||
assertEquals(1, data.clientX);
|
||||
assertEquals(2, data.clientY);
|
||||
assertEquals(3, data.screenX);
|
||||
assertEquals(4, data.screenY);
|
||||
assertEquals(fakeTarget, data.target);
|
||||
}
|
||||
|
||||
function testTouchEvent() {
|
||||
var fakeTarget = {};
|
||||
|
||||
var fakeTouch = {
|
||||
'clientX': 1,
|
||||
'clientY': 2,
|
||||
'screenX': 3,
|
||||
'screenY': 4,
|
||||
'target': fakeTarget
|
||||
};
|
||||
|
||||
var fakeTouchStart = {
|
||||
'targetTouches': [fakeTouch],
|
||||
'target': fakeTarget,
|
||||
'type': 'touchstart'
|
||||
};
|
||||
|
||||
var data = goog.labs.events.touch.getTouchData(fakeTouchStart);
|
||||
assertEquals(1, data.clientX);
|
||||
assertEquals(2, data.clientY);
|
||||
assertEquals(3, data.screenX);
|
||||
assertEquals(4, data.screenY);
|
||||
assertEquals(fakeTarget, data.target);
|
||||
}
|
||||
|
||||
function testTouchChangeEvent() {
|
||||
var fakeTarget = {};
|
||||
|
||||
var fakeTouch = {
|
||||
'clientX': 1,
|
||||
'clientY': 2,
|
||||
'screenX': 3,
|
||||
'screenY': 4,
|
||||
'target': fakeTarget
|
||||
};
|
||||
|
||||
var fakeTouchStart = {
|
||||
'changedTouches': [fakeTouch],
|
||||
'target': fakeTarget,
|
||||
'type': 'touchend'
|
||||
};
|
||||
|
||||
var data = goog.labs.events.touch.getTouchData(fakeTouchStart);
|
||||
assertEquals(1, data.clientX);
|
||||
assertEquals(2, data.clientY);
|
||||
assertEquals(3, data.screenX);
|
||||
assertEquals(4, data.screenY);
|
||||
assertEquals(fakeTarget, data.target);
|
||||
}
|
||||
|
||||
391
nicer-api-docs/closure-library/closure/goog/labs/format/csv.js
Normal file
391
nicer-api-docs/closure-library/closure/goog/labs/format/csv.js
Normal file
@@ -0,0 +1,391 @@
|
||||
// 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 Provides a parser that turns a string of well-formed CSV data
|
||||
* into an array of objects or an array of arrays. All values are returned as
|
||||
* strings; the user has to convert data into numbers or Dates as required.
|
||||
* Empty fields (adjacent commas) are returned as empty strings.
|
||||
*
|
||||
* This parser uses http://tools.ietf.org/html/rfc4180 as the definition of CSV.
|
||||
*
|
||||
*/
|
||||
goog.provide('goog.labs.format.csv');
|
||||
goog.provide('goog.labs.format.csv.ParseError');
|
||||
goog.provide('goog.labs.format.csv.Token');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.debug.Error');
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.string.newlines');
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} Enable verbose debugging. This is a flag so it can be
|
||||
* enabled in production if necessary post-compilation. Otherwise, debug
|
||||
* information will be stripped to minimize final code size.
|
||||
*/
|
||||
goog.labs.format.csv.ENABLE_VERBOSE_DEBUGGING = goog.DEBUG;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Error thrown when parsing fails.
|
||||
*
|
||||
* @param {string} text The CSV source text being parsed.
|
||||
* @param {number} index The index, in the string, of the position of the
|
||||
* error.
|
||||
* @param {string=} opt_message A description of the violated parse expectation.
|
||||
* @constructor
|
||||
* @extends {goog.debug.Error}
|
||||
*/
|
||||
goog.labs.format.csv.ParseError = function(text, index, opt_message) {
|
||||
|
||||
var message;
|
||||
|
||||
/**
|
||||
* @type {?{line: number, column: number}} The line and column of the parse
|
||||
* error.
|
||||
*/
|
||||
this.position = null;
|
||||
|
||||
if (goog.labs.format.csv.ENABLE_VERBOSE_DEBUGGING) {
|
||||
message = opt_message || '';
|
||||
|
||||
var info = goog.labs.format.csv.ParseError.findLineInfo_(text, index);
|
||||
if (info) {
|
||||
var lineNumber = info.lineIndex + 1;
|
||||
var columnNumber = index - info.line.startLineIndex + 1;
|
||||
|
||||
this.position = {
|
||||
line: lineNumber,
|
||||
column: columnNumber
|
||||
};
|
||||
|
||||
message += goog.string.subs(' at line %s column %s',
|
||||
lineNumber, columnNumber);
|
||||
message += '\n' + goog.labs.format.csv.ParseError.getLineDebugString_(
|
||||
info.line.getContent(), columnNumber);
|
||||
}
|
||||
}
|
||||
|
||||
goog.base(this, message);
|
||||
};
|
||||
goog.inherits(goog.labs.format.csv.ParseError, goog.debug.Error);
|
||||
|
||||
|
||||
/** @inheritDoc */
|
||||
goog.labs.format.csv.ParseError.prototype.name = 'ParseError';
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the line and column for an index in a string.
|
||||
* TODO(nnaze): Consider moving to goog.string.newlines.
|
||||
* @param {string} str A string.
|
||||
* @param {number} index An index into the string.
|
||||
* @return {?{line: !goog.string.newlines.Line, lineIndex: number}} The line
|
||||
* and index of the line.
|
||||
* @private
|
||||
*/
|
||||
goog.labs.format.csv.ParseError.findLineInfo_ = function(str, index) {
|
||||
var lines = goog.string.newlines.getLines(str);
|
||||
var lineIndex = goog.array.findIndex(lines, function(line) {
|
||||
return line.startLineIndex <= index && line.endLineIndex > index;
|
||||
});
|
||||
|
||||
if (goog.isNumber(lineIndex)) {
|
||||
var line = lines[lineIndex];
|
||||
return {
|
||||
line: line,
|
||||
lineIndex: lineIndex
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a debug string of a line and a pointing caret beneath it.
|
||||
* @param {string} str The string.
|
||||
* @param {number} column The column to point at (1-indexed).
|
||||
* @return {string} The debug line.
|
||||
* @private
|
||||
*/
|
||||
goog.labs.format.csv.ParseError.getLineDebugString_ = function(str, column) {
|
||||
var returnString = str + '\n';
|
||||
returnString += goog.string.repeat(' ', column - 1) + '^';
|
||||
return returnString;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A token -- a single-character string or a sentinel.
|
||||
* @typedef {string|!goog.labs.format.csv.Sentinels_}
|
||||
*/
|
||||
goog.labs.format.csv.Token;
|
||||
|
||||
|
||||
/**
|
||||
* Parses a CSV string to create a two-dimensional array.
|
||||
*
|
||||
* This function does not process header lines, etc -- such transformations can
|
||||
* be made on the resulting array.
|
||||
*
|
||||
* @param {string} text The entire CSV text to be parsed.
|
||||
* @return {!Array.<!Array.<string>>} The parsed CSV.
|
||||
*/
|
||||
goog.labs.format.csv.parse = function(text) {
|
||||
|
||||
var index = 0; // current char offset being considered
|
||||
|
||||
|
||||
var EOF = goog.labs.format.csv.Sentinels_.EOF;
|
||||
var EOR = goog.labs.format.csv.Sentinels_.EOR;
|
||||
var NEWLINE = goog.labs.format.csv.Sentinels_.NEWLINE; // \r?\n
|
||||
var EMPTY = goog.labs.format.csv.Sentinels_.EMPTY;
|
||||
|
||||
var pushBackToken = null; // A single-token pushback.
|
||||
var sawComma = false; // Special case for terminal comma.
|
||||
|
||||
/**
|
||||
* Push a single token into the push-back variable.
|
||||
* @param {goog.labs.format.csv.Token} t Single token.
|
||||
*/
|
||||
function pushBack(t) {
|
||||
goog.labs.format.csv.assertToken_(t);
|
||||
goog.asserts.assert(goog.isNull(pushBackToken));
|
||||
pushBackToken = t;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {goog.labs.format.csv.Token} The next token in the stream.
|
||||
*/
|
||||
function nextToken() {
|
||||
|
||||
// Give the push back token if present.
|
||||
if (pushBackToken != null) {
|
||||
var c = pushBackToken;
|
||||
pushBackToken = null;
|
||||
return c;
|
||||
}
|
||||
|
||||
// We're done. EOF.
|
||||
if (index >= text.length) {
|
||||
return EOF;
|
||||
}
|
||||
|
||||
// Give the next charater.
|
||||
var chr = text.charAt(index++);
|
||||
goog.labs.format.csv.assertToken_(chr);
|
||||
|
||||
// Check if this is a newline. If so, give the new line sentinel.
|
||||
var isNewline = false;
|
||||
if (chr == '\n') {
|
||||
isNewline = true;
|
||||
} else if (chr == '\r') {
|
||||
|
||||
// This is a '\r\n' newline. Treat as single token, go
|
||||
// forward two indicies.
|
||||
if (index < text.length && text.charAt(index) == '\n') {
|
||||
index++;
|
||||
}
|
||||
|
||||
isNewline = true;
|
||||
}
|
||||
|
||||
if (isNewline) {
|
||||
return NEWLINE;
|
||||
}
|
||||
|
||||
return chr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a quoted field from input.
|
||||
* @return {string} The field, as a string.
|
||||
*/
|
||||
function readQuotedField() {
|
||||
// We've already consumed the first quote by the time we get here.
|
||||
var start = index;
|
||||
var end = null;
|
||||
|
||||
for (var token = nextToken(); token != EOF; token = nextToken()) {
|
||||
if (token == '"') {
|
||||
end = index - 1;
|
||||
token = nextToken();
|
||||
|
||||
// Two double quotes in a row. Keep scanning.
|
||||
if (token == '"') {
|
||||
end = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// End of field. Break out.
|
||||
if (token == ',' || token == EOF || token == NEWLINE) {
|
||||
if (token == NEWLINE) {
|
||||
pushBack(token);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
throw new goog.labs.format.csv.ParseError(
|
||||
text, index - 1,
|
||||
'Unexpected character "' + token + '" after quote mark');
|
||||
}
|
||||
}
|
||||
|
||||
if (goog.isNull(end)) {
|
||||
throw new goog.labs.format.csv.ParseError(
|
||||
text, text.length - 1,
|
||||
'Unexpected end of text after open quote');
|
||||
}
|
||||
|
||||
// Take substring, combine double quotes.
|
||||
return text.substring(start, end).replace(/""/g, '"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a field from input.
|
||||
* @return {string|!goog.labs.format.csv.Sentinels_} The field, as a string,
|
||||
* or a sentinel (if applicable).
|
||||
*/
|
||||
function readField() {
|
||||
var start = index;
|
||||
var didSeeComma = sawComma;
|
||||
sawComma = false;
|
||||
var token = nextToken();
|
||||
if (token == EMPTY) {
|
||||
return EOR;
|
||||
}
|
||||
if (token == EOF || token == NEWLINE) {
|
||||
if (didSeeComma) {
|
||||
pushBack(EMPTY);
|
||||
return '';
|
||||
}
|
||||
return EOR;
|
||||
}
|
||||
|
||||
// This is the beginning of a quoted field.
|
||||
if (token == '"') {
|
||||
return readQuotedField();
|
||||
}
|
||||
|
||||
while (true) {
|
||||
|
||||
// This is the end of line or file.
|
||||
if (token == EOF || token == NEWLINE) {
|
||||
pushBack(token);
|
||||
break;
|
||||
}
|
||||
|
||||
// This is the end of record.
|
||||
if (token == ',') {
|
||||
sawComma = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (token == '"') {
|
||||
throw new goog.labs.format.csv.ParseError(text, index - 1,
|
||||
'Unexpected quote mark');
|
||||
}
|
||||
|
||||
token = nextToken();
|
||||
}
|
||||
|
||||
|
||||
var returnString = (token == EOF) ?
|
||||
text.substring(start) : // Return to end of file.
|
||||
text.substring(start, index - 1);
|
||||
|
||||
return returnString.replace(/[\r\n]+/g, ''); // Squash any CRLFs.
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the next record.
|
||||
* @return {!Array.<string>|!goog.labs.format.csv.Sentinels_} A single record
|
||||
* with multiple fields.
|
||||
*/
|
||||
function readRecord() {
|
||||
if (index >= text.length) {
|
||||
return EOF;
|
||||
}
|
||||
var record = [];
|
||||
for (var field = readField(); field != EOR; field = readField()) {
|
||||
record.push(field);
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
// Read all records and return.
|
||||
var records = [];
|
||||
for (var record = readRecord(); record != EOF; record = readRecord()) {
|
||||
records.push(record);
|
||||
}
|
||||
return records;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sentinel tracking objects.
|
||||
* @enum {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.labs.format.csv.Sentinels_ = {
|
||||
/** Empty field */
|
||||
EMPTY: {},
|
||||
|
||||
/** End of file */
|
||||
EOF: {},
|
||||
|
||||
/** End of record */
|
||||
EOR: {},
|
||||
|
||||
/** Newline. \r?\n */
|
||||
NEWLINE: {}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} str A string.
|
||||
* @return {boolean} Whether the string is a single character.
|
||||
* @private
|
||||
*/
|
||||
goog.labs.format.csv.isCharacterString_ = function(str) {
|
||||
return goog.isString(str) && str.length == 1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Assert the parameter is a token.
|
||||
* @param {*} o What should be a token.
|
||||
* @throws {goog.asserts.AssertionError} If {@ code} is not a token.
|
||||
* @private
|
||||
*/
|
||||
goog.labs.format.csv.assertToken_ = function(o) {
|
||||
if (goog.isString(o)) {
|
||||
goog.asserts.assertString(o);
|
||||
goog.asserts.assert(goog.labs.format.csv.isCharacterString_(o),
|
||||
'Should be a string of length 1 or a sentinel.');
|
||||
} else {
|
||||
goog.asserts.assert(
|
||||
goog.object.containsValue(goog.labs.format.csv.Sentinels_, o),
|
||||
'Should be a string of length 1 or a sentinel.');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
// 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.labs.format.csvTest');
|
||||
|
||||
goog.require('goog.labs.format.csv');
|
||||
goog.require('goog.labs.format.csv.ParseError');
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.testing.asserts');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
goog.setTestOnly('goog.labs.format.csvTest');
|
||||
|
||||
|
||||
function testGoldenPath() {
|
||||
assertObjectEquals(
|
||||
[['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']],
|
||||
goog.labs.format.csv.parse('a,b,c\nd,e,f\ng,h,i\n'));
|
||||
assertObjectEquals(
|
||||
[['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']],
|
||||
goog.labs.format.csv.parse('a,b,c\r\nd,e,f\r\ng,h,i\r\n'));
|
||||
}
|
||||
|
||||
function testNoCrlfAtEnd() {
|
||||
assertObjectEquals(
|
||||
[['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']],
|
||||
goog.labs.format.csv.parse('a,b,c\nd,e,f\ng,h,i'));
|
||||
}
|
||||
|
||||
function testQuotes() {
|
||||
assertObjectEquals(
|
||||
[['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']],
|
||||
goog.labs.format.csv.parse('a,"b",c\n"d","e","f"\ng,h,"i"'));
|
||||
assertObjectEquals(
|
||||
[['a', 'b, as in boy', 'c'], ['d', 'e', 'f']],
|
||||
goog.labs.format.csv.parse('a,"b, as in boy",c\n"d","e","f"\n'));
|
||||
}
|
||||
|
||||
function testEmbeddedCrlfs() {
|
||||
assertObjectEquals(
|
||||
[['a', 'b\nball', 'c'], ['d\nd', 'e', 'f'], ['g', 'h', 'i']],
|
||||
goog.labs.format.csv.parse('a,"b\nball",c\n"d\nd","e","f"\ng,h,"i"\n'));
|
||||
}
|
||||
|
||||
function testEmbeddedQuotes() {
|
||||
assertObjectEquals(
|
||||
[['a', '"b"', 'Jonathan "Smokey" Feinberg'], ['d', 'e', 'f']],
|
||||
goog.labs.format.csv.parse(
|
||||
'a,"""b""","Jonathan ""Smokey"" Feinberg"\nd,e,f\r\n'));
|
||||
}
|
||||
|
||||
function testUnclosedQuote() {
|
||||
var e = assertThrows(function() {
|
||||
goog.labs.format.csv.parse('a,"b,c\nd,e,f');
|
||||
});
|
||||
|
||||
assertTrue(e instanceof goog.labs.format.csv.ParseError);
|
||||
assertEquals(2, e.position.line);
|
||||
assertEquals(5, e.position.column);
|
||||
assertEquals(
|
||||
'Unexpected end of text after open quote at line 2 column 5\n' +
|
||||
'd,e,f\n' +
|
||||
' ^',
|
||||
e.message);
|
||||
}
|
||||
|
||||
function testQuotesInUnquotedField() {
|
||||
var e = assertThrows(function() {
|
||||
goog.labs.format.csv.parse('a,b "and" b,c\nd,e,f');
|
||||
});
|
||||
|
||||
assertTrue(e instanceof goog.labs.format.csv.ParseError);
|
||||
|
||||
assertEquals(1, e.position.line);
|
||||
assertEquals(5, e.position.column);
|
||||
|
||||
assertEquals(
|
||||
'Unexpected quote mark at line 1 column 5\n' +
|
||||
'a,b "and" b,c\n' +
|
||||
' ^',
|
||||
e.message);
|
||||
}
|
||||
|
||||
function testGarbageOutsideQuotes() {
|
||||
var e = assertThrows(function() {
|
||||
goog.labs.format.csv.parse('a,"b",c\nd,"e"oops,f');
|
||||
});
|
||||
|
||||
assertTrue(e instanceof goog.labs.format.csv.ParseError);
|
||||
assertEquals(2, e.position.line);
|
||||
assertEquals(6, e.position.column);
|
||||
assertEquals(
|
||||
'Unexpected character "o" after quote mark at line 2 column 6\n' +
|
||||
'd,"e"oops,f\n' +
|
||||
' ^',
|
||||
e.message);
|
||||
}
|
||||
|
||||
function testEmptyRecords() {
|
||||
assertObjectEquals(
|
||||
[['a', '', 'c'], ['d', 'e', ''], ['', '', '']],
|
||||
goog.labs.format.csv.parse('a,,c\r\nd,e,\n,,'));
|
||||
}
|
||||
|
||||
function testFindLineInfo() {
|
||||
var testString = 'abc\ndef\rghi';
|
||||
var info = goog.labs.format.csv.ParseError.findLineInfo_(testString, 4);
|
||||
|
||||
assertEquals(4, info.line.startLineIndex);
|
||||
assertEquals(7, info.line.endContentIndex);
|
||||
assertEquals(8, info.line.endLineIndex);
|
||||
|
||||
assertEquals(1, info.lineIndex);
|
||||
}
|
||||
|
||||
function testGetLineDebugString() {
|
||||
var str = 'abcdefghijklmnop';
|
||||
var index = str.indexOf('j');
|
||||
var column = index + 1;
|
||||
assertEquals(
|
||||
goog.labs.format.csv.ParseError.getLineDebugString_(str, column),
|
||||
'abcdefghijklmnop\n' +
|
||||
' ^');
|
||||
|
||||
}
|
||||
|
||||
function testIsCharacterString() {
|
||||
assertTrue(goog.labs.format.csv.isCharacterString_('a'));
|
||||
assertTrue(goog.labs.format.csv.isCharacterString_('\n'));
|
||||
assertTrue(goog.labs.format.csv.isCharacterString_(' '));
|
||||
|
||||
assertFalse(goog.labs.format.csv.isCharacterString_(null));
|
||||
assertFalse(goog.labs.format.csv.isCharacterString_(' '));
|
||||
assertFalse(goog.labs.format.csv.isCharacterString_(''));
|
||||
assertFalse(goog.labs.format.csv.isCharacterString_('aa'));
|
||||
}
|
||||
|
||||
|
||||
function testAssertToken() {
|
||||
goog.labs.format.csv.assertToken_('a');
|
||||
|
||||
goog.object.forEach(goog.labs.format.csv.SENTINELS_,
|
||||
function(value) {
|
||||
goog.labs.format.csv.assertToken_(value);
|
||||
});
|
||||
|
||||
assertThrows(function() {
|
||||
goog.labs.format.csv.assertToken_('aa');
|
||||
});
|
||||
|
||||
assertThrows(function() {
|
||||
goog.labs.format.csv.assertToken_('');
|
||||
});
|
||||
|
||||
assertThrows(function() {
|
||||
goog.labs.format.csv.assertToken_({});
|
||||
});
|
||||
}
|
||||
733
nicer-api-docs/closure-library/closure/goog/labs/mock/mock.js
Normal file
733
nicer-api-docs/closure-library/closure/goog/labs/mock/mock.js
Normal file
@@ -0,0 +1,733 @@
|
||||
// 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 Provides a mocking framework in Closure to make unit tests easy
|
||||
* to write and understand. The methods provided here can be used to replace
|
||||
* implementations of existing objects with 'mock' objects to abstract out
|
||||
* external services and dependencies thereby isolating the code under test.
|
||||
* Apart from mocking, methods are also provided to just monitor calls to an
|
||||
* object (spying) and returning specific values for some or all the inputs to
|
||||
* methods (stubbing).
|
||||
*
|
||||
* Design doc : http://go/closuremock
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.labs.mock');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.debug');
|
||||
goog.require('goog.debug.Error');
|
||||
goog.require('goog.functions');
|
||||
goog.require('goog.json');
|
||||
|
||||
|
||||
/**
|
||||
* Mocks a given object or class.
|
||||
*
|
||||
* @param {!Object} objectOrClass An instance or a constructor of a class to be
|
||||
* mocked.
|
||||
* @return {!Object} The mocked object.
|
||||
*/
|
||||
goog.labs.mock.mock = function(objectOrClass) {
|
||||
// Go over properties of 'objectOrClass' and create a MockManager to
|
||||
// be used for stubbing out calls to methods.
|
||||
var mockObjectManager = new goog.labs.mock.MockObjectManager_(objectOrClass);
|
||||
var mockedObject = mockObjectManager.getMockedItem();
|
||||
goog.asserts.assertObject(mockedObject);
|
||||
return /** @type {!Object} */ (mockedObject);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Mocks a given function.
|
||||
*
|
||||
* @param {!Function} func A function to be mocked.
|
||||
* @return {!Function} The mocked function.
|
||||
*/
|
||||
goog.labs.mock.mockFunction = function(func) {
|
||||
var mockFuncManager = new goog.labs.mock.MockFunctionManager_(func);
|
||||
var mockedFunction = mockFuncManager.getMockedItem();
|
||||
goog.asserts.assertFunction(mockedFunction);
|
||||
return /** @type {!Function} */ (mockedFunction);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Spies on a given object.
|
||||
*
|
||||
* @param {!Object} obj The object to be spied on.
|
||||
* @return {!Object} The spy object.
|
||||
*/
|
||||
goog.labs.mock.spy = function(obj) {
|
||||
// Go over properties of 'obj' and create a MockSpyManager_ to
|
||||
// be used for spying on calls to methods.
|
||||
var mockSpyManager = new goog.labs.mock.MockSpyManager_(obj);
|
||||
var spyObject = mockSpyManager.getMockedItem();
|
||||
goog.asserts.assert(spyObject);
|
||||
return spyObject;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an object that can be used to verify calls to specific methods of a
|
||||
* given mock.
|
||||
*
|
||||
* @param {!Object} obj The mocked object.
|
||||
* @return {!Object} The verifier.
|
||||
*/
|
||||
goog.labs.mock.verify = function(obj) {
|
||||
return obj.$callVerifier;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a name to identify a function. Named functions return their names,
|
||||
* unnamed functions return a string of the form '#anonymous{ID}' where ID is
|
||||
* a unique identifier for each anonymous function.
|
||||
* @private
|
||||
* @param {!Function} func The function.
|
||||
* @return {string} The function name.
|
||||
*/
|
||||
goog.labs.mock.getFunctionName_ = function(func) {
|
||||
var funcName = goog.debug.getFunctionName(func);
|
||||
if (funcName == '' || funcName == '[Anonymous]') {
|
||||
funcName = '#anonymous' + goog.getUid(func);
|
||||
}
|
||||
return funcName;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a nicely formatted, readble representation of a method call.
|
||||
* @private
|
||||
* @param {string} methodName The name of the method.
|
||||
* @param {Array=} opt_args The method arguments.
|
||||
* @return {string} The string representation of the method call.
|
||||
*/
|
||||
goog.labs.mock.formatMethodCall_ = function(methodName, opt_args) {
|
||||
opt_args = opt_args || [];
|
||||
opt_args = goog.array.map(opt_args, function(arg) {
|
||||
if (goog.isFunction(arg)) {
|
||||
var funcName = goog.labs.mock.getFunctionName_(arg);
|
||||
return '<function ' + funcName + '>';
|
||||
} else {
|
||||
return goog.json.serialize(arg);
|
||||
}
|
||||
});
|
||||
return methodName + '(' + opt_args.join(', ') + ')';
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Error thrown when verification failed.
|
||||
*
|
||||
* @param {Array} recordedCalls The recorded calls that didn't match the
|
||||
* expectation.
|
||||
* @param {!string} methodName The expected method call.
|
||||
* @param {!Array} args The expected arguments.
|
||||
* @constructor
|
||||
* @extends {goog.debug.Error}
|
||||
*/
|
||||
goog.labs.mock.VerificationError = function(recordedCalls, methodName, args) {
|
||||
var msg = goog.labs.mock.VerificationError.getVerificationErrorMsg_(
|
||||
recordedCalls, methodName, args);
|
||||
goog.base(this, msg);
|
||||
};
|
||||
goog.inherits(goog.labs.mock.VerificationError, goog.debug.Error);
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.labs.mock.VerificationError.prototype.name = 'VerificationError';
|
||||
|
||||
|
||||
/**
|
||||
* This array contains the name of the functions that are part of the base
|
||||
* Object prototype.
|
||||
* Basically a copy of goog.object.PROTOTYPE_FIELDS_.
|
||||
* @const
|
||||
* @type {!Array.<string>}
|
||||
* @private
|
||||
*/
|
||||
goog.labs.mock.PROTOTYPE_FIELDS_ = [
|
||||
'constructor',
|
||||
'hasOwnProperty',
|
||||
'isPrototypeOf',
|
||||
'propertyIsEnumerable',
|
||||
'toLocaleString',
|
||||
'toString',
|
||||
'valueOf'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a descriptive error message for an expected method call.
|
||||
* @private
|
||||
* @param {Array} recordedCalls The recorded calls that didn't match the
|
||||
* expectation.
|
||||
* @param {!string} methodName The expected method call.
|
||||
* @param {!Array} args The expected arguments.
|
||||
* @return {string} The error message.
|
||||
*/
|
||||
goog.labs.mock.VerificationError.getVerificationErrorMsg_ =
|
||||
function(recordedCalls, methodName, args) {
|
||||
|
||||
recordedCalls = goog.array.filter(recordedCalls, function(binding) {
|
||||
return binding.getMethodName() == methodName;
|
||||
});
|
||||
|
||||
var expected = goog.labs.mock.formatMethodCall_(methodName, args);
|
||||
|
||||
var msg = '\nExpected: ' + expected.toString();
|
||||
msg += '\nRecorded: ';
|
||||
|
||||
if (recordedCalls.length > 0) {
|
||||
msg += recordedCalls.join(',\n ');
|
||||
} else {
|
||||
msg += 'No recorded calls';
|
||||
}
|
||||
|
||||
return msg;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Base class that provides basic functionality for creating, adding and
|
||||
* finding bindings, offering an executor method that is called when a call to
|
||||
* the stub is made, an array to hold the bindings and the mocked item, among
|
||||
* other things.
|
||||
*
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
goog.labs.mock.MockManager_ = function() {
|
||||
/**
|
||||
* Proxies the methods for the mocked object or class to execute the stubs.
|
||||
* @type {!Object}
|
||||
* @protected
|
||||
*/
|
||||
this.mockedItem = {};
|
||||
|
||||
/**
|
||||
* A reference to the object or function being mocked.
|
||||
* @type {Object|Function}
|
||||
* @protected
|
||||
*/
|
||||
this.mockee = null;
|
||||
|
||||
/**
|
||||
* Holds the stub bindings established so far.
|
||||
* @protected
|
||||
*/
|
||||
this.methodBindings = [];
|
||||
|
||||
/**
|
||||
* Holds a reference to the binder used to define stubs.
|
||||
* @protected
|
||||
*/
|
||||
this.$stubBinder = null;
|
||||
|
||||
/**
|
||||
* Record method calls with no stub definitions.
|
||||
* @type {!Array.<!goog.labs.mock.MethodBinding_>}
|
||||
* @private
|
||||
*/
|
||||
this.callRecords_ = [];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles the first step in creating a stub, returning a stub-binder that
|
||||
* is later used to bind a stub for a method.
|
||||
*
|
||||
* @param {string} methodName The name of the method being bound.
|
||||
* @param {...} var_args The arguments to the method.
|
||||
* @return {!goog.labs.mock.StubBinder_} The stub binder.
|
||||
* @private
|
||||
*/
|
||||
goog.labs.mock.MockManager_.prototype.handleMockCall_ =
|
||||
function(methodName, var_args) {
|
||||
var args = goog.array.slice(arguments, 1);
|
||||
return new goog.labs.mock.StubBinder_(this, methodName, args);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the mock object. This should have a stubbed method for each method
|
||||
* on the object being mocked.
|
||||
*
|
||||
* @return {!Object|!Function} The mock object.
|
||||
*/
|
||||
goog.labs.mock.MockManager_.prototype.getMockedItem = function() {
|
||||
return this.mockedItem;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a binding for the method name and arguments to be stubbed.
|
||||
*
|
||||
* @param {?string} methodName The name of the stubbed method.
|
||||
* @param {!Array} args The arguments passed to the method.
|
||||
* @param {!Function} func The stub function.
|
||||
*
|
||||
*/
|
||||
goog.labs.mock.MockManager_.prototype.addBinding =
|
||||
function(methodName, args, func) {
|
||||
var binding = new goog.labs.mock.MethodBinding_(methodName, args, func);
|
||||
this.methodBindings.push(binding);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a stub, if defined, for the method name and arguments passed in.
|
||||
*
|
||||
* @param {string} methodName The name of the stubbed method.
|
||||
* @param {!Array} args The arguments passed to the method.
|
||||
* @return {Function} The stub function or undefined.
|
||||
* @protected
|
||||
*/
|
||||
goog.labs.mock.MockManager_.prototype.findBinding =
|
||||
function(methodName, args) {
|
||||
var stub = goog.array.find(this.methodBindings, function(binding) {
|
||||
return binding.matches(methodName, args, false /* isVerification */);
|
||||
});
|
||||
return stub && stub.getStub();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a stub, if defined, for the method name and arguments passed in as
|
||||
* parameters.
|
||||
*
|
||||
* @param {string} methodName The name of the stubbed method.
|
||||
* @param {!Array} args The arguments passed to the method.
|
||||
* @return {Function} The stub function or undefined.
|
||||
* @protected
|
||||
*/
|
||||
goog.labs.mock.MockManager_.prototype.getExecutor = function(methodName, args) {
|
||||
return this.findBinding(methodName, args);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Looks up the list of stubs defined on the mock object and executes the
|
||||
* function associated with that stub.
|
||||
*
|
||||
* @param {string} methodName The name of the method to execute.
|
||||
* @param {...} var_args The arguments passed to the method.
|
||||
* @return {*} Value returned by the stub function.
|
||||
* @protected
|
||||
*/
|
||||
goog.labs.mock.MockManager_.prototype.executeStub =
|
||||
function(methodName, var_args) {
|
||||
var args = goog.array.slice(arguments, 1);
|
||||
|
||||
// Record this call
|
||||
this.recordCall_(methodName, args);
|
||||
|
||||
var func = this.getExecutor(methodName, args);
|
||||
if (func) {
|
||||
return func.apply(null, args);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Records a call to 'methodName' with arguments 'args'.
|
||||
*
|
||||
* @param {string} methodName The name of the called method.
|
||||
* @param {!Array} args The array of arguments.
|
||||
* @private
|
||||
*/
|
||||
goog.labs.mock.MockManager_.prototype.recordCall_ =
|
||||
function(methodName, args) {
|
||||
var callRecord = new goog.labs.mock.MethodBinding_(methodName, args,
|
||||
goog.nullFunction);
|
||||
|
||||
this.callRecords_.push(callRecord);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Verify invocation of a method with specific arguments.
|
||||
*
|
||||
* @param {string} methodName The name of the method.
|
||||
* @param {...} var_args The arguments passed.
|
||||
* @protected
|
||||
*/
|
||||
goog.labs.mock.MockManager_.prototype.verifyInvocation =
|
||||
function(methodName, var_args) {
|
||||
var args = goog.array.slice(arguments, 1);
|
||||
var binding = goog.array.find(this.callRecords_, function(binding) {
|
||||
return binding.matches(methodName, args, true /* isVerification */);
|
||||
});
|
||||
|
||||
if (!binding) {
|
||||
throw new goog.labs.mock.VerificationError(
|
||||
this.callRecords_, methodName, args);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sets up mock for the given object (or class), stubbing out all the defined
|
||||
* methods. By default, all stubs return {@code undefined}, though stubs can be
|
||||
* later defined using {@code goog.labs.mock.when}.
|
||||
*
|
||||
* @param {!Object|!Function} objOrClass The object or class to set up the mock
|
||||
* for. A class is a constructor function.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {goog.labs.mock.MockManager_}
|
||||
* @private
|
||||
*/
|
||||
goog.labs.mock.MockObjectManager_ = function(objOrClass) {
|
||||
goog.base(this);
|
||||
|
||||
/**
|
||||
* Proxies the calls to establish the first step of the stub bindings (object
|
||||
* and method name)
|
||||
* @private
|
||||
*/
|
||||
this.objectStubBinder_ = {};
|
||||
|
||||
this.mockee = objOrClass;
|
||||
|
||||
/**
|
||||
* The call verifier is used to verify the calls. It maps property names to
|
||||
* the method that does call verification.
|
||||
* @type {!Object.<string, function(string, ...)>}
|
||||
* @private
|
||||
*/
|
||||
this.objectCallVerifier_ = {};
|
||||
|
||||
var obj;
|
||||
if (goog.isFunction(objOrClass)) {
|
||||
// Create a temporary subclass with a no-op constructor so that we can
|
||||
// create an instance and determine what methods it has.
|
||||
/** @constructor */
|
||||
var tempCtor = function() {};
|
||||
goog.inherits(tempCtor, objOrClass);
|
||||
obj = new tempCtor();
|
||||
} else {
|
||||
obj = objOrClass;
|
||||
}
|
||||
|
||||
// Put the object being mocked in the prototype chain of the mock so that
|
||||
// it has all the correct properties and instanceof works.
|
||||
/** @constructor */
|
||||
var mockedItemCtor = function() {};
|
||||
mockedItemCtor.prototype = obj;
|
||||
this.mockedItem = new mockedItemCtor();
|
||||
|
||||
var enumerableProperties = goog.object.getKeys(obj);
|
||||
// The non enumerable properties are added due to the fact that IE8 does not
|
||||
// enumerate any of the prototype Object functions even when overriden and
|
||||
// mocking these is sometimes needed.
|
||||
for (var i = 0; i < goog.labs.mock.PROTOTYPE_FIELDS_.length; i++) {
|
||||
var prop = goog.labs.mock.PROTOTYPE_FIELDS_[i];
|
||||
if (!goog.array.contains(enumerableProperties, prop)) {
|
||||
enumerableProperties.push(prop);
|
||||
}
|
||||
}
|
||||
|
||||
// Adds the properties to the mock, creating a proxy stub for each method on
|
||||
// the instance.
|
||||
for (var i = 0; i < enumerableProperties.length; i++) {
|
||||
var prop = enumerableProperties[i];
|
||||
if (goog.isFunction(obj[prop])) {
|
||||
this.mockedItem[prop] = goog.bind(this.executeStub, this, prop);
|
||||
// The stub binder used to create bindings.
|
||||
this.objectStubBinder_[prop] =
|
||||
goog.bind(this.handleMockCall_, this, prop);
|
||||
// The verifier verifies the calls.
|
||||
this.objectCallVerifier_[prop] =
|
||||
goog.bind(this.verifyInvocation, this, prop);
|
||||
}
|
||||
}
|
||||
// The alias for stub binder exposed to the world.
|
||||
this.mockedItem.$stubBinder = this.objectStubBinder_;
|
||||
|
||||
// The alias for verifier for the world.
|
||||
this.mockedItem.$callVerifier = this.objectCallVerifier_;
|
||||
};
|
||||
goog.inherits(goog.labs.mock.MockObjectManager_,
|
||||
goog.labs.mock.MockManager_);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sets up the spying behavior for the given object.
|
||||
*
|
||||
* @param {!Object} obj The object to be spied on.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {goog.labs.mock.MockObjectManager_}
|
||||
* @private
|
||||
*/
|
||||
goog.labs.mock.MockSpyManager_ = function(obj) {
|
||||
goog.base(this, obj);
|
||||
};
|
||||
goog.inherits(goog.labs.mock.MockSpyManager_,
|
||||
goog.labs.mock.MockObjectManager_);
|
||||
|
||||
|
||||
/**
|
||||
* Return a stub, if defined, for the method and arguments passed in. If we lack
|
||||
* a stub, instead look for a call record that matches the method and arguments.
|
||||
*
|
||||
* @return {Function} The stub or the invocation logger, if defined.
|
||||
* @override
|
||||
*/
|
||||
goog.labs.mock.MockSpyManager_.prototype.findBinding =
|
||||
function(methodName, args) {
|
||||
var stub = goog.base(this, 'findBinding', methodName, args);
|
||||
|
||||
if (!stub) {
|
||||
stub = goog.bind(this.mockee[methodName], this.mockee);
|
||||
}
|
||||
|
||||
return stub;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sets up mock for the given function, stubbing out. By default, all stubs
|
||||
* return {@code undefined}, though stubs can be later defined using
|
||||
* {@code goog.labs.mock.when}.
|
||||
*
|
||||
* @param {!Function} func The function to set up the mock for.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {goog.labs.mock.MockManager_}
|
||||
* @private
|
||||
*/
|
||||
goog.labs.mock.MockFunctionManager_ = function(func) {
|
||||
goog.base(this);
|
||||
|
||||
this.func_ = func;
|
||||
|
||||
/**
|
||||
* The stub binder used to create bindings.
|
||||
* Sets the first argument of handleMockCall_ to the function name.
|
||||
* @type {!Function}
|
||||
* @private
|
||||
*/
|
||||
this.functionStubBinder_ = this.useMockedFunctionName_(this.handleMockCall_);
|
||||
|
||||
this.mockedItem = this.useMockedFunctionName_(this.executeStub);
|
||||
this.mockedItem.$stubBinder = this.functionStubBinder_;
|
||||
|
||||
/**
|
||||
* The call verifier is used to verify function invocations.
|
||||
* Sets the first argument of verifyInvocation to the function name.
|
||||
* @type {!Function}
|
||||
*/
|
||||
this.mockedItem.$callVerifier =
|
||||
this.useMockedFunctionName_(this.verifyInvocation);
|
||||
};
|
||||
goog.inherits(goog.labs.mock.MockFunctionManager_,
|
||||
goog.labs.mock.MockManager_);
|
||||
|
||||
|
||||
/**
|
||||
* Given a method, returns a new function that calls the first one setting
|
||||
* the first argument to the mocked function name.
|
||||
* This is used to dynamically override the stub binders and call verifiers.
|
||||
* @private
|
||||
* @param {Function} nextFunc The function to override.
|
||||
* @return {!Function} The overloaded function.
|
||||
*/
|
||||
goog.labs.mock.MockFunctionManager_.prototype.useMockedFunctionName_ =
|
||||
function(nextFunc) {
|
||||
return goog.bind(function(var_args) {
|
||||
var args = goog.array.slice(arguments, 0);
|
||||
var name =
|
||||
'#mockFor<' + goog.labs.mock.getFunctionName_(this.func_) + '>';
|
||||
goog.array.insertAt(args, name, 0);
|
||||
return nextFunc.apply(this, args);
|
||||
}, this);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The stub binder is the object that helps define the stubs by binding
|
||||
* method name to the stub method.
|
||||
*
|
||||
* @param {!goog.labs.mock.MockManager_}
|
||||
* mockManager The mock manager.
|
||||
* @param {?string} name The method name.
|
||||
* @param {!Array} args The other arguments to the method.
|
||||
*
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
goog.labs.mock.StubBinder_ = function(mockManager, name, args) {
|
||||
/**
|
||||
* The mock manager instance.
|
||||
* @type {!goog.labs.mock.MockManager_}
|
||||
* @private
|
||||
*/
|
||||
this.mockManager_ = mockManager;
|
||||
|
||||
/**
|
||||
* Holds the name of the method to be bound.
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
this.name_ = name;
|
||||
|
||||
/**
|
||||
* Holds the arguments for the method.
|
||||
* @type {!Array}
|
||||
* @private
|
||||
*/
|
||||
this.args_ = args;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Defines the stub to be called for the method name and arguments bound
|
||||
* earlier.
|
||||
* TODO(user): Add support for the 'Answer' interface.
|
||||
*
|
||||
* @param {!Function} func The stub.
|
||||
*/
|
||||
goog.labs.mock.StubBinder_.prototype.then = function(func) {
|
||||
this.mockManager_.addBinding(this.name_, this.args_, func);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Defines the stub to return a specific value for a method name and arguments.
|
||||
*
|
||||
* @param {*} value The value to return.
|
||||
*/
|
||||
goog.labs.mock.StubBinder_.prototype.thenReturn = function(value) {
|
||||
this.mockManager_.addBinding(this.name_, this.args_,
|
||||
goog.functions.constant(value));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Facilitates (and is the first step in) setting up stubs. Obtains an object
|
||||
* on which, the method to be mocked is called to create a stub. Sample usage:
|
||||
*
|
||||
* var mockObj = goog.labs.mock.mock(objectBeingMocked);
|
||||
* goog.labs.mock.when(mockObj).getFoo(3).thenReturn(4);
|
||||
*
|
||||
* @param {!Object} mockObject The mocked object.
|
||||
* @return {!goog.labs.mock.StubBinder_} The property binder.
|
||||
*/
|
||||
goog.labs.mock.when = function(mockObject) {
|
||||
goog.asserts.assert(mockObject.$stubBinder, 'Stub binder cannot be null!');
|
||||
return mockObject.$stubBinder;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Represents a binding between a method name, args and a stub.
|
||||
*
|
||||
* @param {?string} methodName The name of the method being stubbed.
|
||||
* @param {!Array} args The arguments passed to the method.
|
||||
* @param {!Function} stub The stub function to be called for the given method.
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
goog.labs.mock.MethodBinding_ = function(methodName, args, stub) {
|
||||
/**
|
||||
* The name of the method being stubbed.
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
this.methodName_ = methodName;
|
||||
|
||||
/**
|
||||
* The arguments for the method being stubbed.
|
||||
* @type {!Array}
|
||||
* @private
|
||||
*/
|
||||
this.args_ = args;
|
||||
|
||||
/**
|
||||
* The stub function.
|
||||
* @type {!Function}
|
||||
* @private
|
||||
*/
|
||||
this.stub_ = stub;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!Function} The stub to be executed.
|
||||
*/
|
||||
goog.labs.mock.MethodBinding_.prototype.getStub = function() {
|
||||
return this.stub_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @return {string} A readable string representation of the binding
|
||||
* as a method call.
|
||||
*/
|
||||
goog.labs.mock.MethodBinding_.prototype.toString = function() {
|
||||
return goog.labs.mock.formatMethodCall_(this.methodName_ || '', this.args_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} The method name for this binding.
|
||||
*/
|
||||
goog.labs.mock.MethodBinding_.prototype.getMethodName = function() {
|
||||
return this.methodName_ || '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether the given args match the stored args_. Used to determine
|
||||
* which stub to invoke for a method.
|
||||
*
|
||||
* @param {string} methodName The name of the method being stubbed.
|
||||
* @param {!Array} args An array of arguments.
|
||||
* @param {boolean} isVerification Whether this is a function verification call
|
||||
* or not.
|
||||
* @return {boolean} If it matches the stored arguments.
|
||||
*/
|
||||
goog.labs.mock.MethodBinding_.prototype.matches = function(
|
||||
methodName, args, isVerification) {
|
||||
var specs = isVerification ? args : this.args_;
|
||||
var calls = isVerification ? this.args_ : args;
|
||||
|
||||
//TODO(user): More elaborate argument matching. Think about matching
|
||||
// objects.
|
||||
return this.methodName_ == methodName &&
|
||||
goog.array.equals(calls, specs, function(arg, spec) {
|
||||
// Duck-type to see if this is an object that implements the
|
||||
// goog.labs.testing.Matcher interface.
|
||||
if (goog.isFunction(spec.matches)) {
|
||||
return spec.matches(arg);
|
||||
} else {
|
||||
return goog.array.defaultCompareEquality(spec, arg);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
// 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 Simple image loader, used for preloading.
|
||||
* @author nnaze@google.com (Nathan Naze)
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.net.image');
|
||||
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.net.EventType');
|
||||
goog.require('goog.result.SimpleResult');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Loads a single image. Useful for preloading images. May be combined with
|
||||
* goog.result.combine to preload many images.
|
||||
*
|
||||
* @param {string} uri URI of the image.
|
||||
* @param {(Image|function(): !Image)=} opt_image If present, instead of
|
||||
* creating a new Image instance the function will use the passed Image
|
||||
* instance or the result of calling the Image factory respectively. This
|
||||
* can be used to control exactly how Image instances are created, for
|
||||
* example if they should be created in a particular document element, or
|
||||
* have fields that will trigger CORS image fetches.
|
||||
* @return {!goog.result.Result} An asyncronous result that will succeed
|
||||
* if the image successfully loads or error if the image load fails.
|
||||
*/
|
||||
goog.labs.net.image.load = function(uri, opt_image) {
|
||||
var image;
|
||||
if (!goog.isDef(opt_image)) {
|
||||
image = new Image();
|
||||
} else if (goog.isFunction(opt_image)) {
|
||||
image = opt_image();
|
||||
} else {
|
||||
image = opt_image;
|
||||
}
|
||||
|
||||
// IE's load event on images can be buggy. Instead, we wait for
|
||||
// readystatechange events and check if readyState is 'complete'.
|
||||
// See:
|
||||
// http://msdn.microsoft.com/en-us/library/ie/ms536957(v=vs.85).aspx
|
||||
// http://msdn.microsoft.com/en-us/library/ie/ms534359(v=vs.85).aspx
|
||||
var loadEvent = goog.userAgent.IE ? goog.net.EventType.READY_STATE_CHANGE :
|
||||
goog.events.EventType.LOAD;
|
||||
|
||||
var result = new goog.result.SimpleResult();
|
||||
|
||||
var handler = new goog.events.EventHandler();
|
||||
handler.listen(
|
||||
image,
|
||||
[loadEvent, goog.net.EventType.ABORT, goog.net.EventType.ERROR],
|
||||
function(e) {
|
||||
|
||||
// We only registered listeners for READY_STATE_CHANGE for IE.
|
||||
// If readyState is now COMPLETE, the image has loaded.
|
||||
// See related comment above.
|
||||
if (e.type == goog.net.EventType.READY_STATE_CHANGE &&
|
||||
image.readyState != goog.net.EventType.COMPLETE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// At this point, we know whether the image load was successful
|
||||
// and no longer care about image events.
|
||||
goog.dispose(handler);
|
||||
|
||||
// Whether the image successfully loaded.
|
||||
if (e.type == loadEvent) {
|
||||
result.setValue(image);
|
||||
} else {
|
||||
result.setError();
|
||||
}
|
||||
});
|
||||
|
||||
// Initiate the image request.
|
||||
image.src = uri;
|
||||
|
||||
return result;
|
||||
};
|
||||
@@ -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 Unit tests for goog.labs.net.Image.
|
||||
*
|
||||
* @author nnaze@google.com (Nathan Naze)
|
||||
*/
|
||||
|
||||
|
||||
/** @suppress {extraProvide} */
|
||||
goog.provide('goog.labs.net.imageTest');
|
||||
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.labs.net.image');
|
||||
goog.require('goog.result');
|
||||
goog.require('goog.result.Result');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.testing.AsyncTestCase');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.recordFunction');
|
||||
|
||||
goog.setTestOnly('goog.labs.net.ImageTest');
|
||||
|
||||
var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall();
|
||||
|
||||
function testValidImage() {
|
||||
var url = 'testdata/cleardot.gif';
|
||||
|
||||
asyncTestCase.waitForAsync('image load');
|
||||
|
||||
assertEquals(0, goog.events.getTotalListenerCount());
|
||||
|
||||
var result = goog.labs.net.image.load(url);
|
||||
|
||||
goog.result.waitOnSuccess(result, function(value) {
|
||||
|
||||
assertEquals(goog.result.Result.State.SUCCESS, result.getState());
|
||||
|
||||
assertEquals('IMG', value.tagName);
|
||||
assertTrue(goog.string.endsWith(value.src, url));
|
||||
assertUndefined(result.getError());
|
||||
|
||||
assertEquals('Listeners should have been cleaned up.',
|
||||
0, goog.events.getTotalListenerCount());
|
||||
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
}
|
||||
|
||||
function testInvalidImage() {
|
||||
|
||||
var url = 'testdata/invalid.gif'; // This file does not exist.
|
||||
|
||||
asyncTestCase.waitForAsync('image load');
|
||||
|
||||
assertEquals(0, goog.events.getTotalListenerCount());
|
||||
|
||||
var result = goog.labs.net.image.load(url);
|
||||
|
||||
goog.result.wait(result, function(result) {
|
||||
|
||||
assertEquals(goog.result.Result.State.ERROR, result.getState());
|
||||
assertUndefined(result.getValue());
|
||||
assertUndefined(result.getError());
|
||||
|
||||
assertEquals('Listeners should have been cleaned up.',
|
||||
0, goog.events.getTotalListenerCount());
|
||||
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
}
|
||||
|
||||
function testImageFactory() {
|
||||
var returnedImage = new Image();
|
||||
var factory = function() {
|
||||
return returnedImage;
|
||||
};
|
||||
var countedFactory = goog.testing.recordFunction(factory);
|
||||
|
||||
var url = 'testdata/cleardot.gif';
|
||||
|
||||
asyncTestCase.waitForAsync('image load');
|
||||
assertEquals(0, goog.events.getTotalListenerCount());
|
||||
var result = goog.labs.net.image.load(url, countedFactory);
|
||||
|
||||
goog.result.waitOnSuccess(result, function(value) {
|
||||
assertEquals(goog.result.Result.State.SUCCESS, result.getState());
|
||||
assertEquals(returnedImage, value);
|
||||
assertEquals(1, countedFactory.getCallCount());
|
||||
assertUndefined(result.getError());
|
||||
|
||||
assertEquals('Listeners should have been cleaned up.',
|
||||
0, goog.events.getTotalListenerCount());
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
}
|
||||
|
||||
function testExistingImage() {
|
||||
var image = new Image();
|
||||
|
||||
var url = 'testdata/cleardot.gif';
|
||||
|
||||
asyncTestCase.waitForAsync('image load');
|
||||
assertEquals(0, goog.events.getTotalListenerCount());
|
||||
var result = goog.labs.net.image.load(url, image);
|
||||
|
||||
goog.result.waitOnSuccess(result, function(value) {
|
||||
assertEquals(goog.result.Result.State.SUCCESS, result.getState());
|
||||
assertEquals(image, value);
|
||||
assertUndefined(result.getError());
|
||||
|
||||
assertEquals('Listeners should have been cleaned up.',
|
||||
0, goog.events.getTotalListenerCount());
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// 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 API spec for the WebChannel messaging library.
|
||||
*
|
||||
* Similar to HTML5 WebSocket and Closure BrowserChannel, WebChannel
|
||||
* offers an abstraction for point-to-point socket-like communication between
|
||||
* a browser client and a remote origin.
|
||||
*
|
||||
* WebChannels are created via <code>WebChannel</code>. Multiple WebChannels
|
||||
* may be multiplexed over the same WebChannelTransport, which represents
|
||||
* the underlying physical connectivity over standard wire protocols
|
||||
* such as HTTP and SPDY.
|
||||
*
|
||||
* A WebChannels in turn represents a logical communication channel between
|
||||
* the client and server end point. A WebChannel remains open for
|
||||
* as long as the client or server end-point allows.
|
||||
*
|
||||
* Messages may be delivered in-order or out-of-order, reliably or unreliably
|
||||
* over the same WebChannel. Message delivery guarantees of a WebChannel is
|
||||
* to be specified by the application code; and the choice of the
|
||||
* underlying wire protocols is completely transparent to the API users.
|
||||
*
|
||||
* Client-to-client messaging via WebRTC based transport may also be support
|
||||
* via the same WebChannel API in future.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.WebChannel');
|
||||
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.Event');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A WebChannel represents a logical bi-directional channel over which the
|
||||
* client communicates with a remote server that holds the other endpoint
|
||||
* of the channel. A WebChannel is always created in the context of a shared
|
||||
* {@link WebChannelTransport} instance. It is up to the underlying client-side
|
||||
* and server-side implementations to decide how or when multiplexing is
|
||||
* to be enabled.
|
||||
*
|
||||
* @interface
|
||||
* @extends {EventTarget}
|
||||
*/
|
||||
goog.net.WebChannel = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Configuration spec of a WebChannel (TODO(user): to complete):
|
||||
* 1) delivery: ordered, reliable, timeout
|
||||
* 2) HTTP: special headers, URI prefix, cross domains
|
||||
* 3) debugging: stats, logging
|
||||
* 4) pattern: full-duplex, server-client or client-server messaging
|
||||
* 5) QoS: priority, throttling,
|
||||
* 6) buffer management: batch size, delivery interval
|
||||
*
|
||||
* WebChannels are configured in the context of the containing
|
||||
* {@link WebChannelTransport}. The configuration parameters are specified
|
||||
* when a new instance of WebChannel is created via {@link WebChannelTransport}.
|
||||
*
|
||||
* @typedef {{
|
||||
* ordered: (boolean|undefined),
|
||||
* reliable: (boolean|undefined),
|
||||
* timeoutMs: (number|undefined),
|
||||
* priority: (number|undefined)
|
||||
* }}
|
||||
*/
|
||||
goog.net.WebChannel.Options;
|
||||
|
||||
|
||||
/**
|
||||
* Types that are allowed as message data.
|
||||
*
|
||||
* @typedef {(ArrayBuffer|Blob|Object.<String>|Array)}
|
||||
*/
|
||||
goog.net.WebChannel.MessageData;
|
||||
|
||||
|
||||
/**
|
||||
* Open the WebChannel against the URI specified in the constructor.
|
||||
*/
|
||||
goog.net.WebChannel.prototype.open = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Close the WebChannel.
|
||||
*/
|
||||
goog.net.WebChannel.prototype.close = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Sends a message to the server that maintains the other end point of
|
||||
* the WebChannel.
|
||||
*
|
||||
* @param {!goog.net.WebChannel.MessageData} message The message to send.
|
||||
*/
|
||||
goog.net.WebChannel.prototype.send = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Common events fired by WebChannels.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.net.WebChannel.EventType = {
|
||||
/** Dispatched when the channel is opened. */
|
||||
OPEN: goog.events.getUniqueId('open'),
|
||||
|
||||
/** Dispatched when the channel is closed. */
|
||||
CLOSE: goog.events.getUniqueId('close'),
|
||||
|
||||
/** Dispatched when the channel is aborted due to errors. */
|
||||
ERROR: goog.events.getUniqueId('error'),
|
||||
|
||||
/** Dispatched when the channel has received a new message. */
|
||||
MESSAGE: goog.events.getUniqueId('message')
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The event interface for the MESSAGE event.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {goog.events.Event}
|
||||
*/
|
||||
goog.net.WebChannel.MessageEvent = function() {
|
||||
goog.base(this, goog.net.WebChannel.EventType.MESSAGE);
|
||||
};
|
||||
goog.inherits(goog.net.WebChannel.MessageEvent, goog.events.Event);
|
||||
|
||||
|
||||
/**
|
||||
* The content of the message received from the server.
|
||||
*
|
||||
* @type {!goog.net.WebChannel.MessageData}
|
||||
*/
|
||||
goog.net.WebChannel.MessageEvent.prototype.data;
|
||||
|
||||
|
||||
/**
|
||||
* WebChannel level error conditions.
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.net.WebChannel.ErrorStatus = {
|
||||
/** No error has occurred. */
|
||||
OK: 0,
|
||||
|
||||
/** Communication to the server has failed. */
|
||||
NETWORK_ERROR: 1,
|
||||
|
||||
/** The server fails to accept the WebChannel. */
|
||||
SERVER_ERROR: 2
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The event interface for the ERROR event.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {goog.events.Event}
|
||||
*/
|
||||
goog.net.WebChannel.ErrorEvent = function() {
|
||||
goog.base(this, goog.net.WebChannel.EventType.ERROR);
|
||||
};
|
||||
goog.inherits(goog.net.WebChannel.ErrorEvent, goog.events.Event);
|
||||
|
||||
|
||||
/**
|
||||
* The error status.
|
||||
*
|
||||
* @type {!goog.net.WebChannel.ErrorStatus}
|
||||
*/
|
||||
goog.net.WebChannel.ErrorEvent.prototype.status;
|
||||
@@ -0,0 +1,639 @@
|
||||
// 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 Base TestChannel implementation.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.labs.net.webChannel.BaseTestChannel');
|
||||
|
||||
goog.require('goog.json.EvalJsonProcessor');
|
||||
goog.require('goog.labs.net.webChannel.Channel');
|
||||
goog.require('goog.labs.net.webChannel.WebChannelRequest');
|
||||
goog.require('goog.labs.net.webChannel.requestStats');
|
||||
goog.require('goog.labs.net.webChannel.requestStats.ServerReachability');
|
||||
goog.require('goog.labs.net.webChannel.requestStats.Stat');
|
||||
goog.require('goog.net.tmpnetwork');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A TestChannel is used during the first part of channel negotiation
|
||||
* with the server to create the channel. It helps us determine whether we're
|
||||
* behind a buffering proxy. It also runs the logic to see if the channel
|
||||
* has been blocked by a network administrator.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!goog.labs.net.webChannel.Channel} channel The channel
|
||||
* that owns this test channel.
|
||||
* @param {!goog.labs.net.webChannel.WebChannelDebug} channelDebug A
|
||||
* WebChannelDebug instance to use for logging.
|
||||
* @implements {goog.labs.net.webChannel.Channel}
|
||||
*/
|
||||
goog.labs.net.webChannel.BaseTestChannel = function(channel, channelDebug) {
|
||||
/**
|
||||
* The channel that owns this test channel
|
||||
* @type {!goog.labs.net.webChannel.Channel}
|
||||
* @private
|
||||
*/
|
||||
this.channel_ = channel;
|
||||
|
||||
/**
|
||||
* The channel debug to use for logging
|
||||
* @type {!goog.labs.net.webChannel.WebChannelDebug}
|
||||
* @private
|
||||
*/
|
||||
this.channelDebug_ = channelDebug;
|
||||
|
||||
/**
|
||||
* Parser for a response payload. Defaults to use
|
||||
* {@code goog.json.unsafeParse}. The parser should return an array.
|
||||
* @type {goog.string.Parser}
|
||||
* @private
|
||||
*/
|
||||
this.parser_ = new goog.json.EvalJsonProcessor(null, true);
|
||||
};
|
||||
|
||||
|
||||
goog.scope(function() {
|
||||
var BaseTestChannel = goog.labs.net.webChannel.BaseTestChannel;
|
||||
var WebChannelDebug = goog.labs.net.webChannel.WebChannelDebug;
|
||||
var WebChannelRequest = goog.labs.net.webChannel.WebChannelRequest;
|
||||
var requestStats = goog.labs.net.webChannel.requestStats;
|
||||
var Channel = goog.labs.net.webChannel.Channel;
|
||||
|
||||
|
||||
/**
|
||||
* Extra HTTP headers to add to all the requests sent to the server.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
BaseTestChannel.prototype.extraHeaders_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The test request.
|
||||
* @type {WebChannelRequest}
|
||||
* @private
|
||||
*/
|
||||
BaseTestChannel.prototype.request_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Whether we have received the first result as an intermediate result. This
|
||||
* helps us determine whether we're behind a buffering proxy.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
BaseTestChannel.prototype.receivedIntermediateResult_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* The time when the test request was started. We use timing in IE as
|
||||
* a heuristic for whether we're behind a buffering proxy.
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
BaseTestChannel.prototype.startTime_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The time for of the first result part. We use timing in IE as a
|
||||
* heuristic for whether we're behind a buffering proxy.
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
BaseTestChannel.prototype.firstTime_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The time for of the last result part. We use timing in IE as a
|
||||
* heuristic for whether we're behind a buffering proxy.
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
BaseTestChannel.prototype.lastTime_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The relative path for test requests.
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
BaseTestChannel.prototype.path_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The state of the state machine for this object.
|
||||
*
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
BaseTestChannel.prototype.state_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The last status code received.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
BaseTestChannel.prototype.lastStatusCode_ = -1;
|
||||
|
||||
|
||||
/**
|
||||
* A subdomain prefix for using a subdomain in IE for the backchannel
|
||||
* requests.
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
BaseTestChannel.prototype.hostPrefix_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* A subdomain prefix for testing whether the channel was disabled by
|
||||
* a network administrator;
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
BaseTestChannel.prototype.blockedPrefix_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Enum type for the test channel state machine
|
||||
* @enum {number}
|
||||
* @private
|
||||
*/
|
||||
BaseTestChannel.State_ = {
|
||||
/**
|
||||
* The state for the TestChannel state machine where we making the
|
||||
* initial call to get the server configured parameters.
|
||||
*/
|
||||
INIT: 0,
|
||||
|
||||
/**
|
||||
* The state for the TestChannel state machine where we're checking to
|
||||
* see if the channel has been blocked.
|
||||
*/
|
||||
CHECKING_BLOCKED: 1,
|
||||
|
||||
/**
|
||||
* The state for the TestChannel state machine where we're checking to
|
||||
* se if we're behind a buffering proxy.
|
||||
*/
|
||||
CONNECTION_TESTING: 2
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Time in MS for waiting for the request to see if the channel is blocked.
|
||||
* If the response takes longer than this many ms, we assume the request has
|
||||
* failed.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
BaseTestChannel.BLOCKED_TIMEOUT_ = 5000;
|
||||
|
||||
|
||||
/**
|
||||
* Number of attempts to try to see if the check to see if we're blocked
|
||||
* succeeds. Sometimes the request can fail because of flaky network conditions
|
||||
* and checking multiple times reduces false positives.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
BaseTestChannel.BLOCKED_RETRIES_ = 3;
|
||||
|
||||
|
||||
/**
|
||||
* Time in ms between retries of the blocked request
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
BaseTestChannel.BLOCKED_PAUSE_BETWEEN_RETRIES_ = 2000;
|
||||
|
||||
|
||||
/**
|
||||
* Time between chunks in the test connection that indicates that we
|
||||
* are not behind a buffering proxy. This value should be less than or
|
||||
* equals to the time between chunks sent from the server.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
BaseTestChannel.MIN_TIME_EXPECTED_BETWEEN_DATA_ = 500;
|
||||
|
||||
|
||||
/**
|
||||
* Sets extra HTTP headers to add to all the requests sent to the server.
|
||||
*
|
||||
* @param {Object} extraHeaders The HTTP headers.
|
||||
*/
|
||||
BaseTestChannel.prototype.setExtraHeaders = function(extraHeaders) {
|
||||
this.extraHeaders_ = extraHeaders;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets a new parser for the response payload. A custom parser may be set to
|
||||
* avoid using eval(), for example.
|
||||
* By default, the parser uses {@code goog.json.unsafeParse}.
|
||||
* @param {!goog.string.Parser} parser Parser.
|
||||
*/
|
||||
BaseTestChannel.prototype.setParser = function(parser) {
|
||||
this.parser_ = parser;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Starts the test channel. This initiates connections to the server.
|
||||
*
|
||||
* @param {string} path The relative uri for the test connection.
|
||||
*/
|
||||
BaseTestChannel.prototype.connect = function(path) {
|
||||
this.path_ = path;
|
||||
var sendDataUri = this.channel_.getForwardChannelUri(this.path_);
|
||||
|
||||
requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_ONE_START);
|
||||
this.startTime_ = goog.now();
|
||||
|
||||
// If the channel already has the result of the first test, then skip it.
|
||||
var firstTestResults = this.channel_.getFirstTestResults();
|
||||
if (goog.isDefAndNotNull(firstTestResults)) {
|
||||
this.hostPrefix_ = this.channel_.correctHostPrefix(firstTestResults[0]);
|
||||
this.blockedPrefix_ = firstTestResults[1];
|
||||
if (this.blockedPrefix_) {
|
||||
this.state_ = BaseTestChannel.State_.CHECKING_BLOCKED;
|
||||
this.checkBlocked_();
|
||||
} else {
|
||||
this.state_ = BaseTestChannel.State_.CONNECTION_TESTING;
|
||||
this.connectStage2_();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// the first request returns server specific parameters
|
||||
sendDataUri.setParameterValues('MODE', 'init');
|
||||
this.request_ = WebChannelRequest.createChannelRequest(
|
||||
this, this.channelDebug_);
|
||||
this.request_.setExtraHeaders(this.extraHeaders_);
|
||||
this.request_.xmlHttpGet(sendDataUri, false /* decodeChunks */,
|
||||
null /* hostPrefix */, true /* opt_noClose */);
|
||||
this.state_ = BaseTestChannel.State_.INIT;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks to see whether the channel is blocked. This is for implementing the
|
||||
* feature that allows network administrators to block Gmail Chat. The
|
||||
* strategy to determine if we're blocked is to try to load an image off a
|
||||
* special subdomain that network administrators will block access to if they
|
||||
* are trying to block chat. For Gmail Chat, the subdomain is
|
||||
* chatenabled.mail.google.com.
|
||||
* @private
|
||||
*/
|
||||
BaseTestChannel.prototype.checkBlocked_ = function() {
|
||||
var uri = this.channel_.createDataUri(this.blockedPrefix_,
|
||||
'/mail/images/cleardot.gif');
|
||||
uri.makeUnique();
|
||||
goog.net.tmpnetwork.testLoadImageWithRetries(uri.toString(),
|
||||
BaseTestChannel.BLOCKED_TIMEOUT_,
|
||||
goog.bind(this.checkBlockedCallback_, this),
|
||||
BaseTestChannel.BLOCKED_RETRIES_,
|
||||
BaseTestChannel.BLOCKED_PAUSE_BETWEEN_RETRIES_);
|
||||
requestStats.notifyServerReachabilityEvent(
|
||||
requestStats.ServerReachability.REQUEST_MADE);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback for testLoadImageWithRetries to check if a channel is blocked.
|
||||
* @param {boolean} succeeded Whether the request succeeded.
|
||||
* @private
|
||||
*/
|
||||
BaseTestChannel.prototype.checkBlockedCallback_ = function(
|
||||
succeeded) {
|
||||
if (succeeded) {
|
||||
this.state_ = BaseTestChannel.State_.CONNECTION_TESTING;
|
||||
this.connectStage2_();
|
||||
} else {
|
||||
requestStats.notifyStatEvent(requestStats.Stat.CHANNEL_BLOCKED);
|
||||
this.channel_.testConnectionBlocked(this);
|
||||
}
|
||||
|
||||
// We don't dispatch a REQUEST_FAILED server reachability event when the
|
||||
// block request fails, as such a failure is not a good signal that the
|
||||
// server has actually become unreachable.
|
||||
if (succeeded) {
|
||||
requestStats.notifyServerReachabilityEvent(
|
||||
requestStats.ServerReachability.REQUEST_SUCCEEDED);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Begins the second stage of the test channel where we test to see if we're
|
||||
* behind a buffering proxy. The server sends back a multi-chunked response
|
||||
* with the first chunk containing the content '1' and then two seconds later
|
||||
* sending the second chunk containing the content '2'. Depending on how we
|
||||
* receive the content, we can tell if we're behind a buffering proxy.
|
||||
* @private
|
||||
*/
|
||||
BaseTestChannel.prototype.connectStage2_ = function() {
|
||||
this.channelDebug_.debug('TestConnection: starting stage 2');
|
||||
|
||||
// If the second test results are available, skip its execution.
|
||||
var secondTestResults = this.channel_.getSecondTestResults();
|
||||
if (goog.isDefAndNotNull(secondTestResults)) {
|
||||
this.channelDebug_.debug(
|
||||
'TestConnection: skipping stage 2, precomputed result is ' +
|
||||
secondTestResults ? 'Buffered' : 'Unbuffered');
|
||||
requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_START);
|
||||
if (secondTestResults) { // Buffered/Proxy connection
|
||||
requestStats.notifyStatEvent(requestStats.Stat.PROXY);
|
||||
this.channel_.testConnectionFinished(this, false);
|
||||
} else { // Unbuffered/NoProxy connection
|
||||
requestStats.notifyStatEvent(requestStats.Stat.NOPROXY);
|
||||
this.channel_.testConnectionFinished(this, true);
|
||||
}
|
||||
return; // Skip the test
|
||||
}
|
||||
this.request_ = WebChannelRequest.createChannelRequest(
|
||||
this, this.channelDebug_);
|
||||
this.request_.setExtraHeaders(this.extraHeaders_);
|
||||
var recvDataUri = this.channel_.getBackChannelUri(this.hostPrefix_,
|
||||
/** @type {string} */ (this.path_));
|
||||
|
||||
requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_START);
|
||||
if (!WebChannelRequest.supportsXhrStreaming()) {
|
||||
recvDataUri.setParameterValues('TYPE', 'html');
|
||||
this.request_.tridentGet(recvDataUri, Boolean(this.hostPrefix_));
|
||||
} else {
|
||||
recvDataUri.setParameterValues('TYPE', 'xmlhttp');
|
||||
this.request_.xmlHttpGet(recvDataUri, false /** decodeChunks */,
|
||||
this.hostPrefix_, false /** opt_noClose */);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
BaseTestChannel.prototype.createXhrIo = function(hostPrefix) {
|
||||
return this.channel_.createXhrIo(hostPrefix);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Aborts the test channel.
|
||||
*/
|
||||
BaseTestChannel.prototype.abort = function() {
|
||||
if (this.request_) {
|
||||
this.request_.cancel();
|
||||
this.request_ = null;
|
||||
}
|
||||
this.lastStatusCode_ = -1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the test channel is closed. The ChannelRequest object expects
|
||||
* this method to be implemented on its handler.
|
||||
*
|
||||
* @return {boolean} Whether the channel is closed.
|
||||
* @override
|
||||
*/
|
||||
BaseTestChannel.prototype.isClosed = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback from ChannelRequest for when new data is received
|
||||
*
|
||||
* @param {WebChannelRequest} req The request object.
|
||||
* @param {string} responseText The text of the response.
|
||||
* @override
|
||||
*/
|
||||
BaseTestChannel.prototype.onRequestData = function(req, responseText) {
|
||||
this.lastStatusCode_ = req.getLastStatusCode();
|
||||
if (this.state_ == BaseTestChannel.State_.INIT) {
|
||||
this.channelDebug_.debug('TestConnection: Got data for stage 1');
|
||||
if (!responseText) {
|
||||
this.channelDebug_.debug('TestConnection: Null responseText');
|
||||
// The server should always send text; something is wrong here
|
||||
this.channel_.testConnectionFailure(this,
|
||||
WebChannelRequest.Error.BAD_DATA);
|
||||
return;
|
||||
}
|
||||
/** @preserveTry */
|
||||
try {
|
||||
var respArray = this.parser_.parse(responseText);
|
||||
} catch (e) {
|
||||
this.channelDebug_.dumpException(e);
|
||||
this.channel_.testConnectionFailure(this,
|
||||
WebChannelRequest.Error.BAD_DATA);
|
||||
return;
|
||||
}
|
||||
this.hostPrefix_ = this.channel_.correctHostPrefix(respArray[0]);
|
||||
this.blockedPrefix_ = respArray[1];
|
||||
} else if (this.state_ == BaseTestChannel.State_.CONNECTION_TESTING) {
|
||||
if (this.receivedIntermediateResult_) {
|
||||
requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_DATA_TWO);
|
||||
this.lastTime_ = goog.now();
|
||||
} else {
|
||||
// '11111' is used instead of '1' to prevent a small amount of buffering
|
||||
// by Safari.
|
||||
if (responseText == '11111') {
|
||||
requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_DATA_ONE);
|
||||
this.receivedIntermediateResult_ = true;
|
||||
this.firstTime_ = goog.now();
|
||||
if (this.checkForEarlyNonBuffered_()) {
|
||||
// If early chunk detection is on, and we passed the tests,
|
||||
// assume HTTP_OK, cancel the test and turn on noproxy mode.
|
||||
this.lastStatusCode_ = 200;
|
||||
this.request_.cancel();
|
||||
this.channelDebug_.debug(
|
||||
'Test connection succeeded; using streaming connection');
|
||||
requestStats.notifyStatEvent(requestStats.Stat.NOPROXY);
|
||||
this.channel_.testConnectionFinished(this, true);
|
||||
}
|
||||
} else {
|
||||
requestStats.notifyStatEvent(
|
||||
requestStats.Stat.TEST_STAGE_TWO_DATA_BOTH);
|
||||
this.firstTime_ = this.lastTime_ = goog.now();
|
||||
this.receivedIntermediateResult_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback from ChannelRequest that indicates a request has completed.
|
||||
*
|
||||
* @param {WebChannelRequest} req The request object.
|
||||
* @override
|
||||
*/
|
||||
BaseTestChannel.prototype.onRequestComplete = function(req) {
|
||||
this.lastStatusCode_ = this.request_.getLastStatusCode();
|
||||
if (!this.request_.getSuccess()) {
|
||||
this.channelDebug_.debug(
|
||||
'TestConnection: request failed, in state ' + this.state_);
|
||||
if (this.state_ == BaseTestChannel.State_.INIT) {
|
||||
requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_ONE_FAILED);
|
||||
} else if (this.state_ == BaseTestChannel.State_.CONNECTION_TESTING) {
|
||||
requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_FAILED);
|
||||
}
|
||||
this.channel_.testConnectionFailure(this,
|
||||
/** @type {WebChannelRequest.Error} */
|
||||
(this.request_.getLastError()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state_ == BaseTestChannel.State_.INIT) {
|
||||
this.channelDebug_.debug(
|
||||
'TestConnection: request complete for initial check');
|
||||
if (this.blockedPrefix_) {
|
||||
this.state_ = BaseTestChannel.State_.CHECKING_BLOCKED;
|
||||
this.checkBlocked_();
|
||||
} else {
|
||||
this.state_ = BaseTestChannel.State_.CONNECTION_TESTING;
|
||||
this.connectStage2_();
|
||||
}
|
||||
} else if (this.state_ == BaseTestChannel.State_.CONNECTION_TESTING) {
|
||||
this.channelDebug_.debug('TestConnection: request complete for stage 2');
|
||||
var goodConn = false;
|
||||
|
||||
if (!WebChannelRequest.supportsXhrStreaming()) {
|
||||
// we always get Trident responses in separate calls to
|
||||
// onRequestData, so we have to check the time they came
|
||||
var ms = this.lastTime_ - this.firstTime_;
|
||||
if (ms < 200) {
|
||||
// TODO: need to empirically verify that this number is OK
|
||||
// for slow computers
|
||||
goodConn = false;
|
||||
} else {
|
||||
goodConn = true;
|
||||
}
|
||||
} else {
|
||||
goodConn = this.receivedIntermediateResult_;
|
||||
}
|
||||
|
||||
if (goodConn) {
|
||||
this.channelDebug_.debug(
|
||||
'Test connection succeeded; using streaming connection');
|
||||
requestStats.notifyStatEvent(requestStats.Stat.NOPROXY);
|
||||
this.channel_.testConnectionFinished(this, true);
|
||||
} else {
|
||||
this.channelDebug_.debug(
|
||||
'Test connection failed; not using streaming');
|
||||
requestStats.notifyStatEvent(requestStats.Stat.PROXY);
|
||||
this.channel_.testConnectionFinished(this, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the last status code received for a request.
|
||||
* @return {number} The last status code received for a request.
|
||||
*/
|
||||
BaseTestChannel.prototype.getLastStatusCode = function() {
|
||||
return this.lastStatusCode_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether we should be using secondary domains when the
|
||||
* server instructs us to do so.
|
||||
* @override
|
||||
*/
|
||||
BaseTestChannel.prototype.shouldUseSecondaryDomains = function() {
|
||||
return this.channel_.shouldUseSecondaryDomains();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
BaseTestChannel.prototype.isActive = function() {
|
||||
return this.channel_.isActive();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} True if test stage 2 detected a non-buffered
|
||||
* channel early and early no buffering detection is enabled.
|
||||
* @private
|
||||
*/
|
||||
BaseTestChannel.prototype.checkForEarlyNonBuffered_ = function() {
|
||||
var ms = this.firstTime_ - this.startTime_;
|
||||
|
||||
// we always get Trident responses in separate calls to
|
||||
// onRequestData, so we have to check the time that the first came in
|
||||
// and verify that the data arrived before the second portion could
|
||||
// have been sent. For all other browser's we skip the timing test.
|
||||
return WebChannelRequest.supportsXhrStreaming() ||
|
||||
ms < BaseTestChannel.MIN_TIME_EXPECTED_BETWEEN_DATA_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
BaseTestChannel.prototype.getForwardChannelUri = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
BaseTestChannel.prototype.getBackChannelUri = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
BaseTestChannel.prototype.correctHostPrefix = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
BaseTestChannel.prototype.createDataUri = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
BaseTestChannel.prototype.testConnectionBlocked = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
BaseTestChannel.prototype.testConnectionFinished = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
BaseTestChannel.prototype.testConnectionFailure = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
BaseTestChannel.prototype.getFirstTestResults = goog.abstractMethod;
|
||||
}); // goog.scope
|
||||
@@ -0,0 +1,195 @@
|
||||
// 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 A shared interface for WebChannelBase and BaseTestChannel.
|
||||
*
|
||||
* @visibility {//visibility:private}
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.labs.net.webChannel.Channel');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Shared interface between Channel and TestChannel to support callbacks
|
||||
* between WebChannelBase and BaseTestChannel and between Channel and
|
||||
* ChannelRequest.
|
||||
*
|
||||
* @interface
|
||||
*/
|
||||
goog.labs.net.webChannel.Channel = function() {};
|
||||
|
||||
|
||||
goog.scope(function() {
|
||||
var Channel = goog.labs.net.webChannel.Channel;
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether to use a secondary domain when the server gives us
|
||||
* a host prefix. This allows us to work around browser per-domain
|
||||
* connection limits.
|
||||
*
|
||||
* Currently, we use secondary domains when using Trident's ActiveXObject,
|
||||
* because it supports cross-domain requests out of the box. Note that in IE10
|
||||
* we no longer use ActiveX since it's not supported in Metro mode and IE10
|
||||
* supports XHR streaming.
|
||||
*
|
||||
* If you need to use secondary domains on other browsers and IE10,
|
||||
* you have two choices:
|
||||
* 1) If you only care about browsers that support CORS
|
||||
* (https://developer.mozilla.org/en-US/docs/HTTP_access_control), you
|
||||
* can use {@link #setSupportsCrossDomainXhrs} and set the appropriate
|
||||
* CORS response headers on the server.
|
||||
* 2) Or, override this method in a subclass, and make sure that those
|
||||
* browsers use some messaging mechanism that works cross-domain (e.g
|
||||
* iframes and window.postMessage).
|
||||
*
|
||||
* @return {boolean} Whether to use secondary domains.
|
||||
* @see http://code.google.com/p/closure-library/issues/detail?id=339
|
||||
*/
|
||||
Channel.prototype.shouldUseSecondaryDomains = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Called when creating an XhrIo object. Override in a subclass if
|
||||
* you need to customize the behavior, for example to enable the creation of
|
||||
* XHR's capable of calling a secondary domain. Will also allow calling
|
||||
* a secondary domain if withCredentials (CORS) is enabled.
|
||||
* @param {?string} hostPrefix The host prefix, if we need an XhrIo object
|
||||
* capable of calling a secondary domain.
|
||||
* @return {!goog.net.XhrIo} A new XhrIo object.
|
||||
*/
|
||||
Channel.prototype.createXhrIo = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Callback from ChannelRequest that indicates a request has completed.
|
||||
* @param {goog.labs.net.webChannel.WebChannelRequest} request
|
||||
* The request object.
|
||||
*/
|
||||
Channel.prototype.onRequestComplete = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the channel is closed
|
||||
* @return {boolean} true if the channel is closed.
|
||||
*/
|
||||
Channel.prototype.isClosed = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Callback from ChannelRequest for when new data is received
|
||||
* @param {goog.labs.net.webChannel.WebChannelRequest} request
|
||||
* The request object.
|
||||
* @param {string} responseText The text of the response.
|
||||
*/
|
||||
Channel.prototype.onRequestData = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Gets whether this channel is currently active. This is used to determine the
|
||||
* length of time to wait before retrying. This call delegates to the handler.
|
||||
* @return {boolean} Whether the channel is currently active.
|
||||
*/
|
||||
Channel.prototype.isActive = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Not needed for testchannel.
|
||||
*
|
||||
* Gets the Uri used for the connection that sends data to the server.
|
||||
* @param {string} path The path on the host.
|
||||
* @return {goog.Uri} The forward channel URI.
|
||||
*/
|
||||
Channel.prototype.getForwardChannelUri = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Not needed for testchannel.
|
||||
*
|
||||
* Gets the Uri used for the connection that receives data from the server.
|
||||
* @param {?string} hostPrefix The host prefix.
|
||||
* @param {string} path The path on the host.
|
||||
* @return {goog.Uri} The back channel URI.
|
||||
*/
|
||||
Channel.prototype.getBackChannelUri = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Not needed for testchannel.
|
||||
*
|
||||
* Allows the handler to override a host prefix provided by the server. Will
|
||||
* be called whenever the channel has received such a prefix and is considering
|
||||
* its use.
|
||||
* @param {?string} serverHostPrefix The host prefix provided by the server.
|
||||
* @return {?string} The host prefix the client should use.
|
||||
*/
|
||||
Channel.prototype.correctHostPrefix = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Not needed for testchannel.
|
||||
*
|
||||
* Creates a data Uri applying logic for secondary hostprefix, port
|
||||
* overrides, and versioning.
|
||||
* @param {?string} hostPrefix The host prefix.
|
||||
* @param {string} path The path on the host (may be absolute or relative).
|
||||
* @param {number=} opt_overridePort Optional override port.
|
||||
* @return {goog.Uri} The data URI.
|
||||
*/
|
||||
Channel.prototype.createDataUri = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Not needed for testchannel.
|
||||
*
|
||||
* Callback from TestChannel for when the channel is blocked.
|
||||
* @param {goog.labs.net.webChannel.BaseTestChannel} testChannel
|
||||
* The TestChannel.
|
||||
*/
|
||||
Channel.prototype.testConnectionBlocked = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Not needed for testchannel.
|
||||
*
|
||||
* Callback from TestChannel for when the channel is finished.
|
||||
* @param {goog.labs.net.webChannel.BaseTestChannel} testChannel
|
||||
* The TestChannel.
|
||||
* @param {boolean} useChunked Whether we can chunk responses.
|
||||
*/
|
||||
Channel.prototype.testConnectionFinished = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Not needed for testchannel.
|
||||
*
|
||||
* Callback from TestChannel for when the channel has an error.
|
||||
* @param {goog.labs.net.webChannel.BaseTestChannel} testChannel
|
||||
* The TestChannel.
|
||||
* @param {goog.labs.net.webChannel.WebChannelRequest.Error} errorCode
|
||||
* The error code of the failure.
|
||||
*/
|
||||
Channel.prototype.testConnectionFailure = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Not needed for testchannel.
|
||||
* Gets the results for the first channel test
|
||||
* @return {Array.<string>} The results.
|
||||
*/
|
||||
Channel.prototype.getFirstTestResults = goog.abstractMethod;
|
||||
}); // goog.scope
|
||||
@@ -0,0 +1,391 @@
|
||||
// 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 Static utilities for collecting stats associated with
|
||||
* WebChannelRequest.
|
||||
*
|
||||
* @visibility {//visibility:private}
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.labs.net.webChannel.requestStats');
|
||||
goog.provide('goog.labs.net.webChannel.requestStats.Event');
|
||||
goog.provide('goog.labs.net.webChannel.requestStats.ServerReachability');
|
||||
goog.provide('goog.labs.net.webChannel.requestStats.ServerReachabilityEvent');
|
||||
goog.provide('goog.labs.net.webChannel.requestStats.Stat');
|
||||
goog.provide('goog.labs.net.webChannel.requestStats.StatEvent');
|
||||
goog.provide('goog.labs.net.webChannel.requestStats.TimingEvent');
|
||||
|
||||
goog.require('goog.events.Event');
|
||||
goog.require('goog.events.EventTarget');
|
||||
|
||||
|
||||
goog.scope(function() {
|
||||
var requestStats = goog.labs.net.webChannel.requestStats;
|
||||
|
||||
|
||||
/**
|
||||
* Events fired.
|
||||
* @type {Object}
|
||||
*/
|
||||
requestStats.Event = {};
|
||||
|
||||
|
||||
/**
|
||||
* Singleton event target for firing stat events
|
||||
* @type {goog.events.EventTarget}
|
||||
* @private
|
||||
*/
|
||||
requestStats.statEventTarget_ = new goog.events.EventTarget();
|
||||
|
||||
|
||||
/**
|
||||
* The type of event that occurs every time some information about how reachable
|
||||
* the server is is discovered.
|
||||
*/
|
||||
requestStats.Event.SERVER_REACHABILITY_EVENT = 'serverreachability';
|
||||
|
||||
|
||||
/**
|
||||
* Types of events which reveal information about the reachability of the
|
||||
* server.
|
||||
* @enum {number}
|
||||
*/
|
||||
requestStats.ServerReachability = {
|
||||
REQUEST_MADE: 1,
|
||||
REQUEST_SUCCEEDED: 2,
|
||||
REQUEST_FAILED: 3,
|
||||
BACK_CHANNEL_ACTIVITY: 4
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Event class for SERVER_REACHABILITY_EVENT.
|
||||
*
|
||||
* @param {goog.events.EventTarget} target The stat event target for
|
||||
the channel.
|
||||
* @param {requestStats.ServerReachability} reachabilityType
|
||||
* The reachability event type.
|
||||
* @constructor
|
||||
* @extends {goog.events.Event}
|
||||
*/
|
||||
requestStats.ServerReachabilityEvent = function(target, reachabilityType) {
|
||||
goog.events.Event.call(this,
|
||||
requestStats.Event.SERVER_REACHABILITY_EVENT, target);
|
||||
|
||||
/**
|
||||
* @type {requestStats.ServerReachability}
|
||||
*/
|
||||
this.reachabilityType = reachabilityType;
|
||||
};
|
||||
goog.inherits(requestStats.ServerReachabilityEvent, goog.events.Event);
|
||||
|
||||
|
||||
/**
|
||||
* Notify the channel that a particular fine grained network event has occurred.
|
||||
* Should be considered package-private.
|
||||
* @param {requestStats.ServerReachability} reachabilityType
|
||||
* The reachability event type.
|
||||
*/
|
||||
requestStats.notifyServerReachabilityEvent = function(reachabilityType) {
|
||||
var target = requestStats.statEventTarget_;
|
||||
target.dispatchEvent(
|
||||
new requestStats.ServerReachabilityEvent(target, reachabilityType));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stat Event that fires when things of interest happen that may be useful for
|
||||
* applications to know about for stats or debugging purposes.
|
||||
*/
|
||||
requestStats.Event.STAT_EVENT = 'statevent';
|
||||
|
||||
|
||||
/**
|
||||
* Enum that identifies events for statistics that are interesting to track.
|
||||
* TODO(user) - Change name not to use Event or use EventTarget
|
||||
* @enum {number}
|
||||
*/
|
||||
requestStats.Stat = {
|
||||
/** Event indicating a new connection attempt. */
|
||||
CONNECT_ATTEMPT: 0,
|
||||
|
||||
/** Event indicating a connection error due to a general network problem. */
|
||||
ERROR_NETWORK: 1,
|
||||
|
||||
/**
|
||||
* Event indicating a connection error that isn't due to a general network
|
||||
* problem.
|
||||
*/
|
||||
ERROR_OTHER: 2,
|
||||
|
||||
/** Event indicating the start of test stage one. */
|
||||
TEST_STAGE_ONE_START: 3,
|
||||
|
||||
|
||||
/** Event indicating the channel is blocked by a network administrator. */
|
||||
CHANNEL_BLOCKED: 4,
|
||||
|
||||
/** Event indicating the start of test stage two. */
|
||||
TEST_STAGE_TWO_START: 5,
|
||||
|
||||
/** Event indicating the first piece of test data was received. */
|
||||
TEST_STAGE_TWO_DATA_ONE: 6,
|
||||
|
||||
/**
|
||||
* Event indicating that the second piece of test data was received and it was
|
||||
* recieved separately from the first.
|
||||
*/
|
||||
TEST_STAGE_TWO_DATA_TWO: 7,
|
||||
|
||||
/** Event indicating both pieces of test data were received simultaneously. */
|
||||
TEST_STAGE_TWO_DATA_BOTH: 8,
|
||||
|
||||
/** Event indicating stage one of the test request failed. */
|
||||
TEST_STAGE_ONE_FAILED: 9,
|
||||
|
||||
/** Event indicating stage two of the test request failed. */
|
||||
TEST_STAGE_TWO_FAILED: 10,
|
||||
|
||||
/**
|
||||
* Event indicating that a buffering proxy is likely between the client and
|
||||
* the server.
|
||||
*/
|
||||
PROXY: 11,
|
||||
|
||||
/**
|
||||
* Event indicating that no buffering proxy is likely between the client and
|
||||
* the server.
|
||||
*/
|
||||
NOPROXY: 12,
|
||||
|
||||
/** Event indicating an unknown SID error. */
|
||||
REQUEST_UNKNOWN_SESSION_ID: 13,
|
||||
|
||||
/** Event indicating a bad status code was received. */
|
||||
REQUEST_BAD_STATUS: 14,
|
||||
|
||||
/** Event indicating incomplete data was received */
|
||||
REQUEST_INCOMPLETE_DATA: 15,
|
||||
|
||||
/** Event indicating bad data was received */
|
||||
REQUEST_BAD_DATA: 16,
|
||||
|
||||
/** Event indicating no data was received when data was expected. */
|
||||
REQUEST_NO_DATA: 17,
|
||||
|
||||
/** Event indicating a request timeout. */
|
||||
REQUEST_TIMEOUT: 18,
|
||||
|
||||
/**
|
||||
* Event indicating that the server never received our hanging GET and so it
|
||||
* is being retried.
|
||||
*/
|
||||
BACKCHANNEL_MISSING: 19,
|
||||
|
||||
/**
|
||||
* Event indicating that we have determined that our hanging GET is not
|
||||
* receiving data when it should be. Thus it is dead dead and will be retried.
|
||||
*/
|
||||
BACKCHANNEL_DEAD: 20,
|
||||
|
||||
/**
|
||||
* The browser declared itself offline during the lifetime of a request, or
|
||||
* was offline when a request was initially made.
|
||||
*/
|
||||
BROWSER_OFFLINE: 21,
|
||||
|
||||
/** ActiveX is blocked by the machine's admin settings. */
|
||||
ACTIVE_X_BLOCKED: 22
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Event class for STAT_EVENT.
|
||||
*
|
||||
* @param {goog.events.EventTarget} eventTarget The stat event target for
|
||||
the channel.
|
||||
* @param {requestStats.Stat} stat The stat.
|
||||
* @constructor
|
||||
* @extends {goog.events.Event}
|
||||
*/
|
||||
requestStats.StatEvent = function(eventTarget, stat) {
|
||||
goog.events.Event.call(this, requestStats.Event.STAT_EVENT, eventTarget);
|
||||
|
||||
/**
|
||||
* The stat
|
||||
* @type {requestStats.Stat}
|
||||
*/
|
||||
this.stat = stat;
|
||||
|
||||
};
|
||||
goog.inherits(requestStats.StatEvent, goog.events.Event);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the singleton event target for stat events.
|
||||
* @return {goog.events.EventTarget} The event target for stat events.
|
||||
*/
|
||||
requestStats.getStatEventTarget = function() {
|
||||
return requestStats.statEventTarget_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper function to call the stat event callback.
|
||||
* @param {requestStats.Stat} stat The stat.
|
||||
*/
|
||||
requestStats.notifyStatEvent = function(stat) {
|
||||
var target = requestStats.statEventTarget_;
|
||||
target.dispatchEvent(new requestStats.StatEvent(target, stat));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* An event that fires when POST requests complete successfully, indicating
|
||||
* the size of the POST and the round trip time.
|
||||
*/
|
||||
requestStats.Event.TIMING_EVENT = 'timingevent';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Event class for requestStats.Event.TIMING_EVENT
|
||||
*
|
||||
* @param {goog.events.EventTarget} target The stat event target for
|
||||
the channel.
|
||||
* @param {number} size The number of characters in the POST data.
|
||||
* @param {number} rtt The total round trip time from POST to response in MS.
|
||||
* @param {number} retries The number of times the POST had to be retried.
|
||||
* @constructor
|
||||
* @extends {goog.events.Event}
|
||||
*/
|
||||
requestStats.TimingEvent = function(target, size, rtt, retries) {
|
||||
goog.events.Event.call(this,
|
||||
requestStats.Event.TIMING_EVENT, target);
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
this.size = size;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
this.rtt = rtt;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
this.retries = retries;
|
||||
|
||||
};
|
||||
goog.inherits(requestStats.TimingEvent, goog.events.Event);
|
||||
|
||||
|
||||
/**
|
||||
* Helper function to notify listeners about POST request performance.
|
||||
*
|
||||
* @param {number} size Number of characters in the POST data.
|
||||
* @param {number} rtt The amount of time from POST start to response.
|
||||
* @param {number} retries The number of times the POST had to be retried.
|
||||
*/
|
||||
requestStats.notifyTimingEvent = function(size, rtt, retries) {
|
||||
var target = requestStats.statEventTarget_;
|
||||
target.dispatchEvent(
|
||||
new requestStats.TimingEvent(
|
||||
target, size, rtt, retries));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Allows the application to set an execution hooks for when a channel
|
||||
* starts processing requests. This is useful to track timing or logging
|
||||
* special information. The function takes no parameters and return void.
|
||||
* @param {Function} startHook The function for the start hook.
|
||||
*/
|
||||
requestStats.setStartThreadExecutionHook = function(startHook) {
|
||||
requestStats.startExecutionHook_ = startHook;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Allows the application to set an execution hooks for when a channel
|
||||
* stops processing requests. This is useful to track timing or logging
|
||||
* special information. The function takes no parameters and return void.
|
||||
* @param {Function} endHook The function for the end hook.
|
||||
*/
|
||||
requestStats.setEndThreadExecutionHook = function(endHook) {
|
||||
requestStats.endExecutionHook_ = endHook;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Application provided execution hook for the start hook.
|
||||
*
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
requestStats.startExecutionHook_ = function() { };
|
||||
|
||||
|
||||
/**
|
||||
* Application provided execution hook for the end hook.
|
||||
*
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
requestStats.endExecutionHook_ = function() { };
|
||||
|
||||
|
||||
/**
|
||||
* Helper function to call the start hook
|
||||
*/
|
||||
requestStats.onStartExecution = function() {
|
||||
requestStats.startExecutionHook_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper function to call the end hook
|
||||
*/
|
||||
requestStats.onEndExecution = function() {
|
||||
requestStats.endExecutionHook_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper around SafeTimeout which calls the start and end execution hooks
|
||||
* with a try...finally block.
|
||||
* @param {Function} fn The callback function.
|
||||
* @param {number} ms The time in MS for the timer.
|
||||
* @return {number} The ID of the timer.
|
||||
*/
|
||||
requestStats.setTimeout = function(fn, ms) {
|
||||
if (!goog.isFunction(fn)) {
|
||||
throw Error('Fn must not be null and must be a function');
|
||||
}
|
||||
return goog.global.setTimeout(function() {
|
||||
requestStats.onStartExecution();
|
||||
try {
|
||||
fn();
|
||||
} finally {
|
||||
requestStats.onEndExecution();
|
||||
}
|
||||
}, ms);
|
||||
};
|
||||
}); // goog.scope
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,274 @@
|
||||
// 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 Implementation of a WebChannel transport using WebChannelBase.
|
||||
*
|
||||
* When WebChannelBase is used as the underlying transport, the capabilities
|
||||
* of the WebChannel are limited to what's supported by the implementation.
|
||||
* Particularly, multiplexing is not possible, and only strings are
|
||||
* supported as message types.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.net.webChannel.WebChannelBaseTransport');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.labs.net.webChannel.WebChannelBase');
|
||||
goog.require('goog.log');
|
||||
goog.require('goog.net.WebChannel');
|
||||
goog.require('goog.net.WebChannelTransport');
|
||||
goog.require('goog.string.path');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of {@link goog.net.WebChannelTransport} with
|
||||
* {@link goog.labs.net.webChannel.WebChannelBase} as the underlying channel
|
||||
* implementation.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.net.WebChannelTransport}
|
||||
*/
|
||||
goog.labs.net.webChannel.WebChannelBaseTransport = function() {};
|
||||
|
||||
|
||||
goog.scope(function() {
|
||||
var WebChannelBaseTransport = goog.labs.net.webChannel.WebChannelBaseTransport;
|
||||
var WebChannelBase = goog.labs.net.webChannel.WebChannelBase;
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
WebChannelBaseTransport.prototype.createWebChannel = function(
|
||||
url, opt_options) {
|
||||
return new WebChannelBaseTransport.Channel(url, opt_options);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of the {@link goog.net.WebChannel} interface.
|
||||
*
|
||||
* @param {string} url The URL path for the new WebChannel instance.
|
||||
* @param {!goog.net.WebChannel.Options=} opt_options Configuration for the
|
||||
* new WebChannel instance.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.net.WebChannel}
|
||||
* @extends {goog.events.EventTarget}
|
||||
*/
|
||||
WebChannelBaseTransport.Channel = function(url, opt_options) {
|
||||
goog.base(this);
|
||||
|
||||
/**
|
||||
* The underlying channel object.
|
||||
*
|
||||
* @type {!WebChannelBase}
|
||||
* @private
|
||||
*/
|
||||
this.channel_ = new WebChannelBase();
|
||||
|
||||
/**
|
||||
* The URL of the target server end-point.
|
||||
*
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.url_ = url;
|
||||
|
||||
/**
|
||||
* The channel options.
|
||||
*
|
||||
* @type {?goog.net.WebChannel.Options}
|
||||
* @private
|
||||
*/
|
||||
this.options_ = opt_options || null;
|
||||
|
||||
/**
|
||||
* The logger for this class.
|
||||
* @type {goog.log.Logger}
|
||||
* @private
|
||||
*/
|
||||
this.logger_ = goog.log.getLogger(
|
||||
'goog.labs.net.webChannel.WebChannelBaseTransport');
|
||||
|
||||
};
|
||||
goog.inherits(WebChannelBaseTransport.Channel, goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* The channel handler.
|
||||
*
|
||||
* @type {WebChannelBase.Handler}
|
||||
* @private
|
||||
*/
|
||||
WebChannelBaseTransport.Channel.prototype.channelHandler_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Test path is always set to "/url/test".
|
||||
*
|
||||
* TODO(user): The test path may be made configurable via the options.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
WebChannelBaseTransport.Channel.prototype.open = function() {
|
||||
var testUrl = goog.string.path.join(this.url_, 'test');
|
||||
this.channel_.connect(testUrl, this.url_);
|
||||
|
||||
this.channelHandler_ = new WebChannelBaseTransport.Channel.Handler_(this);
|
||||
this.channel_.setHandler(this.channelHandler_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
WebChannelBaseTransport.Channel.prototype.close = function() {
|
||||
this.channel_.disconnect();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The WebChannelBase only supports object types.
|
||||
*
|
||||
* @param {!goog.net.WebChannel.MessageData} message The message to send.
|
||||
* @override
|
||||
*/
|
||||
WebChannelBaseTransport.Channel.prototype.send = function(message) {
|
||||
goog.asserts.assert(goog.isObject(message), 'only object type expected');
|
||||
this.channel_.sendMap(message);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
WebChannelBaseTransport.Channel.prototype.disposeInternal = function() {
|
||||
this.channel_.setHandler(null);
|
||||
delete this.channelHandler_;
|
||||
this.channel_.disconnect();
|
||||
delete this.channel_;
|
||||
|
||||
goog.base(this, 'disposeInternal');
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The message event.
|
||||
*
|
||||
* @param {!Array} array The data array from the underlying channel.
|
||||
* @constructor
|
||||
* @extends {goog.net.WebChannel.MessageEvent}
|
||||
*/
|
||||
WebChannelBaseTransport.Channel.MessageEvent = function(array) {
|
||||
goog.base(this);
|
||||
|
||||
this.data = array;
|
||||
};
|
||||
goog.inherits(WebChannelBaseTransport.Channel.MessageEvent,
|
||||
goog.net.WebChannel.MessageEvent);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The error event.
|
||||
*
|
||||
* @param {WebChannelBase.Error} error The error code.
|
||||
* @constructor
|
||||
* @extends {goog.net.WebChannel.ErrorEvent}
|
||||
*/
|
||||
WebChannelBaseTransport.Channel.ErrorEvent = function(error) {
|
||||
goog.base(this);
|
||||
|
||||
/**
|
||||
* Transport specific error code is not to be propagated with the event.
|
||||
*/
|
||||
this.status = goog.net.WebChannel.ErrorStatus.NETWORK_ERROR;
|
||||
};
|
||||
goog.inherits(WebChannelBaseTransport.Channel.ErrorEvent,
|
||||
goog.net.WebChannel.ErrorEvent);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of the {@link WebChannelBase.Handler} interface.
|
||||
*
|
||||
* @param {!WebChannelBaseTransport.Channel} channel The enclosing WebChannel.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {WebChannelBase.Handler}
|
||||
* @private
|
||||
*/
|
||||
WebChannelBaseTransport.Channel.Handler_ = function(channel) {
|
||||
goog.base(this);
|
||||
|
||||
/**
|
||||
* @type {!WebChannelBaseTransport.Channel}
|
||||
* @private
|
||||
*/
|
||||
this.channel_ = channel;
|
||||
};
|
||||
goog.inherits(WebChannelBaseTransport.Channel.Handler_, WebChannelBase.Handler);
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
WebChannelBaseTransport.Channel.Handler_.prototype.channelOpened = function(
|
||||
channel) {
|
||||
goog.log.info(this.channel_.logger_,
|
||||
'WebChannel opened on ' + this.channel_.url_);
|
||||
this.channel_.dispatchEvent(goog.net.WebChannel.EventType.OPEN);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
WebChannelBaseTransport.Channel.Handler_.prototype.channelHandleArray =
|
||||
function(channel, array) {
|
||||
goog.asserts.assert(array, 'array expected to be defined');
|
||||
this.channel_.dispatchEvent(
|
||||
new WebChannelBaseTransport.Channel.MessageEvent(array));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
WebChannelBaseTransport.Channel.Handler_.prototype.channelError = function(
|
||||
channel, error) {
|
||||
goog.log.info(this.channel_.logger_,
|
||||
'WebChannel aborted on ' + this.channel_.url_ +
|
||||
' due to channel error: ' + error);
|
||||
this.channel_.dispatchEvent(
|
||||
new WebChannelBaseTransport.Channel.ErrorEvent(error));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
WebChannelBaseTransport.Channel.Handler_.prototype.channelClosed = function(
|
||||
channel, opt_pendingMaps, opt_undeliveredMaps) {
|
||||
goog.log.info(this.channel_.logger_,
|
||||
'WebChannel closed on ' + this.channel_.url_);
|
||||
this.channel_.dispatchEvent(goog.net.WebChannel.EventType.CLOSE);
|
||||
};
|
||||
}); // goog.scope
|
||||
@@ -0,0 +1,298 @@
|
||||
// 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 Provides a utility for tracing and debugging WebChannel
|
||||
* requests.
|
||||
*
|
||||
* @visibility {//visibility:private}
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.labs.net.webChannel.WebChannelDebug');
|
||||
|
||||
goog.require('goog.json');
|
||||
goog.require('goog.log');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Logs and keeps a buffer of debugging info for the Channel.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
goog.labs.net.webChannel.WebChannelDebug = function() {
|
||||
/**
|
||||
* The logger instance.
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
this.logger_ = goog.log.getLogger('goog.labs.net.webChannel.WebChannelDebug');
|
||||
};
|
||||
|
||||
|
||||
goog.scope(function() {
|
||||
var WebChannelDebug = goog.labs.net.webChannel.WebChannelDebug;
|
||||
|
||||
|
||||
/**
|
||||
* Gets the logger used by this ChannelDebug.
|
||||
* @return {goog.debug.Logger} The logger used by this WebChannelDebug.
|
||||
*/
|
||||
WebChannelDebug.prototype.getLogger = function() {
|
||||
return this.logger_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs that the browser went offline during the lifetime of a request.
|
||||
* @param {goog.Uri} url The URL being requested.
|
||||
*/
|
||||
WebChannelDebug.prototype.browserOfflineResponse = function(url) {
|
||||
this.info('BROWSER_OFFLINE: ' + url);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs an XmlHttp request..
|
||||
* @param {string} verb The request type (GET/POST).
|
||||
* @param {goog.Uri} uri The request destination.
|
||||
* @param {string|number|undefined} id The request id.
|
||||
* @param {number} attempt Which attempt # the request was.
|
||||
* @param {?string} postData The data posted in the request.
|
||||
*/
|
||||
WebChannelDebug.prototype.xmlHttpChannelRequest =
|
||||
function(verb, uri, id, attempt, postData) {
|
||||
this.info(
|
||||
'XMLHTTP REQ (' + id + ') [attempt ' + attempt + ']: ' +
|
||||
verb + '\n' + uri + '\n' +
|
||||
this.maybeRedactPostData_(postData));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs the meta data received from an XmlHttp request.
|
||||
* @param {string} verb The request type (GET/POST).
|
||||
* @param {goog.Uri} uri The request destination.
|
||||
* @param {string|number|undefined} id The request id.
|
||||
* @param {number} attempt Which attempt # the request was.
|
||||
* @param {goog.net.XmlHttp.ReadyState} readyState The ready state.
|
||||
* @param {number} statusCode The HTTP status code.
|
||||
*/
|
||||
WebChannelDebug.prototype.xmlHttpChannelResponseMetaData =
|
||||
function(verb, uri, id, attempt, readyState, statusCode) {
|
||||
this.info(
|
||||
'XMLHTTP RESP (' + id + ') [ attempt ' + attempt + ']: ' +
|
||||
verb + '\n' + uri + '\n' + readyState + ' ' + statusCode);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs the response data received from an XmlHttp request.
|
||||
* @param {string|number|undefined} id The request id.
|
||||
* @param {?string} responseText The response text.
|
||||
* @param {?string=} opt_desc Optional request description.
|
||||
*/
|
||||
WebChannelDebug.prototype.xmlHttpChannelResponseText =
|
||||
function(id, responseText, opt_desc) {
|
||||
this.info(
|
||||
'XMLHTTP TEXT (' + id + '): ' +
|
||||
this.redactResponse_(responseText) +
|
||||
(opt_desc ? ' ' + opt_desc : ''));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs a Trident ActiveX request.
|
||||
* @param {string} verb The request type (GET/POST).
|
||||
* @param {goog.Uri} uri The request destination.
|
||||
* @param {string|number|undefined} id The request id.
|
||||
* @param {number} attempt Which attempt # the request was.
|
||||
*/
|
||||
WebChannelDebug.prototype.tridentChannelRequest =
|
||||
function(verb, uri, id, attempt) {
|
||||
this.info(
|
||||
'TRIDENT REQ (' + id + ') [ attempt ' + attempt + ']: ' +
|
||||
verb + '\n' + uri);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs the response text received from a Trident ActiveX request.
|
||||
* @param {string|number|undefined} id The request id.
|
||||
* @param {string} responseText The response text.
|
||||
*/
|
||||
WebChannelDebug.prototype.tridentChannelResponseText =
|
||||
function(id, responseText) {
|
||||
this.info(
|
||||
'TRIDENT TEXT (' + id + '): ' +
|
||||
this.redactResponse_(responseText));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs the done response received from a Trident ActiveX request.
|
||||
* @param {string|number|undefined} id The request id.
|
||||
* @param {boolean} successful Whether the request was successful.
|
||||
*/
|
||||
WebChannelDebug.prototype.tridentChannelResponseDone =
|
||||
function(id, successful) {
|
||||
this.info(
|
||||
'TRIDENT TEXT (' + id + '): ' + successful ? 'success' : 'failure');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs a request timeout.
|
||||
* @param {goog.Uri} uri The uri that timed out.
|
||||
*/
|
||||
WebChannelDebug.prototype.timeoutResponse = function(uri) {
|
||||
this.info('TIMEOUT: ' + uri);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs a debug message.
|
||||
* @param {string} text The message.
|
||||
*/
|
||||
WebChannelDebug.prototype.debug = function(text) {
|
||||
this.info(text);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs an exception
|
||||
* @param {Error} e The error or error event.
|
||||
* @param {string=} opt_msg The optional message, defaults to 'Exception'.
|
||||
*/
|
||||
WebChannelDebug.prototype.dumpException = function(e, opt_msg) {
|
||||
this.severe((opt_msg || 'Exception') + e);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs an info message.
|
||||
* @param {string} text The message.
|
||||
*/
|
||||
WebChannelDebug.prototype.info = function(text) {
|
||||
goog.log.info(this.logger_, text);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs a warning message.
|
||||
* @param {string} text The message.
|
||||
*/
|
||||
WebChannelDebug.prototype.warning = function(text) {
|
||||
goog.log.warning(this.logger_, text);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs a severe message.
|
||||
* @param {string} text The message.
|
||||
*/
|
||||
WebChannelDebug.prototype.severe = function(text) {
|
||||
goog.log.error(this.logger_, text);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes potentially private data from a response so that we don't
|
||||
* accidentally save private and personal data to the server logs.
|
||||
* @param {?string} responseText A JSON response to clean.
|
||||
* @return {?string} The cleaned response.
|
||||
* @private
|
||||
*/
|
||||
WebChannelDebug.prototype.redactResponse_ = function(responseText) {
|
||||
if (!responseText) {
|
||||
return null;
|
||||
}
|
||||
/** @preserveTry */
|
||||
try {
|
||||
var responseArray = goog.json.unsafeParse(responseText);
|
||||
if (responseArray) {
|
||||
for (var i = 0; i < responseArray.length; i++) {
|
||||
if (goog.isArray(responseArray[i])) {
|
||||
this.maybeRedactArray_(responseArray[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return goog.json.serialize(responseArray);
|
||||
} catch (e) {
|
||||
this.debug('Exception parsing expected JS array - probably was not JS');
|
||||
return responseText;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes data from a response array that may be sensitive.
|
||||
* @param {!Array} array The array to clean.
|
||||
* @private
|
||||
*/
|
||||
WebChannelDebug.prototype.maybeRedactArray_ = function(array) {
|
||||
if (array.length < 2) {
|
||||
return;
|
||||
}
|
||||
var dataPart = array[1];
|
||||
if (!goog.isArray(dataPart)) {
|
||||
return;
|
||||
}
|
||||
if (dataPart.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var type = dataPart[0];
|
||||
if (type != 'noop' && type != 'stop') {
|
||||
// redact all fields in the array
|
||||
for (var i = 1; i < dataPart.length; i++) {
|
||||
dataPart[i] = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes potentially private data from a request POST body so that we don't
|
||||
* accidentally save private and personal data to the server logs.
|
||||
* @param {?string} data The data string to clean.
|
||||
* @return {?string} The data string with sensitive data replaced by 'redacted'.
|
||||
* @private
|
||||
*/
|
||||
WebChannelDebug.prototype.maybeRedactPostData_ = function(data) {
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
var out = '';
|
||||
var params = data.split('&');
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
var param = params[i];
|
||||
var keyValue = param.split('=');
|
||||
if (keyValue.length > 1) {
|
||||
var key = keyValue[0];
|
||||
var value = keyValue[1];
|
||||
|
||||
var keyParts = key.split('_');
|
||||
if (keyParts.length >= 2 && keyParts[1] == 'type') {
|
||||
out += key + '=' + value + '&';
|
||||
} else {
|
||||
out += key + '=' + 'redacted' + '&';
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
};
|
||||
}); // goog.scope
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,74 @@
|
||||
// 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 Transport support for WebChannel.
|
||||
*
|
||||
* The <code>WebChannelTransport</code> implementation serves as the factory
|
||||
* for <code>WebChannel</code>, which offers an abstraction for
|
||||
* point-to-point socket-like communication similar to what BrowserChannel
|
||||
* or HTML5 WebSocket offers.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.WebChannelTransport');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A WebChannelTransport instance represents a shared context of logical
|
||||
* connectivity between a browser client and a remote origin.
|
||||
*
|
||||
* Over a single WebChannelTransport instance, multiple WebChannels may be
|
||||
* created against different URLs, which may all share the same
|
||||
* underlying connectivity (i.e. TCP connection) whenever possible.
|
||||
*
|
||||
* When multi-domains are supported, such as CORS, multiple origins may be
|
||||
* supported over a single WebChannelTransport instance at the same time.
|
||||
*
|
||||
* Sharing between different window contexts such as tabs is not addressed
|
||||
* by WebChannelTransport. Applications may choose HTML5 shared workers
|
||||
* or other techniques to access the same transport instance
|
||||
* across different window contexts.
|
||||
*
|
||||
* @interface
|
||||
*/
|
||||
goog.net.WebChannelTransport = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* The latest protocol version. The protocol version is requested
|
||||
* from the server which is responsible for terminating the underlying
|
||||
* wire protocols.
|
||||
*
|
||||
* @const
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.WebChannelTransport.LATEST_VERSION_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new WebChannel instance.
|
||||
*
|
||||
* The new WebChannel is to be opened against the server-side resource
|
||||
* as specified by the given URL. See {@link goog.net.WebChannel} for detailed
|
||||
* semantics.
|
||||
*
|
||||
* @param {string} url The URL path for the new WebChannel instance.
|
||||
* @param {!goog.net.WebChannel.Options=} opt_options Configuration for the
|
||||
* new WebChannel instance.
|
||||
* @return {!goog.net.WebChannel} the newly created WebChannel instance.
|
||||
*/
|
||||
goog.net.WebChannelTransport.prototype.createWebChannel = goog.abstractMethod;
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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 Default factory for <code>WebChannelTransport</code> to
|
||||
* avoid exposing concrete classes to clients.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.createWebChannelTransport');
|
||||
|
||||
goog.require('goog.functions');
|
||||
goog.require('goog.labs.net.webChannel.WebChannelBaseTransport');
|
||||
|
||||
|
||||
/**
|
||||
* Create a new WebChannelTransport instance using the default implementation.
|
||||
*
|
||||
* @return {!goog.net.WebChannelTransport} the newly created transport instance.
|
||||
*/
|
||||
goog.net.createWebChannelTransport =
|
||||
/** @type {function(): !goog.net.WebChannelTransport} */ (
|
||||
goog.partial(goog.functions.create,
|
||||
goog.labs.net.webChannel.WebChannelBaseTransport));
|
||||
449
nicer-api-docs/closure-library/closure/goog/labs/net/xhr.js
Normal file
449
nicer-api-docs/closure-library/closure/goog/labs/net/xhr.js
Normal file
@@ -0,0 +1,449 @@
|
||||
// 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 Offered as an alternative to XhrIo as a way for making requests
|
||||
* via XMLHttpRequest. Instead of mirroring the XHR interface and exposing
|
||||
* events, results are used as a way to pass a "promise" of the response to
|
||||
* interested parties.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.net.xhr');
|
||||
goog.provide('goog.labs.net.xhr.Error');
|
||||
goog.provide('goog.labs.net.xhr.HttpError');
|
||||
goog.provide('goog.labs.net.xhr.TimeoutError');
|
||||
|
||||
goog.require('goog.debug.Error');
|
||||
goog.require('goog.json');
|
||||
goog.require('goog.net.HttpStatus');
|
||||
goog.require('goog.net.XmlHttp');
|
||||
goog.require('goog.result');
|
||||
goog.require('goog.result.SimpleResult');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.uri.utils');
|
||||
|
||||
|
||||
|
||||
goog.scope(function() {
|
||||
var _ = goog.labs.net.xhr;
|
||||
var Result = goog.result.Result;
|
||||
var SimpleResult = goog.result.SimpleResult;
|
||||
var Wait = goog.result.wait;
|
||||
var HttpStatus = goog.net.HttpStatus;
|
||||
|
||||
|
||||
/**
|
||||
* Configuration options for an XMLHttpRequest.
|
||||
* - headers: map of header key/value pairs.
|
||||
* - timeoutMs: number of milliseconds after which the request will be timed
|
||||
* out by the client. Default is to allow the browser to handle timeouts.
|
||||
* - withCredentials: whether user credentials are to be included in a
|
||||
* cross-origin request. See:
|
||||
* http://dev.w3.org/2006/webapi/XMLHttpRequest-2/#the-withcredentials-attribute
|
||||
* - mimeType: allows the caller to override the content-type and charset for
|
||||
* the request, which is useful when requesting binary data. See:
|
||||
* http://dev.w3.org/2006/webapi/XMLHttpRequest-2/#dom-xmlhttprequest-overridemimetype
|
||||
* - xssiPrefix: Prefix used for protecting against XSSI attacks, which should
|
||||
* be removed before parsing the response as JSON.
|
||||
*
|
||||
* @typedef {{
|
||||
* headers: (Object.<string>|undefined),
|
||||
* timeoutMs: (number|undefined),
|
||||
* withCredentials: (boolean|undefined),
|
||||
* mimeType: (string|undefined),
|
||||
* xssiPrefix: (string|undefined)
|
||||
* }}
|
||||
*/
|
||||
_.Options;
|
||||
|
||||
|
||||
/**
|
||||
* Defines the types that are allowed as post data.
|
||||
* @typedef {(ArrayBuffer|Blob|Document|FormData|null|string|undefined)}
|
||||
*/
|
||||
_.PostData;
|
||||
|
||||
|
||||
/**
|
||||
* The Content-Type HTTP header name.
|
||||
* @type {string}
|
||||
*/
|
||||
_.CONTENT_TYPE_HEADER = 'Content-Type';
|
||||
|
||||
|
||||
/**
|
||||
* The Content-Type HTTP header value for a url-encoded form.
|
||||
* @type {string}
|
||||
*/
|
||||
_.FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded;charset=utf-8';
|
||||
|
||||
|
||||
/**
|
||||
* Sends a get request, returning a transformed result which will be resolved
|
||||
* with the response text once the request completes.
|
||||
*
|
||||
* @param {string} url The URL to request.
|
||||
* @param {_.Options=} opt_options Configuration options for the request.
|
||||
* @return {!Result} A result object that will be resolved
|
||||
* with the response text once the request finishes.
|
||||
*/
|
||||
_.get = function(url, opt_options) {
|
||||
var result = _.send('GET', url, null, opt_options);
|
||||
var transformedResult = goog.result.transform(result,
|
||||
_.getResponseText_);
|
||||
return transformedResult;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends a post request, returning a transformed result which will be resolved
|
||||
* with the response text once the request completes.
|
||||
*
|
||||
* @param {string} url The URL to request.
|
||||
* @param {_.PostData} data The body of the post request.
|
||||
* @param {_.Options=} opt_options Configuration options for the request.
|
||||
* @return {!Result} A result object that will be resolved
|
||||
* with the response text once the request finishes.
|
||||
*/
|
||||
_.post = function(url, data, opt_options) {
|
||||
var result = _.send('POST', url, data, opt_options);
|
||||
var transformedResult = goog.result.transform(result,
|
||||
_.getResponseText_);
|
||||
return transformedResult;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends a get request, returning a result which will be resolved with
|
||||
* the parsed response text once the request completes.
|
||||
*
|
||||
* @param {string} url The URL to request.
|
||||
* @param {_.Options=} opt_options Configuration options for the request.
|
||||
* @return {!Result} A result object that will be resolved
|
||||
* with the response JSON once the request finishes.
|
||||
*/
|
||||
_.getJson = function(url, opt_options) {
|
||||
var result = _.send('GET', url, null, opt_options);
|
||||
var transformedResult = _.addJsonParsingCallbacks_(result, opt_options);
|
||||
return transformedResult;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends a post request, returning a result which will be resolved with
|
||||
* the parsed response text once the request completes.
|
||||
*
|
||||
* @param {string} url The URL to request.
|
||||
* @param {_.PostData} data The body of the post request.
|
||||
* @param {_.Options=} opt_options Configuration options for the request.
|
||||
* @return {!Result} A result object that will be resolved
|
||||
* with the response JSON once the request finishes.
|
||||
*/
|
||||
_.postJson = function(url, data, opt_options) {
|
||||
var result = _.send('POST', url, data, opt_options);
|
||||
var transformedResult = _.addJsonParsingCallbacks_(result, opt_options);
|
||||
return transformedResult;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends a request using XMLHttpRequest and returns a result.
|
||||
*
|
||||
* @param {string} method The HTTP method for the request.
|
||||
* @param {string} url The URL to request.
|
||||
* @param {_.PostData} data The body of the post request.
|
||||
* @param {_.Options=} opt_options Configuration options for the request.
|
||||
* @return {!Result} A result object that will be resolved
|
||||
* with the XHR object as it's value when the request finishes.
|
||||
*/
|
||||
_.send = function(method, url, data, opt_options) {
|
||||
|
||||
var result = new SimpleResult();
|
||||
|
||||
// When the deferred is cancelled, we abort the XHR. We want to make sure
|
||||
// the readystatechange event still fires, so it can do the timeout
|
||||
// cleanup, however we don't want the callback or errback to be called
|
||||
// again. Thus the slight ugliness here. If results were pushed into
|
||||
// makeRequest, this could become a lot cleaner but we want an option for
|
||||
// people not to include goog.result.Result.
|
||||
goog.result.waitOnError(result, function(error, result) {
|
||||
if (result.isCanceled()) {
|
||||
xhr.abort();
|
||||
xhr.onreadystatechange = goog.nullFunction;
|
||||
}
|
||||
});
|
||||
|
||||
function callback(data) {
|
||||
result.setValue(data);
|
||||
}
|
||||
|
||||
function errback(err) {
|
||||
result.setError(err);
|
||||
}
|
||||
|
||||
var xhr = _.makeRequest(method, url, data, opt_options, callback, errback);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new XMLHttpRequest and initiates a request.
|
||||
*
|
||||
* @param {string} method The HTTP method for the request.
|
||||
* @param {string} url The URL to request.
|
||||
* @param {_.PostData} data The body of the post request, unless the content
|
||||
* type is explicitly set in the Options, then it will default to form
|
||||
* urlencoded.
|
||||
* @param {_.Options=} opt_options Configuration options for the request.
|
||||
* @param {function(XMLHttpRequest)=} opt_callback Optional callback to call
|
||||
* when the request completes.
|
||||
* @param {function(Error)=} opt_errback Optional callback to call
|
||||
* when there is an error.
|
||||
* @return {!XMLHttpRequest} The new XMLHttpRequest.
|
||||
*/
|
||||
_.makeRequest = function(
|
||||
method, url, data, opt_options, opt_callback, opt_errback) {
|
||||
var options = opt_options || {};
|
||||
var callback = opt_callback || goog.nullFunction;
|
||||
var errback = opt_errback || goog.nullFunction;
|
||||
var timer;
|
||||
|
||||
var xhr = /** @type {!XMLHttpRequest} */ (goog.net.XmlHttp());
|
||||
try {
|
||||
xhr.open(method, url, true);
|
||||
} catch (e) {
|
||||
// XMLHttpRequest.open may throw when 'open' is called, for example, IE7
|
||||
// throws "Access Denied" for cross-origin requests.
|
||||
errback(new _.Error('Error opening XHR: ' + e.message, url, xhr));
|
||||
return xhr;
|
||||
}
|
||||
|
||||
// So sad that IE doesn't support onload and onerror.
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == goog.net.XmlHttp.ReadyState.COMPLETE) {
|
||||
window.clearTimeout(timer);
|
||||
// Note: When developing locally, XHRs to file:// schemes return a status
|
||||
// code of 0. We mark that case as a success too.
|
||||
if (HttpStatus.isSuccess(xhr.status) ||
|
||||
xhr.status === 0 && !_.isEffectiveSchemeHttp_(url)) {
|
||||
callback(xhr);
|
||||
} else {
|
||||
errback(new _.HttpError(xhr.status, url, xhr));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Set the headers.
|
||||
var contentTypeIsSet = false;
|
||||
if (options.headers) {
|
||||
for (var key in options.headers) {
|
||||
xhr.setRequestHeader(key, options.headers[key]);
|
||||
}
|
||||
contentTypeIsSet = _.CONTENT_TYPE_HEADER in options.headers;
|
||||
}
|
||||
|
||||
// If a content type hasn't been set, default to form-urlencoded/UTF8 for
|
||||
// POSTs. This is because some proxies have been known to reject posts
|
||||
// without a content-type.
|
||||
if (method == 'POST' && !contentTypeIsSet) {
|
||||
xhr.setRequestHeader(_.CONTENT_TYPE_HEADER, _.FORM_CONTENT_TYPE);
|
||||
}
|
||||
|
||||
// Set whether to pass cookies on cross-domain requests (if applicable).
|
||||
// @see http://dev.w3.org/2006/webapi/XMLHttpRequest-2/#the-withcredentials-attribute
|
||||
if (options.withCredentials) {
|
||||
xhr.withCredentials = options.withCredentials;
|
||||
}
|
||||
|
||||
// Allow the request to override the mime type, useful for getting binary
|
||||
// data from the server. e.g. 'text/plain; charset=x-user-defined'.
|
||||
// @see http://dev.w3.org/2006/webapi/XMLHttpRequest-2/#dom-xmlhttprequest-overridemimetype
|
||||
if (options.mimeType) {
|
||||
xhr.overrideMimeType(options.mimeType);
|
||||
}
|
||||
|
||||
// Handle timeouts, if requested.
|
||||
if (options.timeoutMs > 0) {
|
||||
timer = window.setTimeout(function() {
|
||||
// Clear event listener before aborting so the errback will not be
|
||||
// called twice.
|
||||
xhr.onreadystatechange = goog.nullFunction;
|
||||
xhr.abort();
|
||||
errback(new _.TimeoutError(url, xhr));
|
||||
}, options.timeoutMs);
|
||||
}
|
||||
|
||||
// Trigger the send.
|
||||
try {
|
||||
xhr.send(data);
|
||||
} catch (e) {
|
||||
// XMLHttpRequest.send is known to throw on some versions of FF, for example
|
||||
// if a cross-origin request is disallowed.
|
||||
xhr.onreadystatechange = goog.nullFunction;
|
||||
window.clearTimeout(timer);
|
||||
errback(new _.Error('Error sending XHR: ' + e.message, url, xhr));
|
||||
}
|
||||
|
||||
return xhr;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} url The URL to test.
|
||||
* @return {boolean} Whether the effective scheme is HTTP or HTTPs.
|
||||
* @private
|
||||
*/
|
||||
_.isEffectiveSchemeHttp_ = function(url) {
|
||||
var scheme = goog.uri.utils.getEffectiveScheme(url);
|
||||
// NOTE(user): Empty-string is for the case under FF3.5 when the location
|
||||
// is not defined inside a web worker.
|
||||
return scheme == 'http' || scheme == 'https' || scheme == '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the response text of an XHR object. Intended to be called when
|
||||
* the result resolves.
|
||||
*
|
||||
* @param {!XMLHttpRequest} xhr The XHR object.
|
||||
* @return {string} The response text.
|
||||
* @private
|
||||
*/
|
||||
_.getResponseText_ = function(xhr) {
|
||||
return xhr.responseText;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Transforms a result, parsing the JSON in the original result value's
|
||||
* responseText. The transformed result's value is a javascript object.
|
||||
* Parse errors resolve the transformed result in an error.
|
||||
*
|
||||
* @param {!Result} result The result to wait on.
|
||||
* @param {_.Options|undefined} options The options object.
|
||||
*
|
||||
* @return {!Result} The transformed result.
|
||||
* @private
|
||||
*/
|
||||
_.addJsonParsingCallbacks_ = function(result, options) {
|
||||
var resultWithResponseText = goog.result.transform(result,
|
||||
_.getResponseText_);
|
||||
var prefixStrippedResult = resultWithResponseText;
|
||||
if (options && options.xssiPrefix) {
|
||||
prefixStrippedResult = goog.result.transform(resultWithResponseText,
|
||||
goog.partial(_.stripXssiPrefix_, options.xssiPrefix));
|
||||
}
|
||||
|
||||
var jsonParsedResult = goog.result.transform(prefixStrippedResult,
|
||||
goog.json.parse);
|
||||
return jsonParsedResult;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Strips the XSSI prefix from the input string.
|
||||
*
|
||||
* @param {string} prefix The XSSI prefix.
|
||||
* @param {string} string The string to strip the prefix from.
|
||||
* @return {string} The input string without the prefix.
|
||||
* @private
|
||||
*/
|
||||
_.stripXssiPrefix_ = function(prefix, string) {
|
||||
if (goog.string.startsWith(string, prefix)) {
|
||||
string = string.substring(prefix.length);
|
||||
}
|
||||
return string;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Generic error that may occur during a request.
|
||||
*
|
||||
* @param {string} message The error message.
|
||||
* @param {string} url The URL that was being requested.
|
||||
* @param {!XMLHttpRequest} xhr The XMLHttpRequest that failed.
|
||||
* @extends {goog.debug.Error}
|
||||
* @constructor
|
||||
*/
|
||||
_.Error = function(message, url, xhr) {
|
||||
goog.base(this, message + ', url=' + url);
|
||||
|
||||
/**
|
||||
* The URL that was requested.
|
||||
* @type {string}
|
||||
*/
|
||||
this.url = url;
|
||||
|
||||
/**
|
||||
* The XMLHttpRequest corresponding with the failed request.
|
||||
* @type {!XMLHttpRequest}
|
||||
*/
|
||||
this.xhr = xhr;
|
||||
};
|
||||
goog.inherits(_.Error, goog.debug.Error);
|
||||
|
||||
|
||||
/** @override */
|
||||
_.Error.prototype.name = 'XhrError';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class for HTTP errors.
|
||||
*
|
||||
* @param {number} status The HTTP status code of the response.
|
||||
* @param {string} url The URL that was being requested.
|
||||
* @param {!XMLHttpRequest} xhr The XMLHttpRequest that failed.
|
||||
* @extends {_.Error}
|
||||
* @constructor
|
||||
*/
|
||||
_.HttpError = function(status, url, xhr) {
|
||||
goog.base(this, 'Request Failed, status=' + status, url, xhr);
|
||||
|
||||
/**
|
||||
* The HTTP status code for the error.
|
||||
* @type {number}
|
||||
*/
|
||||
this.status = status;
|
||||
};
|
||||
goog.inherits(_.HttpError, _.Error);
|
||||
|
||||
|
||||
/** @override */
|
||||
_.HttpError.prototype.name = 'XhrHttpError';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class for Timeout errors.
|
||||
*
|
||||
* @param {string} url The URL that timed out.
|
||||
* @param {!XMLHttpRequest} xhr The XMLHttpRequest that failed.
|
||||
* @extends {_.Error}
|
||||
* @constructor
|
||||
*/
|
||||
_.TimeoutError = function(url, xhr) {
|
||||
goog.base(this, 'Request timed out', url, xhr);
|
||||
};
|
||||
goog.inherits(_.TimeoutError, _.Error);
|
||||
|
||||
|
||||
/** @override */
|
||||
_.TimeoutError.prototype.name = 'XhrTimeoutError';
|
||||
|
||||
}); // goog.scope
|
||||
@@ -0,0 +1,46 @@
|
||||
// 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 labs location for functions destined for Closure's
|
||||
* {@code goog.object} namespace.
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.object');
|
||||
|
||||
|
||||
/**
|
||||
* Whether two values are not observably distinguishable. This
|
||||
* correctly detects that 0 is not the same as -0 and two NaNs are
|
||||
* practically equivalent.
|
||||
*
|
||||
* The implementation is as suggested by harmony:egal proposal.
|
||||
*
|
||||
* @param {*} v The first value to compare.
|
||||
* @param {*} v2 The second value to compare.
|
||||
* @return {boolean} Whether two values are not observably distinguishable.
|
||||
* @see http://wiki.ecmascript.org/doku.php?id=harmony:egal
|
||||
*/
|
||||
goog.labs.object.is = function(v, v2) {
|
||||
if (v === v2) {
|
||||
// 0 === -0, but they are not identical.
|
||||
// We need the cast because the compiler requires that v2 is a
|
||||
// number (although 1/v2 works with non-number). We cast to ? to
|
||||
// stop the compiler from type-checking this statement.
|
||||
return v !== 0 || 1 / v === 1 / /** @type {?} */ (v2);
|
||||
}
|
||||
|
||||
// NaN is non-reflexive: NaN !== NaN, although they are identical.
|
||||
return v !== v && v2 !== v2;
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
// 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 Provides a notice object that is used to encapsulates
|
||||
* information about a particular change/notification on an observable
|
||||
* object.
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.observe.Notice');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A notice object encapsulates information about a notification fired
|
||||
* by an observable.
|
||||
* @param {!goog.labs.observe.Observable} observable The observable
|
||||
* object that fires this notice.
|
||||
* @param {*=} opt_data The optional data associated with this notice.
|
||||
* @constructor
|
||||
*/
|
||||
goog.labs.observe.Notice = function(observable, opt_data) {
|
||||
/**
|
||||
* @type {!goog.labs.observe.Observable}
|
||||
* @private
|
||||
*/
|
||||
this.observable_ = observable;
|
||||
|
||||
/**
|
||||
* @type {*}
|
||||
* @private
|
||||
*/
|
||||
this.data_ = opt_data;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!goog.labs.observe.Observable} The observable object that
|
||||
* fires this notice.
|
||||
*/
|
||||
goog.labs.observe.Notice.prototype.getObservable = function() {
|
||||
return this.observable_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {*} The optional data associated with this notice. May be
|
||||
* null/undefined.
|
||||
*/
|
||||
goog.labs.observe.Notice.prototype.getData = function() {
|
||||
return this.data_;
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
// 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 Experimental observer-observable API. This is
|
||||
* intended as super lightweight replacement of
|
||||
* goog.events.EventTarget when w3c event model bubble/capture
|
||||
* behavior is not required.
|
||||
*
|
||||
* This is similar to {@code goog.pubsub.PubSub} but with different
|
||||
* intent and naming so that it is more discoverable. The API is
|
||||
* tighter while allowing for more flexibility offered by the
|
||||
* interface {@code Observable}.
|
||||
*
|
||||
* WARNING: This is still highly experimental. Please contact author
|
||||
* before using this.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.observe.Observable');
|
||||
|
||||
goog.require('goog.disposable.IDisposable');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Interface for an observable object.
|
||||
* @interface
|
||||
* @extends {goog.disposable.IDisposable}
|
||||
*/
|
||||
goog.labs.observe.Observable = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Registers an observer on the observable.
|
||||
*
|
||||
* Note that no guarantee is provided on order of execution of the
|
||||
* observers. For a single notification, one Notice object is reused
|
||||
* across all invoked observers.
|
||||
*
|
||||
* Note that if an observation with the same observer is already
|
||||
* registered, it will not be registered again. Comparison is done via
|
||||
* observer's {@code equals} method.
|
||||
*
|
||||
* @param {!goog.labs.observe.Observer} observer The observer to add.
|
||||
* @return {boolean} Whether the observer was successfully added.
|
||||
*/
|
||||
goog.labs.observe.Observable.prototype.observe = function(observer) {};
|
||||
|
||||
|
||||
/**
|
||||
* Unregisters an observer from the observable. The parameter must be
|
||||
* the same as those passed to {@code observe} method. Comparison is
|
||||
* done via observer's {@code equals} method.
|
||||
* @param {!goog.labs.observe.Observer} observer The observer to remove.
|
||||
* @return {boolean} Whether the observer is removed.
|
||||
*/
|
||||
goog.labs.observe.Observable.prototype.unobserve = function(observer) {};
|
||||
|
||||
|
||||
/**
|
||||
* Notifies observers by invoking them. Optionally, a data object may be
|
||||
* given to be passed to each observer.
|
||||
* @param {*=} opt_data An optional data object.
|
||||
*/
|
||||
goog.labs.observe.Observable.prototype.notify = function(opt_data) {};
|
||||
@@ -0,0 +1,180 @@
|
||||
// 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 set of {@code goog.labs.observe.Observable}s that
|
||||
* allow registering and removing observers to all of the observables
|
||||
* in the set.
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.observe.ObservableSet');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.labs.observe.Observer');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a set of observables.
|
||||
*
|
||||
* An ObservableSet is a collection of observables. Observers may be
|
||||
* reigstered and will receive notifications when any of the
|
||||
* observables notify. This class is meant to simplify management of
|
||||
* observations on multiple observables of the same nature.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
goog.labs.observe.ObservableSet = function() {
|
||||
/**
|
||||
* The observers registered with this set.
|
||||
* @type {!Array.<!goog.labs.observe.Observer>}
|
||||
* @private
|
||||
*/
|
||||
this.observers_ = [];
|
||||
|
||||
/**
|
||||
* The observables in this set.
|
||||
* @type {!Array.<!goog.labs.observe.Observable>}
|
||||
* @private
|
||||
*/
|
||||
this.observables_ = [];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an observer that observes all observables in the set. If new
|
||||
* observables are added to or removed from the set, the observer will
|
||||
* be registered or unregistered accordingly.
|
||||
*
|
||||
* The observer will not be added if there is already an equivalent
|
||||
* observer.
|
||||
*
|
||||
* @param {!goog.labs.observe.Observer} observer The observer to invoke.
|
||||
* @return {boolean} Whether the observer is actually added.
|
||||
*/
|
||||
goog.labs.observe.ObservableSet.prototype.addObserver = function(observer) {
|
||||
// Check whether the observer already exists.
|
||||
if (goog.array.find(this.observers_, goog.partial(
|
||||
goog.labs.observe.Observer.equals, observer))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.observers_.push(observer);
|
||||
goog.array.forEach(this.observables_, function(o) {
|
||||
o.observe(observer);
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes an observer from the set. The observer will be removed from
|
||||
* all observables in the set. Does nothing if the observer is not in
|
||||
* the set.
|
||||
* @param {!goog.labs.observe.Observer} observer The observer to remove.
|
||||
* @return {boolean} Whether the observer is actually removed.
|
||||
*/
|
||||
goog.labs.observe.ObservableSet.prototype.removeObserver = function(observer) {
|
||||
// Check that the observer exists before removing.
|
||||
var removed = goog.array.removeIf(this.observers_, goog.partial(
|
||||
goog.labs.observe.Observer.equals, observer));
|
||||
|
||||
if (removed) {
|
||||
goog.array.forEach(this.observables_, function(o) {
|
||||
o.unobserve(observer);
|
||||
});
|
||||
}
|
||||
return removed;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all registered observers.
|
||||
*/
|
||||
goog.labs.observe.ObservableSet.prototype.removeAllObservers = function() {
|
||||
this.unregisterAll_();
|
||||
this.observers_.length = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an observable to the set. All previously added and future
|
||||
* observers will be added to the new observable as well.
|
||||
*
|
||||
* The observable will not be added if it is already registered in the
|
||||
* set.
|
||||
*
|
||||
* @param {!goog.labs.observe.Observable} observable The observable to add.
|
||||
* @return {boolean} Whether the observable is actually added.
|
||||
*/
|
||||
goog.labs.observe.ObservableSet.prototype.addObservable = function(observable) {
|
||||
if (goog.array.contains(this.observables_, observable)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.observables_.push(observable);
|
||||
goog.array.forEach(this.observers_, function(observer) {
|
||||
observable.observe(observer);
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes an observable from the set. All observers registered on the
|
||||
* set will be removed from the observable as well.
|
||||
* @param {!goog.labs.observe.Observable} observable The observable to remove.
|
||||
* @return {boolean} Whether the observable is actually removed.
|
||||
*/
|
||||
goog.labs.observe.ObservableSet.prototype.removeObservable = function(
|
||||
observable) {
|
||||
var removed = goog.array.remove(this.observables_, observable);
|
||||
if (removed) {
|
||||
goog.array.forEach(this.observers_, function(observer) {
|
||||
observable.unobserve(observer);
|
||||
});
|
||||
}
|
||||
return removed;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all registered observables.
|
||||
*/
|
||||
goog.labs.observe.ObservableSet.prototype.removeAllObservables = function() {
|
||||
this.unregisterAll_();
|
||||
this.observables_.length = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all registered observations and observables.
|
||||
*/
|
||||
goog.labs.observe.ObservableSet.prototype.removeAll = function() {
|
||||
this.removeAllObservers();
|
||||
this.observables_.length = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Unregisters all registered observers from all registered observables.
|
||||
* @private
|
||||
*/
|
||||
goog.labs.observe.ObservableSet.prototype.unregisterAll_ = function() {
|
||||
goog.array.forEach(this.observers_, function(observer) {
|
||||
goog.array.forEach(this.observables_, function(o) {
|
||||
o.unobserve(observer);
|
||||
});
|
||||
}, this);
|
||||
};
|
||||
@@ -0,0 +1,156 @@
|
||||
// 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 set of observations. This set provides a convenient
|
||||
* means of observing many observables at once.
|
||||
*
|
||||
* This is similar in purpose to {@code goog.events.EventHandler}.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.observe.ObservationSet');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.labs.observe.Observer');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A set of observations. An observation is defined by an observable
|
||||
* and an observer. The set keeps track of observations and
|
||||
* allows their removal.
|
||||
* @param {!Object=} opt_defaultScope Optional function scope to use
|
||||
* when using {@code observeWithFunction} and
|
||||
* {@code unobserveWithFunction}.
|
||||
* @constructor
|
||||
*/
|
||||
goog.labs.observe.ObservationSet = function(opt_defaultScope) {
|
||||
/**
|
||||
* @type {!Array.<!goog.labs.observe.ObservationSet.Observation_>}
|
||||
* @private
|
||||
*/
|
||||
this.storedObservations_ = [];
|
||||
|
||||
/**
|
||||
* @type {!Object|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.defaultScope_ = opt_defaultScope;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Observes the given observer on the observable.
|
||||
* @param {!goog.labs.observe.Observable} observable The observable to
|
||||
* observe on.
|
||||
* @param {!goog.labs.observe.Observer} observer The observer.
|
||||
* @return {boolean} True if the observer is successfully registered.
|
||||
*/
|
||||
goog.labs.observe.ObservationSet.prototype.observe = function(
|
||||
observable, observer) {
|
||||
var success = observable.observe(observer);
|
||||
if (success) {
|
||||
this.storedObservations_.push(
|
||||
new goog.labs.observe.ObservationSet.Observation_(
|
||||
observable, observer));
|
||||
}
|
||||
return success;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Observes the given function on the observable.
|
||||
* @param {!goog.labs.observe.Observable} observable The observable to
|
||||
* observe on.
|
||||
* @param {function(!goog.labs.observe.Notice)} fn The handler function.
|
||||
* @param {!Object=} opt_scope Optional scope.
|
||||
* @return {goog.labs.observe.Observer} The registered observer object.
|
||||
* If the observer is not successfully registered, this will be null.
|
||||
*/
|
||||
goog.labs.observe.ObservationSet.prototype.observeWithFunction = function(
|
||||
observable, fn, opt_scope) {
|
||||
var observer = goog.labs.observe.Observer.fromFunction(
|
||||
fn, opt_scope || this.defaultScope_);
|
||||
if (this.observe(observable, observer)) {
|
||||
return observer;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Unobserves the given observer from the observable.
|
||||
* @param {!goog.labs.observe.Observable} observable The observable to
|
||||
* unobserve from.
|
||||
* @param {!goog.labs.observe.Observer} observer The observer.
|
||||
* @return {boolean} True if the observer is successfully removed.
|
||||
*/
|
||||
goog.labs.observe.ObservationSet.prototype.unobserve = function(
|
||||
observable, observer) {
|
||||
var removed = goog.array.removeIf(
|
||||
this.storedObservations_, function(o) {
|
||||
return o.observable == observable &&
|
||||
goog.labs.observe.Observer.equals(o.observer, observer);
|
||||
});
|
||||
|
||||
if (removed) {
|
||||
observable.unobserve(observer);
|
||||
}
|
||||
return removed;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Unobserves the given function from the observable.
|
||||
* @param {!goog.labs.observe.Observable} observable The observable to
|
||||
* unobserve from.
|
||||
* @param {function(!goog.labs.observe.Notice)} fn The handler function.
|
||||
* @param {!Object=} opt_scope Optional scope.
|
||||
* @return {boolean} True if the observer is successfully removed.
|
||||
*/
|
||||
goog.labs.observe.ObservationSet.prototype.unobserveWithFunction = function(
|
||||
observable, fn, opt_scope) {
|
||||
var observer = goog.labs.observe.Observer.fromFunction(
|
||||
fn, opt_scope || this.defaultScope_);
|
||||
return this.unobserve(observable, observer);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all observations registered through this set.
|
||||
*/
|
||||
goog.labs.observe.ObservationSet.prototype.removeAll = function() {
|
||||
goog.array.forEach(this.storedObservations_, function(observation) {
|
||||
var observable = observation.observable;
|
||||
var observer = observation.observer;
|
||||
observable.unobserve(observer);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A representation of an observation, which is defined uniquely by
|
||||
* the observable and observer.
|
||||
* @param {!goog.labs.observe.Observable} observable The observable.
|
||||
* @param {!goog.labs.observe.Observer} observer The observer.
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
goog.labs.observe.ObservationSet.Observation_ = function(
|
||||
observable, observer) {
|
||||
this.observable = observable;
|
||||
this.observer = observer;
|
||||
};
|
||||
@@ -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 Provide definition of an observer. This is meant to
|
||||
* be used with {@code goog.labs.observe.Observable}.
|
||||
*
|
||||
* This file also provides convenient functions to compare and create
|
||||
* Observer objects.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.observe.Observer');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A class implementing {@code Observer} may be informed of changes in
|
||||
* observable object.
|
||||
* @see {goog.labs.observe.Observable}
|
||||
* @interface
|
||||
*/
|
||||
goog.labs.observe.Observer = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Notifies the observer of changes to the observable object.
|
||||
* @param {!goog.labs.observe.Notice} notice The notice object.
|
||||
*/
|
||||
goog.labs.observe.Observer.prototype.notify;
|
||||
|
||||
|
||||
/**
|
||||
* Whether this observer is equal to the given observer.
|
||||
* @param {!goog.labs.observe.Observer} observer The observer to compare with.
|
||||
* @return {boolean} Whether the two observers are equal.
|
||||
*/
|
||||
goog.labs.observe.Observer.prototype.equals;
|
||||
|
||||
|
||||
/**
|
||||
* @param {!goog.labs.observe.Observer} observer1 Observer to compare.
|
||||
* @param {!goog.labs.observe.Observer} observer2 Observer to compare.
|
||||
* @return {boolean} Whether observer1 and observer2 are equal, as
|
||||
* determined by the first observer1's {@code equals} method.
|
||||
*/
|
||||
goog.labs.observe.Observer.equals = function(observer1, observer2) {
|
||||
return observer1 == observer2 || observer1.equals(observer2);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates an observer that calls the given function.
|
||||
* @param {function(!goog.labs.observe.Notice)} fn Function to be converted.
|
||||
* @param {!Object=} opt_scope Optional scope to execute the function.
|
||||
* @return {!goog.labs.observe.Observer} An observer object.
|
||||
*/
|
||||
goog.labs.observe.Observer.fromFunction = function(fn, opt_scope) {
|
||||
return new goog.labs.observe.Observer.FunctionObserver_(fn, opt_scope);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An observer that calls the given function on {@code notify}.
|
||||
* @param {function(!goog.labs.observe.Notice)} fn Function to delegate to.
|
||||
* @param {!Object=} opt_scope Optional scope to execute the function.
|
||||
* @constructor
|
||||
* @implements {goog.labs.observe.Observer}
|
||||
* @private
|
||||
*/
|
||||
goog.labs.observe.Observer.FunctionObserver_ = function(fn, opt_scope) {
|
||||
this.fn_ = fn;
|
||||
this.scope_ = opt_scope;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.labs.observe.Observer.FunctionObserver_.prototype.notify = function(
|
||||
notice) {
|
||||
this.fn_.call(this.scope_, notice);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.labs.observe.Observer.FunctionObserver_.prototype.equals = function(
|
||||
observer) {
|
||||
return this.fn_ === observer.fn_ && this.scope_ === observer.scope_;
|
||||
};
|
||||
@@ -0,0 +1,129 @@
|
||||
// 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 An implementation of {@code Observable} that can be
|
||||
* used as base class or composed into another class that wants to
|
||||
* implement {@code Observable}.
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.observe.SimpleObservable');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.labs.observe.Notice');
|
||||
goog.require('goog.labs.observe.Observable');
|
||||
goog.require('goog.labs.observe.Observer');
|
||||
goog.require('goog.object');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A simple implementation of {@code goog.labs.observe.Observable} that can
|
||||
* be used as a standalone observable or as a base class for other
|
||||
* observable object.
|
||||
*
|
||||
* When another class wants to implement observable without extending
|
||||
* {@code SimpleObservable}, they can create an instance of
|
||||
* {@code SimpleObservable}, specifying {@code opt_actualObservable},
|
||||
* and delegate to the instance. Here is a trivial example:
|
||||
*
|
||||
* <pre>
|
||||
* ClassA = function() {
|
||||
* goog.base(this);
|
||||
* this.observable_ = new SimpleObservable(this);
|
||||
* this.registerDisposable(this.observable_);
|
||||
* };
|
||||
* goog.inherits(ClassA, goog.Disposable);
|
||||
*
|
||||
* ClassA.prototype.observe = function(observer) {
|
||||
* this.observable_.observe(observer);
|
||||
* };
|
||||
*
|
||||
* ClassA.prototype.unobserve = function(observer) {
|
||||
* this.observable_.unobserve(observer);
|
||||
* };
|
||||
*
|
||||
* ClassA.prototype.notify = function(opt_data) {
|
||||
* this.observable_.notify(opt_data);
|
||||
* };
|
||||
* </pre>
|
||||
*
|
||||
* @param {!goog.labs.observe.Observable=} opt_actualObservable
|
||||
* Optional observable object. Defaults to 'this'. When used as
|
||||
* base class, the parameter need not be given. It is only useful
|
||||
* when using this class to implement implement {@code Observable}
|
||||
* interface on another object, see example above.
|
||||
* @constructor
|
||||
* @implements {goog.labs.observe.Observable}
|
||||
* @extends {goog.Disposable}
|
||||
*/
|
||||
goog.labs.observe.SimpleObservable = function(opt_actualObservable) {
|
||||
goog.base(this);
|
||||
|
||||
/**
|
||||
* @type {!goog.labs.observe.Observable}
|
||||
* @private
|
||||
*/
|
||||
this.actualObservable_ = opt_actualObservable || this;
|
||||
|
||||
/**
|
||||
* Observers registered on this object.
|
||||
* @type {!Array.<!goog.labs.observe.Observer>}
|
||||
* @private
|
||||
*/
|
||||
this.observers_ = [];
|
||||
};
|
||||
goog.inherits(goog.labs.observe.SimpleObservable, goog.Disposable);
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.labs.observe.SimpleObservable.prototype.observe = function(observer) {
|
||||
goog.asserts.assert(!this.isDisposed());
|
||||
|
||||
// Registers the (type, observer) only if it has not been previously
|
||||
// registered.
|
||||
var shouldRegisterObserver = !goog.array.some(this.observers_, goog.partial(
|
||||
goog.labs.observe.Observer.equals, observer));
|
||||
if (shouldRegisterObserver) {
|
||||
this.observers_.push(observer);
|
||||
}
|
||||
return shouldRegisterObserver;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.labs.observe.SimpleObservable.prototype.unobserve = function(observer) {
|
||||
goog.asserts.assert(!this.isDisposed());
|
||||
return goog.array.removeIf(this.observers_, goog.partial(
|
||||
goog.labs.observe.Observer.equals, observer));
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.labs.observe.SimpleObservable.prototype.notify = function(opt_data) {
|
||||
goog.asserts.assert(!this.isDisposed());
|
||||
var notice = new goog.labs.observe.Notice(this.actualObservable_, opt_data);
|
||||
goog.array.forEach(
|
||||
goog.array.clone(this.observers_), function(observer) {
|
||||
observer.notify(notice);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.labs.observe.SimpleObservable.prototype.disposeInternal = function() {
|
||||
this.observers_.length = 0;
|
||||
};
|
||||
339
nicer-api-docs/closure-library/closure/goog/labs/structs/map.js
Normal file
339
nicer-api-docs/closure-library/closure/goog/labs/structs/map.js
Normal file
@@ -0,0 +1,339 @@
|
||||
// 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 map data structure that offers a convenient API to
|
||||
* manipulate a key, value map. The key must be a string.
|
||||
*
|
||||
* This implementation also ensure that you can use keys that would
|
||||
* not be usable using a normal object literal {}. Some examples
|
||||
* include __proto__ (all newer browsers), toString/hasOwnProperty (IE
|
||||
* <= 8).
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.structs.Map');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.labs.object');
|
||||
goog.require('goog.object');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new map.
|
||||
* @constructor
|
||||
*/
|
||||
goog.labs.structs.Map = function() {
|
||||
// clear() initializes the map to the empty state.
|
||||
this.clear();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @type {function(this: Object, string): boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.labs.structs.Map.objectPropertyIsEnumerable_ =
|
||||
Object.prototype.propertyIsEnumerable;
|
||||
|
||||
|
||||
/**
|
||||
* @type {function(this: Object, string): boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.labs.structs.Map.objectHasOwnProperty_ =
|
||||
Object.prototype.hasOwnProperty;
|
||||
|
||||
|
||||
/**
|
||||
* Primary backing store of this map.
|
||||
* @type {!Object}
|
||||
* @private
|
||||
*/
|
||||
goog.labs.structs.Map.prototype.map_;
|
||||
|
||||
|
||||
/**
|
||||
* Secondary backing store for keys. The index corresponds to the
|
||||
* index for secondaryStoreValues_.
|
||||
* @type {!Array.<string>}
|
||||
* @private
|
||||
*/
|
||||
goog.labs.structs.Map.prototype.secondaryStoreKeys_;
|
||||
|
||||
|
||||
/**
|
||||
* Secondary backing store for keys. The index corresponds to the
|
||||
* index for secondaryStoreValues_.
|
||||
* @type {!Array.<*>}
|
||||
* @private
|
||||
*/
|
||||
goog.labs.structs.Map.prototype.secondaryStoreValues_;
|
||||
|
||||
|
||||
/**
|
||||
* Adds the (key, value) pair, overriding previous entry with the same
|
||||
* key, if any.
|
||||
* @param {string} key The key.
|
||||
* @param {*} value The value.
|
||||
*/
|
||||
goog.labs.structs.Map.prototype.set = function(key, value) {
|
||||
this.assertKeyIsString_(key);
|
||||
|
||||
var newKey = !this.hasKeyInPrimaryStore_(key);
|
||||
this.map_[key] = value;
|
||||
|
||||
// __proto__ is not settable on object.
|
||||
if (key == '__proto__' ||
|
||||
// Shadows for built-in properties are not enumerable in IE <= 8 .
|
||||
(!goog.labs.structs.Map.BrowserFeature.OBJECT_CREATE_SUPPORTED &&
|
||||
!goog.labs.structs.Map.objectPropertyIsEnumerable_.call(
|
||||
this.map_, key))) {
|
||||
delete this.map_[key];
|
||||
var index = goog.array.indexOf(this.secondaryStoreKeys_, key);
|
||||
if ((newKey = index < 0)) {
|
||||
index = this.secondaryStoreKeys_.length;
|
||||
}
|
||||
|
||||
this.secondaryStoreKeys_[index] = key;
|
||||
this.secondaryStoreValues_[index] = value;
|
||||
}
|
||||
|
||||
if (newKey) this.count_++;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the value for the given key.
|
||||
* @param {string} key The key whose value we want to retrieve.
|
||||
* @param {*=} opt_default The default value to return if the key does
|
||||
* not exist in the map, default to undefined.
|
||||
* @return {*} The value corresponding to the given key, or opt_default
|
||||
* if the key does not exist in this map.
|
||||
*/
|
||||
goog.labs.structs.Map.prototype.get = function(key, opt_default) {
|
||||
this.assertKeyIsString_(key);
|
||||
|
||||
if (this.hasKeyInPrimaryStore_(key)) {
|
||||
return this.map_[key];
|
||||
}
|
||||
|
||||
var index = goog.array.indexOf(this.secondaryStoreKeys_, key);
|
||||
return index >= 0 ? this.secondaryStoreValues_[index] : opt_default;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes the map entry with the given key.
|
||||
* @param {string} key The key to remove.
|
||||
* @return {boolean} True if the entry is removed.
|
||||
*/
|
||||
goog.labs.structs.Map.prototype.remove = function(key) {
|
||||
this.assertKeyIsString_(key);
|
||||
|
||||
if (this.hasKeyInPrimaryStore_(key)) {
|
||||
this.count_--;
|
||||
delete this.map_[key];
|
||||
return true;
|
||||
} else {
|
||||
var index = goog.array.indexOf(this.secondaryStoreKeys_, key);
|
||||
if (index >= 0) {
|
||||
this.count_--;
|
||||
goog.array.removeAt(this.secondaryStoreKeys_, index);
|
||||
goog.array.removeAt(this.secondaryStoreValues_, index);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds the content of the map to this map. If a new entry uses a key
|
||||
* that already exists in this map, the existing key is replaced.
|
||||
* @param {!goog.labs.structs.Map} map The map to add.
|
||||
*/
|
||||
goog.labs.structs.Map.prototype.addAll = function(map) {
|
||||
goog.array.forEach(map.getKeys(), function(key) {
|
||||
this.set(key, map.get(key));
|
||||
}, this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} True if the map is empty.
|
||||
*/
|
||||
goog.labs.structs.Map.prototype.isEmpty = function() {
|
||||
return !this.count_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The number of the entries in this map.
|
||||
*/
|
||||
goog.labs.structs.Map.prototype.getCount = function() {
|
||||
return this.count_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} key The key to check.
|
||||
* @return {boolean} True if the map contains the given key.
|
||||
*/
|
||||
goog.labs.structs.Map.prototype.containsKey = function(key) {
|
||||
this.assertKeyIsString_(key);
|
||||
return this.hasKeyInPrimaryStore_(key) ||
|
||||
goog.array.contains(this.secondaryStoreKeys_, key);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the map contains the given value. The comparison is done
|
||||
* using !== comparator. Also returns true if the passed value is NaN
|
||||
* and a NaN value exists in the map.
|
||||
* @param {*} value Value to check.
|
||||
* @return {boolean} True if the map contains the given value.
|
||||
*/
|
||||
goog.labs.structs.Map.prototype.containsValue = function(value) {
|
||||
var found = goog.object.some(this.map_, function(v, k) {
|
||||
return this.hasKeyInPrimaryStore_(k) &&
|
||||
goog.labs.object.is(v, value);
|
||||
}, this);
|
||||
return found || goog.array.contains(this.secondaryStoreValues_, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!Array.<string>} An array of all the keys contained in this map.
|
||||
*/
|
||||
goog.labs.structs.Map.prototype.getKeys = function() {
|
||||
var keys;
|
||||
if (goog.labs.structs.Map.BrowserFeature.OBJECT_KEYS_SUPPORTED) {
|
||||
keys = goog.array.clone(Object.keys(this.map_));
|
||||
} else {
|
||||
keys = [];
|
||||
for (var key in this.map_) {
|
||||
if (goog.labs.structs.Map.objectHasOwnProperty_.call(this.map_, key)) {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
goog.array.extend(keys, this.secondaryStoreKeys_);
|
||||
return keys;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!Array.<*>} An array of all the values contained in this map.
|
||||
* There may be duplicates.
|
||||
*/
|
||||
goog.labs.structs.Map.prototype.getValues = function() {
|
||||
var values = [];
|
||||
var keys = this.getKeys();
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
values.push(this.get(keys[i]));
|
||||
}
|
||||
return values;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!Array.<Array>} An array of entries. Each entry is of the
|
||||
* form [key, value]. Do not rely on consistent ordering of entries.
|
||||
*/
|
||||
goog.labs.structs.Map.prototype.getEntries = function() {
|
||||
var entries = [];
|
||||
var keys = this.getKeys();
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i];
|
||||
entries.push([key, this.get(key)]);
|
||||
}
|
||||
return entries;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the map to the initial state.
|
||||
*/
|
||||
goog.labs.structs.Map.prototype.clear = function() {
|
||||
this.map_ = goog.labs.structs.Map.BrowserFeature.OBJECT_CREATE_SUPPORTED ?
|
||||
Object.create(null) : {};
|
||||
this.secondaryStoreKeys_ = [];
|
||||
this.secondaryStoreValues_ = [];
|
||||
this.count_ = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clones this map.
|
||||
* @return {!goog.labs.structs.Map} The clone of this map.
|
||||
*/
|
||||
goog.labs.structs.Map.prototype.clone = function() {
|
||||
var map = new goog.labs.structs.Map();
|
||||
map.addAll(this);
|
||||
return map;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} key The key to check.
|
||||
* @return {boolean} True if the given key has been added successfully
|
||||
* to the primary store.
|
||||
* @private
|
||||
*/
|
||||
goog.labs.structs.Map.prototype.hasKeyInPrimaryStore_ = function(key) {
|
||||
// New browsers that support Object.create do not allow setting of
|
||||
// __proto__. In other browsers, hasOwnProperty will return true for
|
||||
// __proto__ for object created with literal {}, so we need to
|
||||
// special case it.
|
||||
if (key == '__proto__') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (goog.labs.structs.Map.BrowserFeature.OBJECT_CREATE_SUPPORTED) {
|
||||
return key in this.map_;
|
||||
}
|
||||
|
||||
return goog.labs.structs.Map.objectHasOwnProperty_.call(this.map_, key);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Asserts that the given key is a string.
|
||||
* @param {string} key The key to check.
|
||||
* @private
|
||||
*/
|
||||
goog.labs.structs.Map.prototype.assertKeyIsString_ = function(key) {
|
||||
goog.asserts.assert(goog.isString(key), 'key must be a string.');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Browser feature enum necessary for map.
|
||||
* @enum {boolean}
|
||||
*/
|
||||
goog.labs.structs.Map.BrowserFeature = {
|
||||
// TODO(user): Replace with goog.userAgent detection.
|
||||
/**
|
||||
* Whether Object.create method is supported.
|
||||
*/
|
||||
OBJECT_CREATE_SUPPORTED: !!Object.create,
|
||||
|
||||
/**
|
||||
* Whether Object.keys method is supported.
|
||||
*/
|
||||
OBJECT_KEYS_SUPPORTED: !!Object.keys
|
||||
};
|
||||
@@ -0,0 +1,201 @@
|
||||
// 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 Performance test for goog.structs.Map and
|
||||
* goog.labs.structs.Map. To run this test fairly, you would have to
|
||||
* compile this via JsCompiler (with --export_test_functions), and
|
||||
* pull the compiled JS into an empty HTML file.
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.structs.mapPerf');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.labs.structs.Map');
|
||||
goog.require('goog.structs.Map');
|
||||
goog.require('goog.testing.PerformanceTable');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
goog.scope(function() {
|
||||
var mapPerf = goog.labs.structs.mapPerf;
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {goog.labs.structs.Map|goog.structs.Map}
|
||||
*/
|
||||
mapPerf.MapType;
|
||||
|
||||
|
||||
/**
|
||||
* @type {goog.testing.PerformanceTable}
|
||||
*/
|
||||
mapPerf.perfTable;
|
||||
|
||||
|
||||
/**
|
||||
* A key list. This maps loop index to key name to be used during
|
||||
* benchmark. This ensure that we do not need to pay the cost of
|
||||
* string concatenation/GC whenever we derive a key from loop index.
|
||||
*
|
||||
* This is filled once in setUpPage and then remain unchanged for the
|
||||
* rest of the test case.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
mapPerf.keyList = [];
|
||||
|
||||
|
||||
/**
|
||||
* Maxium number of keys in keyList (and, by extension, the map under
|
||||
* test).
|
||||
* @type {number}
|
||||
*/
|
||||
mapPerf.MAX_NUM_KEY = 10000;
|
||||
|
||||
|
||||
/**
|
||||
* Fills the given map with generated key-value pair.
|
||||
* @param {mapPerf.MapType} map The map to fill.
|
||||
* @param {number} numKeys The number of key-value pair to fill.
|
||||
*/
|
||||
mapPerf.fillMap = function(map, numKeys) {
|
||||
goog.asserts.assert(numKeys <= mapPerf.MAX_NUM_KEY);
|
||||
|
||||
for (var i = 0; i < numKeys; ++i) {
|
||||
map.set(mapPerf.keyList[i], i);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Primes the given map with deletion of keys.
|
||||
* @param {mapPerf.MapType} map The map to prime.
|
||||
* @return {mapPerf.MapType} The primed map (for chaining).
|
||||
*/
|
||||
mapPerf.primeMapWithDeletion = function(map) {
|
||||
for (var i = 0; i < 1000; ++i) {
|
||||
map.set(mapPerf.keyList[i], i);
|
||||
}
|
||||
for (var i = 0; i < 1000; ++i) {
|
||||
map.remove(mapPerf.keyList[i]);
|
||||
}
|
||||
return map;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Runs performance test for Map#get with the given map.
|
||||
* @param {mapPerf.MapType} map The map to stress.
|
||||
* @param {string} message Message to be put in performance table.
|
||||
*/
|
||||
mapPerf.runPerformanceTestForMapGet = function(map, message) {
|
||||
mapPerf.fillMap(map, 10000);
|
||||
|
||||
mapPerf.perfTable.run(
|
||||
function() {
|
||||
// Creates local alias for map and keyList.
|
||||
var localMap = map;
|
||||
var localKeyList = mapPerf.keyList;
|
||||
|
||||
for (var i = 0; i < 500; ++i) {
|
||||
var sum = 0;
|
||||
for (var j = 0; j < 10000; ++j) {
|
||||
sum += localMap.get(localKeyList[j]);
|
||||
}
|
||||
}
|
||||
},
|
||||
message);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Runs performance test for Map#set with the given map.
|
||||
* @param {mapPerf.MapType} map The map to stress.
|
||||
* @param {string} message Message to be put in performance table.
|
||||
*/
|
||||
mapPerf.runPerformanceTestForMapSet = function(map, message) {
|
||||
mapPerf.perfTable.run(
|
||||
function() {
|
||||
// Creates local alias for map and keyList.
|
||||
var localMap = map;
|
||||
var localKeyList = mapPerf.keyList;
|
||||
|
||||
for (var i = 0; i < 500; ++i) {
|
||||
for (var j = 0; j < 10000; ++j) {
|
||||
localMap.set(localKeyList[i], i);
|
||||
}
|
||||
}
|
||||
},
|
||||
message);
|
||||
};
|
||||
|
||||
|
||||
goog.global['setUpPage'] = function() {
|
||||
var content = goog.dom.createDom('div');
|
||||
goog.dom.insertChildAt(document.body, content, 0);
|
||||
var ua = navigator.userAgent;
|
||||
content.innerHTML =
|
||||
'<h1>Closure Performance Tests - Map</h1>' +
|
||||
'<p><strong>User-agent: </strong><span id="ua">' + ua + '</span></p>' +
|
||||
'<div id="perf-table"></div>' +
|
||||
'<hr>';
|
||||
|
||||
mapPerf.perfTable = new goog.testing.PerformanceTable(
|
||||
goog.dom.getElement('perf-table'));
|
||||
|
||||
// Fills keyList.
|
||||
for (var i = 0; i < mapPerf.MAX_NUM_KEY; ++i) {
|
||||
mapPerf.keyList.push('k' + i);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
goog.global['testGetFromLabsMap'] = function() {
|
||||
mapPerf.runPerformanceTestForMapGet(
|
||||
new goog.labs.structs.Map(), '#get: no previous deletion (Labs)');
|
||||
};
|
||||
|
||||
|
||||
goog.global['testGetFromOriginalMap'] = function() {
|
||||
mapPerf.runPerformanceTestForMapGet(
|
||||
new goog.structs.Map(), '#get: no previous deletion (Original)');
|
||||
};
|
||||
|
||||
|
||||
goog.global['testGetWithPreviousDeletionFromLabsMap'] = function() {
|
||||
mapPerf.runPerformanceTestForMapGet(
|
||||
mapPerf.primeMapWithDeletion(new goog.labs.structs.Map()),
|
||||
'#get: with previous deletion (Labs)');
|
||||
};
|
||||
|
||||
|
||||
goog.global['testGetWithPreviousDeletionFromOriginalMap'] = function() {
|
||||
mapPerf.runPerformanceTestForMapGet(
|
||||
mapPerf.primeMapWithDeletion(new goog.structs.Map()),
|
||||
'#get: with previous deletion (Original)');
|
||||
};
|
||||
|
||||
|
||||
goog.global['testSetFromLabsMap'] = function() {
|
||||
mapPerf.runPerformanceTestForMapSet(
|
||||
new goog.labs.structs.Map(), '#set: no previous deletion (Labs)');
|
||||
};
|
||||
|
||||
|
||||
goog.global['testSetFromOriginalMap'] = function() {
|
||||
mapPerf.runPerformanceTestForMapSet(
|
||||
new goog.structs.Map(), '#set: no previous deletion (Original)');
|
||||
};
|
||||
|
||||
}); // goog.scope
|
||||
@@ -0,0 +1,279 @@
|
||||
// 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 collection similar to
|
||||
* {@code goog.labs.structs.Map}, but also allows associating multiple
|
||||
* values with a single key.
|
||||
*
|
||||
* This implementation ensures that you can use any string keys.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.structs.Multimap');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.labs.object');
|
||||
goog.require('goog.labs.structs.Map');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new multimap.
|
||||
* @constructor
|
||||
*/
|
||||
goog.labs.structs.Multimap = function() {
|
||||
this.clear();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The backing map.
|
||||
* @type {!goog.labs.structs.Map}
|
||||
* @private
|
||||
*/
|
||||
goog.labs.structs.Multimap.prototype.map_;
|
||||
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.labs.structs.Multimap.prototype.count_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Clears the multimap.
|
||||
*/
|
||||
goog.labs.structs.Multimap.prototype.clear = function() {
|
||||
this.count_ = 0;
|
||||
this.map_ = new goog.labs.structs.Map();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clones this multimap.
|
||||
* @return {!goog.labs.structs.Multimap} A multimap that contains all
|
||||
* the mapping this multimap has.
|
||||
*/
|
||||
goog.labs.structs.Multimap.prototype.clone = function() {
|
||||
var map = new goog.labs.structs.Multimap();
|
||||
map.addAllFromMultimap(this);
|
||||
return map;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds the given (key, value) pair to the map. The (key, value) pair
|
||||
* is guaranteed to be added.
|
||||
* @param {string} key The key to add.
|
||||
* @param {*} value The value to add.
|
||||
*/
|
||||
goog.labs.structs.Multimap.prototype.add = function(key, value) {
|
||||
var values = this.map_.get(key);
|
||||
if (!values) {
|
||||
this.map_.set(key, (values = []));
|
||||
}
|
||||
|
||||
values.push(value);
|
||||
this.count_++;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stores a collection of values to the given key. Does not replace
|
||||
* existing (key, value) pairs.
|
||||
* @param {string} key The key to add.
|
||||
* @param {!Array.<*>} values The values to add.
|
||||
*/
|
||||
goog.labs.structs.Multimap.prototype.addAllValues = function(key, values) {
|
||||
goog.array.forEach(values, function(v) {
|
||||
this.add(key, v);
|
||||
}, this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds the contents of the given map/multimap to this multimap.
|
||||
* @param {!(goog.labs.structs.Map|goog.labs.structs.Multimap)} map The
|
||||
* map to add.
|
||||
*/
|
||||
goog.labs.structs.Multimap.prototype.addAllFromMultimap = function(map) {
|
||||
goog.array.forEach(map.getEntries(), function(entry) {
|
||||
this.add(entry[0], entry[1]);
|
||||
}, this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Replaces all the values for the given key with the given values.
|
||||
* @param {string} key The key whose values are to be replaced.
|
||||
* @param {!Array.<*>} values The new values. If empty, this is
|
||||
* equivalent to {@code removaAll(key)}.
|
||||
*/
|
||||
goog.labs.structs.Multimap.prototype.replaceValues = function(key, values) {
|
||||
this.removeAll(key);
|
||||
this.addAllValues(key, values);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the values correspond to the given key.
|
||||
* @param {string} key The key to retrieve.
|
||||
* @return {!Array.<*>} An array of values corresponding to the given
|
||||
* key. May be empty. Note that the ordering of values are not
|
||||
* guaranteed to be consistent.
|
||||
*/
|
||||
goog.labs.structs.Multimap.prototype.get = function(key) {
|
||||
var values = /** @type {Array.<string>} */ (this.map_.get(key));
|
||||
return values ? goog.array.clone(values) : [];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a single occurrence of (key, value) pair.
|
||||
* @param {string} key The key to remove.
|
||||
* @param {*} value The value to remove.
|
||||
* @return {boolean} Whether any matching (key, value) pair is removed.
|
||||
*/
|
||||
goog.labs.structs.Multimap.prototype.remove = function(key, value) {
|
||||
var values = /** @type {Array.<string>} */ (this.map_.get(key));
|
||||
if (!values) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var removed = goog.array.removeIf(values, function(v) {
|
||||
return goog.labs.object.is(value, v);
|
||||
});
|
||||
|
||||
if (removed) {
|
||||
this.count_--;
|
||||
if (values.length == 0) {
|
||||
this.map_.remove(key);
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all values corresponding to the given key.
|
||||
* @param {string} key The key whose values are to be removed.
|
||||
* @return {boolean} Whether any value is removed.
|
||||
*/
|
||||
goog.labs.structs.Multimap.prototype.removeAll = function(key) {
|
||||
// We have to first retrieve the values from the backing map because
|
||||
// we need to keep track of count (and correctly calculates the
|
||||
// return value). values may be undefined.
|
||||
var values = this.map_.get(key);
|
||||
if (this.map_.remove(key)) {
|
||||
this.count_ -= values.length;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the multimap is empty.
|
||||
*/
|
||||
goog.labs.structs.Multimap.prototype.isEmpty = function() {
|
||||
return !this.count_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The count of (key, value) pairs in the map.
|
||||
*/
|
||||
goog.labs.structs.Multimap.prototype.getCount = function() {
|
||||
return this.count_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} key The key to check.
|
||||
* @param {string} value The value to check.
|
||||
* @return {boolean} Whether the (key, value) pair exists in the multimap.
|
||||
*/
|
||||
goog.labs.structs.Multimap.prototype.containsEntry = function(key, value) {
|
||||
var values = /** @type {Array.<string>} */ (this.map_.get(key));
|
||||
if (!values) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var index = goog.array.findIndex(values, function(v) {
|
||||
return goog.labs.object.is(v, value);
|
||||
});
|
||||
return index >= 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} key The key to check.
|
||||
* @return {boolean} Whether the multimap contains at least one (key,
|
||||
* value) pair with the given key.
|
||||
*/
|
||||
goog.labs.structs.Multimap.prototype.containsKey = function(key) {
|
||||
return this.map_.containsKey(key);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {*} value The value to check.
|
||||
* @return {boolean} Whether the multimap contains at least one (key,
|
||||
* value) pair with the given value.
|
||||
*/
|
||||
goog.labs.structs.Multimap.prototype.containsValue = function(value) {
|
||||
return goog.array.some(this.map_.getValues(),
|
||||
function(values) {
|
||||
return goog.array.some(/** @type {Array} */ (values), function(v) {
|
||||
return goog.labs.object.is(v, value);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!Array.<string>} An array of unique keys.
|
||||
*/
|
||||
goog.labs.structs.Multimap.prototype.getKeys = function() {
|
||||
return this.map_.getKeys();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!Array.<*>} An array of values. There may be duplicates.
|
||||
*/
|
||||
goog.labs.structs.Multimap.prototype.getValues = function() {
|
||||
return goog.array.flatten(this.map_.getValues());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!Array.<!Array>} An array of entries. Each entry is of the
|
||||
* form [key, value].
|
||||
*/
|
||||
goog.labs.structs.Multimap.prototype.getEntries = function() {
|
||||
var keys = this.getKeys();
|
||||
var entries = [];
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i];
|
||||
var values = this.get(key);
|
||||
for (var j = 0; j < values.length; j++) {
|
||||
entries.push([key, values[j]]);
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
};
|
||||
@@ -0,0 +1,178 @@
|
||||
// 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 Utility class that monitors pixel density ratio changes.
|
||||
*
|
||||
* @see ../demos/pixeldensitymonitor.html
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.style.PixelDensityMonitor');
|
||||
goog.provide('goog.labs.style.PixelDensityMonitor.Density');
|
||||
goog.provide('goog.labs.style.PixelDensityMonitor.EventType');
|
||||
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventTarget');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Monitors the window for changes to the ratio between device and screen
|
||||
* pixels, e.g. when the user moves the window from a high density screen to a
|
||||
* screen with normal density. Dispatches
|
||||
* goog.labs.style.PixelDensityMonitor.EventType.CHANGE events when the density
|
||||
* changes between the two predefined values NORMAL and HIGH.
|
||||
*
|
||||
* This class uses the window.devicePixelRatio value which is supported in
|
||||
* WebKit and FF18. If the value does not exist, it will always return a
|
||||
* NORMAL density. It requires support for MediaQueryList to detect changes to
|
||||
* the devicePixelRatio.
|
||||
*
|
||||
* @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper which contains the
|
||||
* document associated with the window to listen to. Defaults to the one in
|
||||
* which this code is executing.
|
||||
* @constructor
|
||||
* @extends {goog.events.EventTarget}
|
||||
*/
|
||||
goog.labs.style.PixelDensityMonitor = function(opt_domHelper) {
|
||||
goog.base(this);
|
||||
|
||||
/**
|
||||
* @type {Window}
|
||||
* @private
|
||||
*/
|
||||
this.window_ = opt_domHelper ? opt_domHelper.getWindow() : window;
|
||||
|
||||
/**
|
||||
* The last density that was reported so that changes can be detected.
|
||||
* @type {goog.labs.style.PixelDensityMonitor.Density}
|
||||
* @private
|
||||
*/
|
||||
this.lastDensity_ = this.getDensity();
|
||||
|
||||
/**
|
||||
* @type {function (MediaQueryList)}
|
||||
* @private
|
||||
*/
|
||||
this.listener_ = goog.bind(this.handleMediaQueryChange_, this);
|
||||
|
||||
/**
|
||||
* The media query list for a query that detects high density, if supported
|
||||
* by the browser. Because matchMedia returns a new object for every call, it
|
||||
* needs to be saved here so the listener can be removed when disposing.
|
||||
* @type {?MediaQueryList}
|
||||
* @private
|
||||
*/
|
||||
this.mediaQueryList_ = this.window_.matchMedia ? this.window_.matchMedia(
|
||||
goog.labs.style.PixelDensityMonitor.HIGH_DENSITY_QUERY_) : null;
|
||||
};
|
||||
goog.inherits(goog.labs.style.PixelDensityMonitor, goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* The two different pixel density modes on which the various ratios between
|
||||
* physical and device pixels are mapped.
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.labs.style.PixelDensityMonitor.Density = {
|
||||
/**
|
||||
* Mode for older portable devices and desktop screens, defined as having a
|
||||
* device pixel ratio of less than 1.5.
|
||||
*/
|
||||
NORMAL: 1,
|
||||
|
||||
/**
|
||||
* Mode for newer portable devices with a high resolution screen, defined as
|
||||
* having a device pixel ratio of more than 1.5.
|
||||
*/
|
||||
HIGH: 2
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The events fired by the PixelDensityMonitor.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.labs.style.PixelDensityMonitor.EventType = {
|
||||
/**
|
||||
* Dispatched when density changes between NORMAL and HIGH.
|
||||
*/
|
||||
CHANGE: goog.events.getUniqueId('change')
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Minimum ratio between device and screen pixel needed for high density mode.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.labs.style.PixelDensityMonitor.HIGH_DENSITY_RATIO_ = 1.5;
|
||||
|
||||
|
||||
/**
|
||||
* Media query that matches for high density.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.labs.style.PixelDensityMonitor.HIGH_DENSITY_QUERY_ =
|
||||
'(min-resolution: 1.5dppx), (-webkit-min-device-pixel-ratio: 1.5)';
|
||||
|
||||
|
||||
/**
|
||||
* Starts monitoring for changes in pixel density.
|
||||
*/
|
||||
goog.labs.style.PixelDensityMonitor.prototype.start = function() {
|
||||
if (this.mediaQueryList_) {
|
||||
this.mediaQueryList_.addListener(this.listener_);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {goog.labs.style.PixelDensityMonitor.Density} The density for the
|
||||
* window.
|
||||
*/
|
||||
goog.labs.style.PixelDensityMonitor.prototype.getDensity = function() {
|
||||
if (this.window_.devicePixelRatio >=
|
||||
goog.labs.style.PixelDensityMonitor.HIGH_DENSITY_RATIO_) {
|
||||
return goog.labs.style.PixelDensityMonitor.Density.HIGH;
|
||||
} else {
|
||||
return goog.labs.style.PixelDensityMonitor.Density.NORMAL;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles a change to the media query and checks whether the density has
|
||||
* changed since the last call.
|
||||
* @param {MediaQueryList} mql The list of changed media queries.
|
||||
* @private
|
||||
*/
|
||||
goog.labs.style.PixelDensityMonitor.prototype.handleMediaQueryChange_ =
|
||||
function(mql) {
|
||||
var newDensity = this.getDensity();
|
||||
if (this.lastDensity_ != newDensity) {
|
||||
this.lastDensity_ = newDensity;
|
||||
this.dispatchEvent(goog.labs.style.PixelDensityMonitor.EventType.CHANGE);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.labs.style.PixelDensityMonitor.prototype.disposeInternal = function() {
|
||||
if (this.mediaQueryList_) {
|
||||
this.mediaQueryList_.removeListener(this.listener_);
|
||||
}
|
||||
goog.base(this, 'disposeInternal');
|
||||
};
|
||||
@@ -0,0 +1,146 @@
|
||||
// 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 Tests for goog.labs.style.PixelDensityMonitor.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.style.PixelDensityMonitorTest');
|
||||
goog.setTestOnly('goog.labs.style.PixelDensityMonitorTest');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.dom.DomHelper');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.labs.style.PixelDensityMonitor');
|
||||
goog.require('goog.testing.MockControl');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.recordFunction');
|
||||
|
||||
var fakeWindow;
|
||||
var recordFunction;
|
||||
var monitor;
|
||||
var mockControl;
|
||||
var mediaQueryLists;
|
||||
|
||||
function setUp() {
|
||||
recordFunction = goog.testing.recordFunction();
|
||||
mediaQueryLists = [];
|
||||
mockControl = new goog.testing.MockControl();
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
mockControl.$verifyAll();
|
||||
goog.dispose(monitor);
|
||||
goog.dispose(recordFunction);
|
||||
}
|
||||
|
||||
function setUpMonitor(initialRatio, hasMatchMedia) {
|
||||
fakeWindow = {
|
||||
devicePixelRatio: initialRatio
|
||||
};
|
||||
|
||||
if (hasMatchMedia) {
|
||||
// Every call to matchMedia should return a new media query list with its
|
||||
// own set of listeners.
|
||||
fakeWindow.matchMedia = function(query) {
|
||||
var listeners = [];
|
||||
var newList = {
|
||||
addListener: function(listener) {
|
||||
listeners.push(listener);
|
||||
},
|
||||
removeListener: function(listener) {
|
||||
goog.array.remove(listeners, listener);
|
||||
},
|
||||
callListeners: function() {
|
||||
for (var i = 0; i < listeners.length; i++) {
|
||||
listeners[i]();
|
||||
}
|
||||
},
|
||||
getListenerCount: function() {
|
||||
return listeners.length;
|
||||
}
|
||||
};
|
||||
mediaQueryLists.push(newList);
|
||||
return newList;
|
||||
};
|
||||
}
|
||||
|
||||
var domHelper = mockControl.createStrictMock(goog.dom.DomHelper);
|
||||
domHelper.getWindow().$returns(fakeWindow);
|
||||
mockControl.$replayAll();
|
||||
|
||||
monitor = new goog.labs.style.PixelDensityMonitor(domHelper);
|
||||
goog.events.listen(monitor,
|
||||
goog.labs.style.PixelDensityMonitor.EventType.CHANGE, recordFunction);
|
||||
}
|
||||
|
||||
function setNewRatio(newRatio) {
|
||||
fakeWindow.devicePixelRatio = newRatio;
|
||||
for (var i = 0; i < mediaQueryLists.length; i++) {
|
||||
mediaQueryLists[i].callListeners();
|
||||
}
|
||||
}
|
||||
|
||||
function testNormalDensity() {
|
||||
setUpMonitor(1, false);
|
||||
assertEquals(goog.labs.style.PixelDensityMonitor.Density.NORMAL,
|
||||
monitor.getDensity());
|
||||
}
|
||||
|
||||
function testHighDensity() {
|
||||
setUpMonitor(1.5, false);
|
||||
assertEquals(goog.labs.style.PixelDensityMonitor.Density.HIGH,
|
||||
monitor.getDensity());
|
||||
}
|
||||
|
||||
function testNormalDensityIfUndefined() {
|
||||
setUpMonitor(undefined, false);
|
||||
assertEquals(goog.labs.style.PixelDensityMonitor.Density.NORMAL,
|
||||
monitor.getDensity());
|
||||
}
|
||||
|
||||
function testChangeEvent() {
|
||||
setUpMonitor(1, true);
|
||||
assertEquals(goog.labs.style.PixelDensityMonitor.Density.NORMAL,
|
||||
monitor.getDensity());
|
||||
monitor.start();
|
||||
|
||||
setNewRatio(2);
|
||||
var call = recordFunction.popLastCall();
|
||||
assertEquals(goog.labs.style.PixelDensityMonitor.Density.HIGH,
|
||||
call.getArgument(0).target.getDensity());
|
||||
assertEquals(goog.labs.style.PixelDensityMonitor.Density.HIGH,
|
||||
monitor.getDensity());
|
||||
|
||||
setNewRatio(1);
|
||||
call = recordFunction.popLastCall();
|
||||
assertEquals(goog.labs.style.PixelDensityMonitor.Density.NORMAL,
|
||||
call.getArgument(0).target.getDensity());
|
||||
assertEquals(goog.labs.style.PixelDensityMonitor.Density.NORMAL,
|
||||
monitor.getDensity());
|
||||
}
|
||||
|
||||
function testListenerIsDisposed() {
|
||||
setUpMonitor(1, true);
|
||||
monitor.start();
|
||||
|
||||
assertEquals(1, mediaQueryLists.length);
|
||||
assertEquals(1, mediaQueryLists[0].getListenerCount());
|
||||
|
||||
goog.dispose(monitor);
|
||||
|
||||
assertEquals(1, mediaQueryLists.length);
|
||||
assertEquals(0, mediaQueryLists[0].getListenerCount());
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// 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 Provides main functionality of assertThat. assertThat calls the
|
||||
* matcher's matches method to test if a matcher matches assertThat's arguments.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.labs.testing.MatcherError');
|
||||
goog.provide('goog.labs.testing.assertThat');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.debug.Error');
|
||||
goog.require('goog.labs.testing.Matcher');
|
||||
|
||||
|
||||
/**
|
||||
* Asserts that the actual value evaluated by the matcher is true.
|
||||
*
|
||||
* @param {*} actual The object to assert by the matcher.
|
||||
* @param {!goog.labs.testing.Matcher} matcher A matcher to verify values.
|
||||
* @param {string=} opt_reason Description of what is asserted.
|
||||
*
|
||||
*/
|
||||
goog.labs.testing.assertThat = function(actual, matcher, opt_reason) {
|
||||
if (!matcher.matches(actual)) {
|
||||
// Prefix the error description with a reason from the assert ?
|
||||
var prefix = opt_reason ? opt_reason + ': ' : '';
|
||||
var desc = prefix + matcher.describe(actual);
|
||||
|
||||
// some sort of failure here
|
||||
throw new goog.labs.testing.MatcherError(desc);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Error thrown when a Matcher fails to match the input value.
|
||||
* @param {string=} opt_message The error message.
|
||||
* @constructor
|
||||
* @extends {goog.debug.Error}
|
||||
*/
|
||||
goog.labs.testing.MatcherError = function(opt_message) {
|
||||
goog.base(this, opt_message);
|
||||
};
|
||||
goog.inherits(goog.labs.testing.MatcherError, goog.debug.Error);
|
||||
@@ -0,0 +1,94 @@
|
||||
// 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 Provides the built-in decorators: is, describedAs, anything.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
goog.provide('goog.labs.testing.AnythingMatcher');
|
||||
|
||||
|
||||
goog.require('goog.labs.testing.Matcher');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The Anything matcher. Matches all possible inputs.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.AnythingMatcher = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Matches anything. Useful if one doesn't care what the object under test is.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.AnythingMatcher.prototype.matches =
|
||||
function(actualObject) {
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This method is never called but is needed so AnythingMatcher implements the
|
||||
* Matcher interface.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.AnythingMatcher.prototype.describe =
|
||||
function(actualObject) {
|
||||
throw Error('AnythingMatcher should never fail!');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a matcher that matches anything.
|
||||
*
|
||||
* @return {!goog.labs.testing.AnythingMatcher} A AnythingMatcher.
|
||||
*/
|
||||
function anything() {
|
||||
return new goog.labs.testing.AnythingMatcher();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returnes any matcher that is passed to it (aids readability).
|
||||
*
|
||||
* @param {!goog.labs.testing.Matcher} matcher A matcher.
|
||||
* @return {!goog.labs.testing.Matcher} The wrapped matcher.
|
||||
*/
|
||||
function is(matcher) {
|
||||
return matcher;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a matcher with a customized description for the given matcher.
|
||||
*
|
||||
* @param {string} description The custom description for the matcher.
|
||||
* @param {!goog.labs.testing.Matcher} matcher The matcher.
|
||||
*
|
||||
* @return {!goog.labs.testing.Matcher} The matcher with custom description.
|
||||
*/
|
||||
function describedAs(description, matcher) {
|
||||
matcher.describe = function(value) {
|
||||
return description;
|
||||
};
|
||||
return matcher;
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
// 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 Provides the built-in dictionary matcher methods like
|
||||
* hasEntry, hasEntries, hasKey, hasValue, etc.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
goog.provide('goog.labs.testing.HasEntriesMatcher');
|
||||
goog.provide('goog.labs.testing.HasEntryMatcher');
|
||||
goog.provide('goog.labs.testing.HasKeyMatcher');
|
||||
goog.provide('goog.labs.testing.HasValueMatcher');
|
||||
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.labs.testing.Matcher');
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The HasEntries matcher.
|
||||
*
|
||||
* @param {!Object} entries The entries to check in the object.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.HasEntriesMatcher = function(entries) {
|
||||
/**
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.entries_ = entries;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if an object has particular entries.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.HasEntriesMatcher.prototype.matches =
|
||||
function(actualObject) {
|
||||
goog.asserts.assertObject(actualObject, 'Expected an Object');
|
||||
var object = /** @type {!Object} */(actualObject);
|
||||
return goog.object.every(this.entries_, function(value, key) {
|
||||
return goog.object.containsKey(object, key) &&
|
||||
object[key] === value;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.HasEntriesMatcher.prototype.describe =
|
||||
function(actualObject) {
|
||||
goog.asserts.assertObject(actualObject, 'Expected an Object');
|
||||
var object = /** @type {!Object} */(actualObject);
|
||||
var errorString = 'Input object did not contain the following entries:\n';
|
||||
goog.object.forEach(this.entries_, function(value, key) {
|
||||
if (!goog.object.containsKey(object, key) ||
|
||||
object[key] !== value) {
|
||||
errorString += key + ': ' + value + '\n';
|
||||
}
|
||||
});
|
||||
return errorString;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The HasEntry matcher.
|
||||
*
|
||||
* @param {string} key The key for the entry.
|
||||
* @param {*} value The value for the key.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.HasEntryMatcher = function(key, value) {
|
||||
/**
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.key_ = key;
|
||||
/**
|
||||
* @type {*}
|
||||
* @private
|
||||
*/
|
||||
this.value_ = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if an object has a particular entry.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.HasEntryMatcher.prototype.matches =
|
||||
function(actualObject) {
|
||||
goog.asserts.assertObject(actualObject);
|
||||
return goog.object.containsKey(actualObject, this.key_) &&
|
||||
actualObject[this.key_] === this.value_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.HasEntryMatcher.prototype.describe =
|
||||
function(actualObject) {
|
||||
goog.asserts.assertObject(actualObject);
|
||||
var errorMsg;
|
||||
if (goog.object.containsKey(actualObject, this.key_)) {
|
||||
errorMsg = 'Input object did not contain key: ' + this.key_;
|
||||
} else {
|
||||
errorMsg = 'Value for key did not match value: ' + this.value_;
|
||||
}
|
||||
return errorMsg;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The HasKey matcher.
|
||||
*
|
||||
* @param {string} key The key to check in the object.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.HasKeyMatcher = function(key) {
|
||||
/**
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.key_ = key;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if an object has a key.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.HasKeyMatcher.prototype.matches =
|
||||
function(actualObject) {
|
||||
goog.asserts.assertObject(actualObject);
|
||||
return goog.object.containsKey(actualObject, this.key_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.HasKeyMatcher.prototype.describe =
|
||||
function(actualObject) {
|
||||
goog.asserts.assertObject(actualObject);
|
||||
return 'Input object did not contain the key: ' + this.key_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The HasValue matcher.
|
||||
*
|
||||
* @param {*} value The value to check in the object.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.HasValueMatcher = function(value) {
|
||||
/**
|
||||
* @type {*}
|
||||
* @private
|
||||
*/
|
||||
this.value_ = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if an object contains a value
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.HasValueMatcher.prototype.matches =
|
||||
function(actualObject) {
|
||||
goog.asserts.assertObject(actualObject, 'Expected an Object');
|
||||
var object = /** @type {!Object} */(actualObject);
|
||||
return goog.object.containsValue(object, this.value_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.HasValueMatcher.prototype.describe =
|
||||
function(actualObject) {
|
||||
return 'Input object did not contain the value: ' + this.value_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gives a matcher that asserts an object contains all the given key-value pairs
|
||||
* in the input object.
|
||||
*
|
||||
* @param {!Object} entries The entries to check for presence in the object.
|
||||
*
|
||||
* @return {!goog.labs.testing.HasEntriesMatcher} A HasEntriesMatcher.
|
||||
*/
|
||||
function hasEntries(entries) {
|
||||
return new goog.labs.testing.HasEntriesMatcher(entries);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gives a matcher that asserts an object contains the given key-value pair.
|
||||
*
|
||||
* @param {string} key The key to check for presence in the object.
|
||||
* @param {*} value The value to check for presence in the object.
|
||||
*
|
||||
* @return {!goog.labs.testing.HasEntryMatcher} A HasEntryMatcher.
|
||||
*/
|
||||
function hasEntry(key, value) {
|
||||
return new goog.labs.testing.HasEntryMatcher(key, value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gives a matcher that asserts an object contains the given key.
|
||||
*
|
||||
* @param {string} key The key to check for presence in the object.
|
||||
*
|
||||
* @return {!goog.labs.testing.HasKeyMatcher} A HasKeyMatcher.
|
||||
*/
|
||||
function hasKey(key) {
|
||||
return new goog.labs.testing.HasKeyMatcher(key);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gives a matcher that asserts an object contains the given value.
|
||||
*
|
||||
* @param {*} value The value to check for presence in the object.
|
||||
*
|
||||
* @return {!goog.labs.testing.HasValueMatcher} A HasValueMatcher.
|
||||
*/
|
||||
function hasValue(value) {
|
||||
return new goog.labs.testing.HasValueMatcher(value);
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
// 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 Provides the built-in logic matchers: anyOf, allOf, and isNot.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.labs.testing.AllOfMatcher');
|
||||
goog.provide('goog.labs.testing.AnyOfMatcher');
|
||||
goog.provide('goog.labs.testing.IsNotMatcher');
|
||||
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.labs.testing.Matcher');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The AllOf matcher.
|
||||
*
|
||||
* @param {!Array.<!goog.labs.testing.Matcher>} matchers Input matchers.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.AllOfMatcher = function(matchers) {
|
||||
/**
|
||||
* @type {!Array.<!goog.labs.testing.Matcher>}
|
||||
* @private
|
||||
*/
|
||||
this.matchers_ = matchers;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if all of the matchers match the input value.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.AllOfMatcher.prototype.matches = function(actualValue) {
|
||||
return goog.array.every(this.matchers_, function(matcher) {
|
||||
return matcher.matches(actualValue);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Describes why the matcher failed. The returned string is a concatenation of
|
||||
* all the failed matchers' error strings.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.AllOfMatcher.prototype.describe =
|
||||
function(actualValue) {
|
||||
// TODO(user) : Optimize this to remove duplication with matches ?
|
||||
var errorString = '';
|
||||
goog.array.forEach(this.matchers_, function(matcher) {
|
||||
if (!matcher.matches(actualValue)) {
|
||||
errorString += matcher.describe(actualValue) + '\n';
|
||||
}
|
||||
});
|
||||
return errorString;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The AnyOf matcher.
|
||||
*
|
||||
* @param {!Array.<!goog.labs.testing.Matcher>} matchers Input matchers.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.AnyOfMatcher = function(matchers) {
|
||||
/**
|
||||
* @type {!Array.<!goog.labs.testing.Matcher>}
|
||||
* @private
|
||||
*/
|
||||
this.matchers_ = matchers;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if any of the matchers matches the input value.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.AnyOfMatcher.prototype.matches = function(actualValue) {
|
||||
return goog.array.some(this.matchers_, function(matcher) {
|
||||
return matcher.matches(actualValue);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Describes why the matcher failed.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.AnyOfMatcher.prototype.describe =
|
||||
function(actualValue) {
|
||||
// TODO(user) : Optimize this to remove duplication with matches ?
|
||||
var errorString = '';
|
||||
goog.array.forEach(this.matchers_, function(matcher) {
|
||||
if (!matcher.matches(actualValue)) {
|
||||
errorString += matcher.describe(actualValue) + '\n';
|
||||
}
|
||||
});
|
||||
return errorString;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The IsNot matcher.
|
||||
*
|
||||
* @param {!goog.labs.testing.Matcher} matcher The matcher to negate.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.IsNotMatcher = function(matcher) {
|
||||
/**
|
||||
* @type {!goog.labs.testing.Matcher}
|
||||
* @private
|
||||
*/
|
||||
this.matcher_ = matcher;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if the input value doesn't satisfy a matcher.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.IsNotMatcher.prototype.matches = function(actualValue) {
|
||||
return !this.matcher_.matches(actualValue);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Describes why the matcher failed.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.IsNotMatcher.prototype.describe =
|
||||
function(actualValue) {
|
||||
return 'The following is false: ' + this.matcher_.describe(actualValue);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a matcher that will succeed only if all of the given matchers
|
||||
* succeed.
|
||||
*
|
||||
* @param {...goog.labs.testing.Matcher} var_args The matchers to test
|
||||
* against.
|
||||
*
|
||||
* @return {!goog.labs.testing.AllOfMatcher} The AllOf matcher.
|
||||
*/
|
||||
function allOf(var_args) {
|
||||
var matchers = goog.array.toArray(arguments);
|
||||
return new goog.labs.testing.AllOfMatcher(matchers);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Accepts a set of matchers and returns a matcher which matches
|
||||
* values which satisfy the constraints of any of the given matchers.
|
||||
*
|
||||
* @param {...goog.labs.testing.Matcher} var_args The matchers to test
|
||||
* against.
|
||||
*
|
||||
* @return {!goog.labs.testing.AnyOfMatcher} The AnyOf matcher.
|
||||
*/
|
||||
function anyOf(var_args) {
|
||||
var matchers = goog.array.toArray(arguments);
|
||||
return new goog.labs.testing.AnyOfMatcher(matchers);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a matcher that negates the input matcher. The returned
|
||||
* matcher matches the values not matched by the input matcher and vice-versa.
|
||||
*
|
||||
* @param {!goog.labs.testing.Matcher} matcher The matcher to test against.
|
||||
*
|
||||
* @return {!goog.labs.testing.IsNotMatcher} The IsNot matcher.
|
||||
*/
|
||||
function isNot(matcher) {
|
||||
return new goog.labs.testing.IsNotMatcher(matcher);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// 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 Provides the base Matcher interface. User code should use the
|
||||
* matchers through assertThat statements and not directly.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.labs.testing.Matcher');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A matcher object to be used in assertThat statements.
|
||||
* @interface
|
||||
*/
|
||||
goog.labs.testing.Matcher = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether a value matches the constraints of the match.
|
||||
*
|
||||
* @param {*} value The object to match.
|
||||
* @return {boolean} Whether the input value matches this matcher.
|
||||
*/
|
||||
goog.labs.testing.Matcher.prototype.matches = function(value) {};
|
||||
|
||||
|
||||
/**
|
||||
* Describes why the matcher failed.
|
||||
*
|
||||
* @param {*} value The value that didn't match.
|
||||
* @param {string=} opt_description A partial description to which the reason
|
||||
* will be appended.
|
||||
*
|
||||
* @return {string} Description of why the matcher failed.
|
||||
*/
|
||||
goog.labs.testing.Matcher.prototype.describe =
|
||||
function(value, opt_description) {};
|
||||
@@ -0,0 +1,334 @@
|
||||
// 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 Provides the built-in number matchers like lessThan,
|
||||
* greaterThan, etc.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.labs.testing.CloseToMatcher');
|
||||
goog.provide('goog.labs.testing.EqualToMatcher');
|
||||
goog.provide('goog.labs.testing.GreaterThanEqualToMatcher');
|
||||
goog.provide('goog.labs.testing.GreaterThanMatcher');
|
||||
goog.provide('goog.labs.testing.LessThanEqualToMatcher');
|
||||
goog.provide('goog.labs.testing.LessThanMatcher');
|
||||
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.labs.testing.Matcher');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The GreaterThan matcher.
|
||||
*
|
||||
* @param {number} value The value to compare.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.GreaterThanMatcher = function(value) {
|
||||
/**
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.value_ = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if input value is greater than the expected value.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.GreaterThanMatcher.prototype.matches = function(actualValue) {
|
||||
goog.asserts.assertNumber(actualValue);
|
||||
return actualValue > this.value_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.GreaterThanMatcher.prototype.describe =
|
||||
function(actualValue) {
|
||||
goog.asserts.assertNumber(actualValue);
|
||||
return actualValue + ' is not greater than ' + this.value_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The lessThan matcher.
|
||||
*
|
||||
* @param {number} value The value to compare.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.LessThanMatcher = function(value) {
|
||||
/**
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.value_ = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if the input value is less than the expected value.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.LessThanMatcher.prototype.matches = function(actualValue) {
|
||||
goog.asserts.assertNumber(actualValue);
|
||||
return actualValue < this.value_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.LessThanMatcher.prototype.describe =
|
||||
function(actualValue) {
|
||||
goog.asserts.assertNumber(actualValue);
|
||||
return actualValue + ' is not less than ' + this.value_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The GreaterThanEqualTo matcher.
|
||||
*
|
||||
* @param {number} value The value to compare.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.GreaterThanEqualToMatcher = function(value) {
|
||||
/**
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.value_ = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if the input value is greater than equal to the expected value.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.GreaterThanEqualToMatcher.prototype.matches =
|
||||
function(actualValue) {
|
||||
goog.asserts.assertNumber(actualValue);
|
||||
return actualValue >= this.value_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.GreaterThanEqualToMatcher.prototype.describe =
|
||||
function(actualValue) {
|
||||
goog.asserts.assertNumber(actualValue);
|
||||
return actualValue + ' is not greater than equal to ' + this.value_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The LessThanEqualTo matcher.
|
||||
*
|
||||
* @param {number} value The value to compare.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.LessThanEqualToMatcher = function(value) {
|
||||
/**
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.value_ = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if the input value is less than or equal to the expected value.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.LessThanEqualToMatcher.prototype.matches =
|
||||
function(actualValue) {
|
||||
goog.asserts.assertNumber(actualValue);
|
||||
return actualValue <= this.value_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.LessThanEqualToMatcher.prototype.describe =
|
||||
function(actualValue) {
|
||||
goog.asserts.assertNumber(actualValue);
|
||||
return actualValue + ' is not less than equal to ' + this.value_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The EqualTo matcher.
|
||||
*
|
||||
* @param {number} value The value to compare.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.EqualToMatcher = function(value) {
|
||||
/**
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.value_ = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if the input value is equal to the expected value.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.EqualToMatcher.prototype.matches = function(actualValue) {
|
||||
goog.asserts.assertNumber(actualValue);
|
||||
return actualValue === this.value_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.EqualToMatcher.prototype.describe =
|
||||
function(actualValue) {
|
||||
goog.asserts.assertNumber(actualValue);
|
||||
return actualValue + ' is not equal to ' + this.value_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The CloseTo matcher.
|
||||
*
|
||||
* @param {number} value The value to compare.
|
||||
* @param {number} range The range to check within.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.CloseToMatcher = function(value, range) {
|
||||
/**
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.value_ = value;
|
||||
/**
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.range_ = range;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if input value is within a certain range of the expected value.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.CloseToMatcher.prototype.matches = function(actualValue) {
|
||||
goog.asserts.assertNumber(actualValue);
|
||||
return Math.abs(this.value_ - actualValue) < this.range_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.CloseToMatcher.prototype.describe =
|
||||
function(actualValue) {
|
||||
goog.asserts.assertNumber(actualValue);
|
||||
return actualValue + ' is not close to(' + this.range_ + ') ' + this.value_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} value The expected value.
|
||||
*
|
||||
* @return {!goog.labs.testing.GreaterThanMatcher} A GreaterThanMatcher.
|
||||
*/
|
||||
function greaterThan(value) {
|
||||
return new goog.labs.testing.GreaterThanMatcher(value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} value The expected value.
|
||||
*
|
||||
* @return {!goog.labs.testing.GreaterThanEqualToMatcher} A
|
||||
* GreaterThanEqualToMatcher.
|
||||
*/
|
||||
function greaterThanEqualTo(value) {
|
||||
return new goog.labs.testing.GreaterThanEqualToMatcher(value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} value The expected value.
|
||||
*
|
||||
* @return {!goog.labs.testing.LessThanMatcher} A LessThanMatcher.
|
||||
*/
|
||||
function lessThan(value) {
|
||||
return new goog.labs.testing.LessThanMatcher(value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} value The expected value.
|
||||
*
|
||||
* @return {!goog.labs.testing.LessThanEqualToMatcher} A LessThanEqualToMatcher.
|
||||
*/
|
||||
function lessThanEqualTo(value) {
|
||||
return new goog.labs.testing.LessThanEqualToMatcher(value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} value The expected value.
|
||||
*
|
||||
* @return {!goog.labs.testing.EqualToMatcher} An EqualToMatcher.
|
||||
*/
|
||||
function equalTo(value) {
|
||||
return new goog.labs.testing.EqualToMatcher(value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} value The expected value.
|
||||
* @param {number} range The maximum allowed difference from the expected value.
|
||||
*
|
||||
* @return {!goog.labs.testing.CloseToMatcher} A CloseToMatcher.
|
||||
*/
|
||||
function closeTo(value, range) {
|
||||
return new goog.labs.testing.CloseToMatcher(value, range);
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
// 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 Provides the built-in object matchers like equalsObject,
|
||||
* hasProperty, instanceOf, etc.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
goog.provide('goog.labs.testing.HasPropertyMatcher');
|
||||
goog.provide('goog.labs.testing.InstanceOfMatcher');
|
||||
goog.provide('goog.labs.testing.IsNullMatcher');
|
||||
goog.provide('goog.labs.testing.IsNullOrUndefinedMatcher');
|
||||
goog.provide('goog.labs.testing.IsUndefinedMatcher');
|
||||
goog.provide('goog.labs.testing.ObjectEqualsMatcher');
|
||||
|
||||
|
||||
goog.require('goog.labs.testing.Matcher');
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The Equals matcher.
|
||||
*
|
||||
* @param {!Object} expectedObject The expected object.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.ObjectEqualsMatcher = function(expectedObject) {
|
||||
/**
|
||||
* @type {!Object}
|
||||
* @private
|
||||
*/
|
||||
this.object_ = expectedObject;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if two objects are the same.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.ObjectEqualsMatcher.prototype.matches =
|
||||
function(actualObject) {
|
||||
return actualObject === this.object_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.ObjectEqualsMatcher.prototype.describe =
|
||||
function(actualObject) {
|
||||
return 'Input object is not the same as the expected object.';
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The HasProperty matcher.
|
||||
*
|
||||
* @param {string} property Name of the property to test.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.HasPropertyMatcher = function(property) {
|
||||
/**
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.property_ = property;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if an object has a property.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.HasPropertyMatcher.prototype.matches =
|
||||
function(actualObject) {
|
||||
return this.property_ in actualObject;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.HasPropertyMatcher.prototype.describe =
|
||||
function(actualObject) {
|
||||
return 'Object does not have property: ' + this.property_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The InstanceOf matcher.
|
||||
*
|
||||
* @param {!Object} object The expected class object.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.InstanceOfMatcher = function(object) {
|
||||
/**
|
||||
* @type {!Object}
|
||||
* @private
|
||||
*/
|
||||
this.object_ = object;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if an object is an instance of another object.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.InstanceOfMatcher.prototype.matches =
|
||||
function(actualObject) {
|
||||
return actualObject instanceof this.object_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.InstanceOfMatcher.prototype.describe =
|
||||
function(actualObject) {
|
||||
return 'Input object is not an instance of the expected object';
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The IsNullOrUndefined matcher.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.IsNullOrUndefinedMatcher = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if input value is null or undefined.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.IsNullOrUndefinedMatcher.prototype.matches =
|
||||
function(actualValue) {
|
||||
return !goog.isDefAndNotNull(actualValue);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.IsNullOrUndefinedMatcher.prototype.describe =
|
||||
function(actualValue) {
|
||||
return actualValue + ' is not null or undefined.';
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The IsNull matcher.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.IsNullMatcher = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if input value is null.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.IsNullMatcher.prototype.matches =
|
||||
function(actualValue) {
|
||||
return goog.isNull(actualValue);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.IsNullMatcher.prototype.describe =
|
||||
function(actualValue) {
|
||||
return actualValue + ' is not null.';
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The IsUndefined matcher.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.IsUndefinedMatcher = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if input value is undefined.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.IsUndefinedMatcher.prototype.matches =
|
||||
function(actualValue) {
|
||||
return !goog.isDef(actualValue);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.IsUndefinedMatcher.prototype.describe =
|
||||
function(actualValue) {
|
||||
return actualValue + ' is not undefined.';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a matcher that matches objects that are equal to the input object.
|
||||
* Equality in this case means the two objects are references to the same
|
||||
* object.
|
||||
*
|
||||
* @param {!Object} object The expected object.
|
||||
*
|
||||
* @return {!goog.labs.testing.ObjectEqualsMatcher} A
|
||||
* ObjectEqualsMatcher.
|
||||
*/
|
||||
function equalsObject(object) {
|
||||
return new goog.labs.testing.ObjectEqualsMatcher(object);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a matcher that matches objects that contain the input property.
|
||||
*
|
||||
* @param {string} property The property name to check.
|
||||
*
|
||||
* @return {!goog.labs.testing.HasPropertyMatcher} A HasPropertyMatcher.
|
||||
*/
|
||||
function hasProperty(property) {
|
||||
return new goog.labs.testing.HasPropertyMatcher(property);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a matcher that matches instances of the input class.
|
||||
*
|
||||
* @param {!Object} object The class object.
|
||||
*
|
||||
* @return {!goog.labs.testing.InstanceOfMatcher} A
|
||||
* InstanceOfMatcher.
|
||||
*/
|
||||
function instanceOfClass(object) {
|
||||
return new goog.labs.testing.InstanceOfMatcher(object);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a matcher that matches all null values.
|
||||
*
|
||||
* @return {!goog.labs.testing.IsNullMatcher} A IsNullMatcher.
|
||||
*/
|
||||
function isNull() {
|
||||
return new goog.labs.testing.IsNullMatcher();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a matcher that matches all null and undefined values.
|
||||
*
|
||||
* @return {!goog.labs.testing.IsNullOrUndefinedMatcher} A
|
||||
* IsNullOrUndefinedMatcher.
|
||||
*/
|
||||
function isNullOrUndefined() {
|
||||
return new goog.labs.testing.IsNullOrUndefinedMatcher();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a matcher that matches undefined values.
|
||||
*
|
||||
* @return {!goog.labs.testing.IsUndefinedMatcher} A IsUndefinedMatcher.
|
||||
*/
|
||||
function isUndefined() {
|
||||
return new goog.labs.testing.IsUndefinedMatcher();
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
// 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 Provides the built-in string matchers like containsString,
|
||||
* startsWith, endsWith, etc.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.labs.testing.ContainsStringMatcher');
|
||||
goog.provide('goog.labs.testing.EndsWithMatcher');
|
||||
goog.provide('goog.labs.testing.EqualToIgnoringCaseMatcher');
|
||||
goog.provide('goog.labs.testing.EqualToIgnoringWhitespaceMatcher');
|
||||
goog.provide('goog.labs.testing.EqualsMatcher');
|
||||
goog.provide('goog.labs.testing.RegexMatcher');
|
||||
goog.provide('goog.labs.testing.StartsWithMatcher');
|
||||
goog.provide('goog.labs.testing.StringContainsInOrderMatcher');
|
||||
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.labs.testing.Matcher');
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The ContainsString matcher.
|
||||
*
|
||||
* @param {string} value The expected string.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.ContainsStringMatcher = function(value) {
|
||||
/**
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.value_ = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if input string contains the expected string.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.ContainsStringMatcher.prototype.matches =
|
||||
function(actualValue) {
|
||||
goog.asserts.assertString(actualValue);
|
||||
return goog.string.contains(actualValue, this.value_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.ContainsStringMatcher.prototype.describe =
|
||||
function(actualValue) {
|
||||
return actualValue + ' does not contain ' + this.value_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The EndsWith matcher.
|
||||
*
|
||||
* @param {string} value The expected string.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.EndsWithMatcher = function(value) {
|
||||
/**
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.value_ = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if input string ends with the expected string.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.EndsWithMatcher.prototype.matches = function(actualValue) {
|
||||
goog.asserts.assertString(actualValue);
|
||||
return goog.string.endsWith(actualValue, this.value_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.EndsWithMatcher.prototype.describe =
|
||||
function(actualValue) {
|
||||
return actualValue + ' does not end with ' + this.value_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The EqualToIgnoringWhitespace matcher.
|
||||
*
|
||||
* @param {string} value The expected string.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.EqualToIgnoringWhitespaceMatcher = function(value) {
|
||||
/**
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.value_ = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if input string contains the expected string.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.EqualToIgnoringWhitespaceMatcher.prototype.matches =
|
||||
function(actualValue) {
|
||||
goog.asserts.assertString(actualValue);
|
||||
var string1 = goog.string.collapseWhitespace(actualValue);
|
||||
|
||||
return goog.string.caseInsensitiveCompare(this.value_, string1) === 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.EqualToIgnoringWhitespaceMatcher.prototype.describe =
|
||||
function(actualValue) {
|
||||
return actualValue + ' is not equal(ignoring whitespace) to ' + this.value_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The Equals matcher.
|
||||
*
|
||||
* @param {string} value The expected string.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.EqualsMatcher = function(value) {
|
||||
/**
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.value_ = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if input string is equal to the expected string.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.EqualsMatcher.prototype.matches = function(actualValue) {
|
||||
goog.asserts.assertString(actualValue);
|
||||
return this.value_ === actualValue;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.EqualsMatcher.prototype.describe =
|
||||
function(actualValue) {
|
||||
return actualValue + ' is not equal to ' + this.value_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The MatchesRegex matcher.
|
||||
*
|
||||
* @param {!RegExp} regex The expected regex.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.RegexMatcher = function(regex) {
|
||||
/**
|
||||
* @type {!RegExp}
|
||||
* @private
|
||||
*/
|
||||
this.regex_ = regex;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if input string is equal to the expected string.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.RegexMatcher.prototype.matches = function(
|
||||
actualValue) {
|
||||
goog.asserts.assertString(actualValue);
|
||||
return this.regex_.test(actualValue);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.RegexMatcher.prototype.describe =
|
||||
function(actualValue) {
|
||||
return actualValue + ' does not match ' + this.regex_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The StartsWith matcher.
|
||||
*
|
||||
* @param {string} value The expected string.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.StartsWithMatcher = function(value) {
|
||||
/**
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.value_ = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if input string starts with the expected string.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.StartsWithMatcher.prototype.matches = function(actualValue) {
|
||||
goog.asserts.assertString(actualValue);
|
||||
return goog.string.startsWith(actualValue, this.value_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.StartsWithMatcher.prototype.describe =
|
||||
function(actualValue) {
|
||||
return actualValue + ' does not start with ' + this.value_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The StringContainsInOrdermatcher.
|
||||
*
|
||||
* @param {Array.<string>} values The expected string values.
|
||||
*
|
||||
* @constructor
|
||||
* @implements {goog.labs.testing.Matcher}
|
||||
*/
|
||||
goog.labs.testing.StringContainsInOrderMatcher = function(values) {
|
||||
/**
|
||||
* @type {Array.<string>}
|
||||
* @private
|
||||
*/
|
||||
this.values_ = values;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if input string contains, in order, the expected array of strings.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.StringContainsInOrderMatcher.prototype.matches =
|
||||
function(actualValue) {
|
||||
goog.asserts.assertString(actualValue);
|
||||
var currentIndex, previousIndex = 0;
|
||||
for (var i = 0; i < this.values_.length; i++) {
|
||||
currentIndex = goog.string.contains(actualValue, this.values_[i]);
|
||||
if (currentIndex < 0 || currentIndex < previousIndex) {
|
||||
return false;
|
||||
}
|
||||
previousIndex = currentIndex;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.labs.testing.StringContainsInOrderMatcher.prototype.describe =
|
||||
function(actualValue) {
|
||||
return actualValue + ' does not contain the expected values in order.';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Matches a string containing the given string.
|
||||
*
|
||||
* @param {string} value The expected value.
|
||||
*
|
||||
* @return {!goog.labs.testing.ContainsStringMatcher} A
|
||||
* ContainsStringMatcher.
|
||||
*/
|
||||
function containsString(value) {
|
||||
return new goog.labs.testing.ContainsStringMatcher(value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Matches a string that ends with the given string.
|
||||
*
|
||||
* @param {string} value The expected value.
|
||||
*
|
||||
* @return {!goog.labs.testing.EndsWithMatcher} A
|
||||
* EndsWithMatcher.
|
||||
*/
|
||||
function endsWith(value) {
|
||||
return new goog.labs.testing.EndsWithMatcher(value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Matches a string that equals (ignoring whitespace) the given string.
|
||||
*
|
||||
* @param {string} value The expected value.
|
||||
*
|
||||
* @return {!goog.labs.testing.EqualToIgnoringWhitespaceMatcher} A
|
||||
* EqualToIgnoringWhitespaceMatcher.
|
||||
*/
|
||||
function equalToIgnoringWhitespace(value) {
|
||||
return new goog.labs.testing.EqualToIgnoringWhitespaceMatcher(value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Matches a string that equals the given string.
|
||||
*
|
||||
* @param {string} value The expected value.
|
||||
*
|
||||
* @return {!goog.labs.testing.EqualsMatcher} A EqualsMatcher.
|
||||
*/
|
||||
function equals(value) {
|
||||
return new goog.labs.testing.EqualsMatcher(value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Matches a string against a regular expression.
|
||||
*
|
||||
* @param {!RegExp} regex The expected regex.
|
||||
*
|
||||
* @return {!goog.labs.testing.RegexMatcher} A RegexMatcher.
|
||||
*/
|
||||
function matchesRegex(regex) {
|
||||
return new goog.labs.testing.RegexMatcher(regex);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Matches a string that starts with the given string.
|
||||
*
|
||||
* @param {string} value The expected value.
|
||||
*
|
||||
* @return {!goog.labs.testing.StartsWithMatcher} A
|
||||
* StartsWithMatcher.
|
||||
*/
|
||||
function startsWith(value) {
|
||||
return new goog.labs.testing.StartsWithMatcher(value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Matches a string that contains the given strings in order.
|
||||
*
|
||||
* @param {Array.<string>} values The expected value.
|
||||
*
|
||||
* @return {!goog.labs.testing.StringContainsInOrderMatcher} A
|
||||
* StringContainsInOrderMatcher.
|
||||
*/
|
||||
function stringContainsInOrder(values) {
|
||||
return new goog.labs.testing.StringContainsInOrderMatcher(values);
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
// 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 Closure user agent detection (Browser).
|
||||
* @see <a href="http://www.useragentstring.com/">User agent strings</a>
|
||||
* For more information on rendering engine, platform, or device see the other
|
||||
* sub-namespaces in goog.labs.userAgent, goog.labs.userAgent.platform,
|
||||
* goog.labs.userAgent.device respectively.)
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.userAgent.browser');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.labs.userAgent.util');
|
||||
goog.require('goog.memoize');
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the user's browser is Opera.
|
||||
* @private
|
||||
*/
|
||||
goog.labs.userAgent.browser.matchOpera_ = goog.memoize(
|
||||
goog.partial(goog.labs.userAgent.util.matchUserAgent, 'Opera'));
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the user's browser is IE.
|
||||
* @private
|
||||
*/
|
||||
goog.labs.userAgent.browser.matchIE_ = goog.memoize(function() {
|
||||
return goog.labs.userAgent.util.matchUserAgent('Trident') ||
|
||||
goog.labs.userAgent.util.matchUserAgent('MSIE');
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the user's browser is Firefox.
|
||||
* @private
|
||||
*/
|
||||
goog.labs.userAgent.browser.matchFirefox_ = goog.memoize(function() {
|
||||
return goog.labs.userAgent.util.matchUserAgent('Firefox');
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the user's browser is Safari.
|
||||
* @private
|
||||
*/
|
||||
goog.labs.userAgent.browser.matchSafari_ = goog.memoize(function() {
|
||||
return goog.labs.userAgent.util.matchUserAgent('Safari') &&
|
||||
!goog.labs.userAgent.util.matchUserAgent('Chrome') &&
|
||||
!goog.labs.userAgent.util.matchUserAgent('CriOS') &&
|
||||
!goog.labs.userAgent.util.matchUserAgent('Android');
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the user's browser is Chrome.
|
||||
* @private
|
||||
*/
|
||||
goog.labs.userAgent.browser.matchChrome_ = goog.memoize(function() {
|
||||
return goog.labs.userAgent.util.matchUserAgent('Chrome') ||
|
||||
goog.labs.userAgent.util.matchUserAgent('CriOS');
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the user's browser is the Android browser.
|
||||
* @private
|
||||
*/
|
||||
goog.labs.userAgent.browser.matchAndroidBrowser_ = goog.memoize(function() {
|
||||
return goog.labs.userAgent.util.matchUserAgent('Android') &&
|
||||
!goog.labs.userAgent.util.matchUserAgent('Chrome') &&
|
||||
!goog.labs.userAgent.util.matchUserAgent('CriOS');
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the user's browser is Opera.
|
||||
*/
|
||||
goog.labs.userAgent.browser.isOpera =
|
||||
goog.memoize(goog.labs.userAgent.browser.matchOpera_);
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the user's browser is IE.
|
||||
*/
|
||||
goog.labs.userAgent.browser.isIE =
|
||||
goog.memoize(goog.labs.userAgent.browser.matchIE_);
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the user's browser is Firefox.
|
||||
*/
|
||||
goog.labs.userAgent.browser.isFirefox =
|
||||
goog.memoize(goog.labs.userAgent.browser.matchFirefox_);
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the user's browser is Safari.
|
||||
*/
|
||||
goog.labs.userAgent.browser.isSafari =
|
||||
goog.memoize(goog.labs.userAgent.browser.matchSafari_);
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the user's browser is Chrome.
|
||||
*/
|
||||
goog.labs.userAgent.browser.isChrome =
|
||||
goog.memoize(goog.labs.userAgent.browser.matchChrome_);
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the user's browser is the Android browser.
|
||||
*/
|
||||
goog.labs.userAgent.browser.isAndroidBrowser =
|
||||
goog.memoize(goog.labs.userAgent.browser.matchAndroidBrowser_);
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} The browser version or empty string if version cannot be
|
||||
* determined.
|
||||
*/
|
||||
goog.labs.userAgent.browser.getVersion = goog.memoize(function() {
|
||||
var userAgentString = goog.labs.userAgent.util.getUserAgentString();
|
||||
// Special case IE since IE's version is inside the parenthesis and without
|
||||
// the '/'.
|
||||
if (goog.labs.userAgent.browser.isIE()) {
|
||||
return goog.labs.userAgent.browser.getIEVersion_();
|
||||
}
|
||||
|
||||
var versionTuples =
|
||||
goog.labs.userAgent.util.extractVersionTuples(userAgentString);
|
||||
// tuples[2] (The first X/Y tuple after the parenthesis) contains the browser
|
||||
// version number.
|
||||
// TODO (vbhasin): Make this check more robust.
|
||||
goog.asserts.assert(versionTuples.length > 2,
|
||||
'Couldn\'t extract version tuple from user agent string');
|
||||
return goog.isDef(versionTuples[2][1]) ? versionTuples[2][1] : '';
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @param {string|number} version The version to check.
|
||||
* @return {boolean} Whether the browser version is higher or the same as the
|
||||
* given version.
|
||||
*/
|
||||
goog.labs.userAgent.browser.isVersionOrHigher = function(version) {
|
||||
return goog.string.compareVersions(goog.labs.userAgent.browser.getVersion(),
|
||||
version) >= 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines IE version. More information:
|
||||
* http://msdn.microsoft.com/en-us/library/jj676915(v=vs.85).aspx
|
||||
*
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
goog.labs.userAgent.browser.getIEVersion_ = goog.memoize(function() {
|
||||
var gDoc = goog.global['document'];
|
||||
var version;
|
||||
var userAgentString = goog.labs.userAgent.util.getUserAgentString();
|
||||
|
||||
if (gDoc && gDoc.documentMode) {
|
||||
version = gDoc.documentMode;
|
||||
} else if (gDoc && gDoc.compatMode && gDoc.compatMode == 'CSS1Compat') {
|
||||
version = 7;
|
||||
} else {
|
||||
var arr = /\b(?:MSIE|rv)\s+([^\);]+)(?:\)|;)/.exec(userAgentString);
|
||||
version = arr && arr[1] ? arr[1] : '';
|
||||
}
|
||||
return version;
|
||||
});
|
||||
@@ -0,0 +1,193 @@
|
||||
// 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 Unit tests for goog.labs.userAgent.browser.
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.userAgent.browserTest');
|
||||
|
||||
goog.require('goog.labs.userAgent.browser');
|
||||
goog.require('goog.labs.userAgent.testAgents');
|
||||
goog.require('goog.testing.PropertyReplacer');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
goog.setTestOnly('goog.labs.userAgent.browserTest');
|
||||
|
||||
|
||||
var propertyReplacer = new goog.testing.PropertyReplacer();
|
||||
|
||||
function setUp() {
|
||||
// disable memoization
|
||||
propertyReplacer.set(goog.memoize, 'ENABLE_MEMOIZE', false);
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
propertyReplacer.reset();
|
||||
}
|
||||
|
||||
function testOpera() {
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.OPERA_10);
|
||||
assertTrue(goog.labs.userAgent.browser.isOpera());
|
||||
assertVersion('10.00');
|
||||
assertVersionBetween('10.00', '10.10');
|
||||
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.OPERA_MAC);
|
||||
assertTrue(goog.labs.userAgent.browser.isOpera());
|
||||
assertVersion('11.52');
|
||||
assertVersionBetween('11.50', '12.00');
|
||||
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.OPERA_LINUX);
|
||||
assertTrue(goog.labs.userAgent.browser.isOpera());
|
||||
assertVersion('11.50');
|
||||
assertVersionBetween('11.00', '12.00');
|
||||
}
|
||||
|
||||
function testIE() {
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.IE_6);
|
||||
assertTrue(goog.labs.userAgent.browser.isIE());
|
||||
assertVersion('6.0');
|
||||
assertVersionBetween('5.0', '7.0');
|
||||
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.IE_10);
|
||||
assertTrue(goog.labs.userAgent.browser.isIE());
|
||||
assertVersion('10.6');
|
||||
assertVersionBetween('10.0', '11.0');
|
||||
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.IE_9);
|
||||
assertTrue(goog.labs.userAgent.browser.isIE());
|
||||
assertVersion('9.0');
|
||||
assertVersionBetween('8.0', '10.0');
|
||||
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.IE_8);
|
||||
assertTrue(goog.labs.userAgent.browser.isIE());
|
||||
assertVersion('8.0');
|
||||
assertVersionBetween('7.0', '9.0');
|
||||
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.IE_8_COMPATIBILITY);
|
||||
// Test Document mode override
|
||||
setDocumentMode('9');
|
||||
assertTrue(goog.labs.userAgent.browser.isIE());
|
||||
assertVersion('9');
|
||||
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.IE_9_COMPATIBILITY);
|
||||
setDocumentMode('9');
|
||||
assertTrue(goog.labs.userAgent.browser.isIE());
|
||||
assertVersion('9');
|
||||
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.IE_9_COMPATIBILITY);
|
||||
setDocumentMode('8');
|
||||
assertTrue(goog.labs.userAgent.browser.isIE());
|
||||
assertVersion('8');
|
||||
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.IE_11);
|
||||
assertTrue(goog.labs.userAgent.browser.isIE());
|
||||
assertVersion('11.0');
|
||||
assertVersionBetween('10.0', '12.0');
|
||||
}
|
||||
|
||||
function testFirefox() {
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.FIREFOX_19);
|
||||
assertTrue(goog.labs.userAgent.browser.isFirefox());
|
||||
assertVersion('19.0');
|
||||
assertVersionBetween('18.0', '20.0');
|
||||
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.FIREFOX_WINDOWS);
|
||||
assertTrue(goog.labs.userAgent.browser.isFirefox());
|
||||
assertVersion('14.0.1');
|
||||
assertVersionBetween('14.0', '15.0');
|
||||
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.FIREFOX_LINUX);
|
||||
assertTrue(goog.labs.userAgent.browser.isFirefox());
|
||||
assertVersion('15.0.1');
|
||||
}
|
||||
|
||||
function testChrome() {
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.CHROME_ANDROID);
|
||||
assertTrue(goog.labs.userAgent.browser.isChrome());
|
||||
assertVersion('18.0.1025.133');
|
||||
assertVersionBetween('18.0', '19.0');
|
||||
assertVersionBetween('17.0', '18.1');
|
||||
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.CHROME_IPHONE);
|
||||
assertTrue(goog.labs.userAgent.browser.isChrome());
|
||||
assertVersion('22.0.1194.0');
|
||||
assertVersionBetween('22.0', '23.0');
|
||||
assertVersionBetween('22.0', '22.10');
|
||||
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.CHROME_MAC);
|
||||
assertTrue(goog.labs.userAgent.browser.isChrome());
|
||||
assertVersion('24.0.1309.0');
|
||||
assertVersionBetween('24.0', '25.0');
|
||||
assertVersionBetween('24.0', '24.10');
|
||||
}
|
||||
|
||||
function testSafari() {
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.IPAD_6);
|
||||
assertTrue(goog.labs.userAgent.browser.isSafari());
|
||||
assertVersion('6.0');
|
||||
assertVersionBetween('5.1', '7.0');
|
||||
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.SAFARI_6);
|
||||
assertTrue(goog.labs.userAgent.browser.isSafari());
|
||||
assertVersion('6.0');
|
||||
assertVersionBetween('6.0', '7.0');
|
||||
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.SAFARI_IPHONE);
|
||||
assertTrue(goog.labs.userAgent.browser.isSafari());
|
||||
assertVersion('5.0.2');
|
||||
assertVersionBetween('5.0', '6.0');
|
||||
}
|
||||
|
||||
function testAndroidBrowser() {
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.ANDROID_BROWSER_235);
|
||||
assertTrue(goog.labs.userAgent.browser.isAndroidBrowser());
|
||||
assertVersion('4.0');
|
||||
assertVersionBetween('3.0', '5.0');
|
||||
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.ANDROID_BROWSER_403);
|
||||
assertTrue(goog.labs.userAgent.browser.isAndroidBrowser());
|
||||
assertVersion('4.0');
|
||||
assertVersionBetween('3.0', '5.0');
|
||||
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.ANDROID_BROWSER_233);
|
||||
assertTrue(goog.labs.userAgent.browser.isAndroidBrowser());
|
||||
assertVersion('4.0');
|
||||
assertVersionBetween('3.0', '5.0');
|
||||
}
|
||||
|
||||
function setGlobalUAString(uaString) {
|
||||
var mockGlobal = {
|
||||
'navigator': {
|
||||
'userAgent': uaString
|
||||
}
|
||||
};
|
||||
propertyReplacer.set(goog, 'global', mockGlobal);
|
||||
}
|
||||
|
||||
function setDocumentMode(docMode) {
|
||||
var mockDocument = {
|
||||
'documentMode': docMode
|
||||
};
|
||||
propertyReplacer.set(goog.global, 'document', mockDocument);
|
||||
}
|
||||
|
||||
function assertVersion(version) {
|
||||
assertEquals(version, goog.labs.userAgent.browser.getVersion());
|
||||
}
|
||||
|
||||
function assertVersionBetween(lowVersion, highVersion) {
|
||||
assertTrue(goog.labs.userAgent.browser.isVersionOrHigher(lowVersion));
|
||||
assertFalse(goog.labs.userAgent.browser.isVersionOrHigher(highVersion));
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// 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 Closure user device detection (based on user agent).
|
||||
* @see http://en.wikipedia.org/wiki/User_agent
|
||||
* For more information on browser brand, platform, or engine see the other
|
||||
* sub-namespaces in goog.labs.userAgent (browser, platform, and engine).
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.userAgent.device');
|
||||
|
||||
goog.require('goog.labs.userAgent.util');
|
||||
|
||||
|
||||
/**
|
||||
* Currently we detect the iPhone, iPod and Android mobiles (devices that have
|
||||
* both Android and Mobile in the user agent string).
|
||||
*
|
||||
* @return {boolean} Whether the user is using a tablet.
|
||||
*/
|
||||
goog.labs.userAgent.device.isMobile = goog.memoize(function() {
|
||||
return !goog.labs.userAgent.device.isTablet() &&
|
||||
(goog.labs.userAgent.util.matchUserAgent('iPod') ||
|
||||
goog.labs.userAgent.util.matchUserAgent('iPhone') ||
|
||||
goog.labs.userAgent.util.matchUserAgent('Android'));
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Currently we detect Kindle Fire, iPad, and Android tablets (devices that have
|
||||
* Android but not Mobile in the user agent string).
|
||||
*
|
||||
* @return {boolean} Whether the user is using a tablet.
|
||||
*/
|
||||
goog.labs.userAgent.device.isTablet = goog.memoize(function() {
|
||||
return goog.labs.userAgent.util.matchUserAgent('iPad') ||
|
||||
(goog.labs.userAgent.util.matchUserAgent('Android') &&
|
||||
!goog.labs.userAgent.util.matchUserAgent('Mobile')) ||
|
||||
goog.labs.userAgent.util.matchUserAgent('Silk');
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Currently we detect Kindle Fire, iPad, and Android tablets.
|
||||
*
|
||||
* @return {boolean} Whether the user is using a tablet.
|
||||
*/
|
||||
goog.labs.userAgent.device.isDesktop = goog.memoize(function() {
|
||||
return !goog.labs.userAgent.device.isMobile() &&
|
||||
!goog.labs.userAgent.device.isTablet();
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
// 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 Unit tests for goog.labs.userAgent.device.
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.userAgent.deviceTest');
|
||||
|
||||
goog.require('goog.labs.userAgent.device');
|
||||
goog.require('goog.labs.userAgent.testAgents');
|
||||
goog.require('goog.testing.PropertyReplacer');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
goog.setTestOnly('goog.labs.userAgent.deviceTest');
|
||||
|
||||
var propertyReplacer = new goog.testing.PropertyReplacer();
|
||||
|
||||
function setUp() {
|
||||
// disable memoization
|
||||
propertyReplacer.set(goog.memoize, 'ENABLE_MEMOIZE', false);
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
propertyReplacer.reset();
|
||||
}
|
||||
|
||||
function setGlobalUAString(uaString) {
|
||||
var mockGlobal = {
|
||||
'navigator': {
|
||||
'userAgent': uaString
|
||||
}
|
||||
};
|
||||
propertyReplacer.set(goog, 'global', mockGlobal);
|
||||
}
|
||||
|
||||
function testMobile() {
|
||||
assertIsMobile(goog.labs.userAgent.testAgents.ANDROID_BROWSER_235);
|
||||
assertIsMobile(goog.labs.userAgent.testAgents.CHROME_ANDROID);
|
||||
assertIsMobile(goog.labs.userAgent.testAgents.SAFARI_IPHONE);
|
||||
}
|
||||
|
||||
function testTablet() {
|
||||
assertIsTablet(goog.labs.userAgent.testAgents.CHROME_ANDROID_TABLET);
|
||||
assertIsTablet(goog.labs.userAgent.testAgents.KINDLE_FIRE);
|
||||
assertIsTablet(goog.labs.userAgent.testAgents.IPAD_6);
|
||||
}
|
||||
|
||||
function testDesktop() {
|
||||
assertIsDesktop(goog.labs.userAgent.testAgents.CHROME_25);
|
||||
assertIsDesktop(goog.labs.userAgent.testAgents.OPERA_10);
|
||||
assertIsDesktop(goog.labs.userAgent.testAgents.FIREFOX_19);
|
||||
assertIsDesktop(goog.labs.userAgent.testAgents.IE_9);
|
||||
assertIsDesktop(goog.labs.userAgent.testAgents.IE_10);
|
||||
assertIsDesktop(goog.labs.userAgent.testAgents.IE_11);
|
||||
}
|
||||
|
||||
function assertIsMobile(uaString) {
|
||||
setGlobalUAString(uaString);
|
||||
assertTrue(goog.labs.userAgent.device.isMobile());
|
||||
assertFalse(goog.labs.userAgent.device.isTablet());
|
||||
assertFalse(goog.labs.userAgent.device.isDesktop());
|
||||
}
|
||||
|
||||
function assertIsTablet(uaString) {
|
||||
setGlobalUAString(uaString);
|
||||
assertTrue(goog.labs.userAgent.device.isTablet());
|
||||
assertFalse(goog.labs.userAgent.device.isMobile());
|
||||
assertFalse(goog.labs.userAgent.device.isDesktop());
|
||||
}
|
||||
|
||||
function assertIsDesktop(uaString) {
|
||||
setGlobalUAString(uaString);
|
||||
assertTrue(goog.labs.userAgent.device.isDesktop());
|
||||
assertFalse(goog.labs.userAgent.device.isMobile());
|
||||
assertFalse(goog.labs.userAgent.device.isTablet());
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
// 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 Closure user agent detection.
|
||||
* @see http://en.wikipedia.org/wiki/User_agent
|
||||
* For more information on browser brand, platform, or device see the other
|
||||
* sub-namespaces in goog.labs.userAgent (browser, platform, and device).
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.userAgent.engine');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.labs.userAgent.util');
|
||||
goog.require('goog.memoize');
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
/**
|
||||
* Returns the user agent string.
|
||||
*
|
||||
* @return {?string} The user agent string.
|
||||
*/
|
||||
goog.labs.userAgent.engine.getUserAgentString = goog.memoize(function() {
|
||||
return goog.global['navigator'] ? goog.global['navigator'].userAgent : null;
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @return {boolean} Whether the user agent contains the given string.
|
||||
* @private
|
||||
*/
|
||||
goog.labs.userAgent.engine.matchUserAgent_ = function(str) {
|
||||
var userAgentString = goog.labs.userAgent.engine.getUserAgentString();
|
||||
return Boolean(userAgentString && goog.string.contains(userAgentString, str));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the rendering engine is Presto.
|
||||
*/
|
||||
goog.labs.userAgent.engine.isPresto = goog.memoize(
|
||||
goog.partial(goog.labs.userAgent.engine.matchUserAgent_, 'Presto'));
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the rendering engine is Trident.
|
||||
*/
|
||||
goog.labs.userAgent.engine.isTrident = goog.memoize(
|
||||
goog.partial(goog.labs.userAgent.engine.matchUserAgent_, 'Trident'));
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the rendering engine is WebKit.
|
||||
*/
|
||||
goog.labs.userAgent.engine.isWebKit = goog.memoize(
|
||||
goog.partial(goog.labs.userAgent.engine.matchUserAgent_, 'WebKit'));
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the rendering engine is Gecko.
|
||||
*/
|
||||
goog.labs.userAgent.engine.isGecko = goog.memoize(
|
||||
goog.partial(goog.labs.userAgent.engine.matchUserAgent_, 'Gecko'));
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} The rendering engine's version or empty string if version
|
||||
* can't be determined.
|
||||
*/
|
||||
goog.labs.userAgent.engine.getVersion = goog.memoize(function() {
|
||||
var userAgentString = goog.labs.userAgent.engine.getUserAgentString();
|
||||
|
||||
if (userAgentString) {
|
||||
var tuples = goog.labs.userAgent.util.extractVersionTuples(userAgentString);
|
||||
|
||||
var engineTuple = tuples[1];
|
||||
if (engineTuple) {
|
||||
// In Gecko, the version string is either in the browser info or the
|
||||
// Firefox version. See Gecko user agent string reference:
|
||||
// http://goo.gl/mULqa
|
||||
if (engineTuple[0] == 'Gecko') {
|
||||
return goog.labs.userAgent.engine.getVersionForKey_(tuples, 'Firefox');
|
||||
}
|
||||
|
||||
return engineTuple[1];
|
||||
}
|
||||
|
||||
// IE has only one version identifier, and the Trident version is
|
||||
// specified in the parenthetical.
|
||||
var browserTuple = tuples[0];
|
||||
var info;
|
||||
if (browserTuple && (info = browserTuple[2])) {
|
||||
var match = /Trident\/([^\s;]+)/.exec(info);
|
||||
if (match) {
|
||||
return match[1];
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @param {string|number} version The version to check.
|
||||
* @return {boolean} Whether the rendering engine version is higher or the same
|
||||
* as the given version.
|
||||
*/
|
||||
goog.labs.userAgent.engine.isVersionOrHigher = function(version) {
|
||||
return goog.string.compareVersions(goog.labs.userAgent.engine.getVersion(),
|
||||
version) >= 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!Array.<string>} tuples Version tuples.
|
||||
* @param {string} key The key to look for.
|
||||
* @return {string} The version string of the given key, if present.
|
||||
* Otherwise, the empty string.
|
||||
* @private
|
||||
*/
|
||||
goog.labs.userAgent.engine.getVersionForKey_ = function(tuples, key) {
|
||||
// TODO(nnaze): Move to util if useful elsewhere.
|
||||
|
||||
var pair = goog.array.find(tuples, function(pair) {
|
||||
return key == pair[0];
|
||||
});
|
||||
|
||||
return pair && pair[1] || '';
|
||||
};
|
||||
@@ -0,0 +1,164 @@
|
||||
// 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 Unit tests for goog.labs.userAgent.engine.
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.userAgent.engineTest');
|
||||
|
||||
goog.require('goog.labs.userAgent.engine');
|
||||
goog.require('goog.labs.userAgent.testAgents');
|
||||
goog.require('goog.testing.PropertyReplacer');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
goog.setTestOnly('goog.labs.userAgent.engineTest');
|
||||
|
||||
var propertyReplacer = new goog.testing.PropertyReplacer();
|
||||
|
||||
function setUp() {
|
||||
// disable memoization
|
||||
propertyReplacer.set(goog.memoize, 'ENABLE_MEMOIZE', false);
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
propertyReplacer.reset();
|
||||
}
|
||||
|
||||
function setGlobalUAString(uaString) {
|
||||
var mockGlobal = {
|
||||
'navigator': {
|
||||
'userAgent': uaString
|
||||
}
|
||||
};
|
||||
propertyReplacer.set(goog, 'global', mockGlobal);
|
||||
}
|
||||
|
||||
function assertVersion(version) {
|
||||
assertEquals(version, goog.labs.userAgent.engine.getVersion());
|
||||
}
|
||||
|
||||
function assertLowAndHighVersions(lowVersion, highVersion) {
|
||||
assertTrue(goog.labs.userAgent.engine.isVersionOrHigher(lowVersion));
|
||||
assertFalse(goog.labs.userAgent.engine.isVersionOrHigher(highVersion));
|
||||
}
|
||||
|
||||
function testPresto() {
|
||||
setGlobalUAString(
|
||||
'Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00');
|
||||
assertTrue(goog.labs.userAgent.engine.isPresto());
|
||||
assertVersion('2.9.181');
|
||||
assertLowAndHighVersions('2.9', '2.10');
|
||||
|
||||
setGlobalUAString(
|
||||
'Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168' +
|
||||
' Version/11.52');
|
||||
assertTrue(goog.labs.userAgent.engine.isPresto());
|
||||
assertVersion('2.9.168');
|
||||
assertLowAndHighVersions('2.9', '2.10');
|
||||
|
||||
setGlobalUAString(
|
||||
'Opera/9.80 (X11; Linux i686; U; ru) Presto/2.8.131 Version/11.11');
|
||||
assertTrue(goog.labs.userAgent.engine.isPresto());
|
||||
assertVersion('2.8.131');
|
||||
assertLowAndHighVersions('2.8', '2.9');
|
||||
}
|
||||
|
||||
function testTrident() {
|
||||
setGlobalUAString(
|
||||
'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; ' +
|
||||
'WOW64; Trident/6.0)');
|
||||
assertTrue(goog.labs.userAgent.engine.isTrident());
|
||||
assertVersion('6.0');
|
||||
assertLowAndHighVersions('6.0', '7.0');
|
||||
|
||||
setGlobalUAString(
|
||||
'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; ' +
|
||||
'Trident/4.0; SLCC2; Media Center PC 6.0; InfoPath.2; MS-RTC LM 8)');
|
||||
assertTrue(goog.labs.userAgent.engine.isTrident());
|
||||
assertVersion('4.0');
|
||||
assertLowAndHighVersions('4.0', '5.0');
|
||||
|
||||
setGlobalUAString(
|
||||
'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)');
|
||||
assertTrue(goog.labs.userAgent.engine.isTrident());
|
||||
assertVersion('5.0');
|
||||
assertLowAndHighVersions('5.0', '6.0');
|
||||
|
||||
setGlobalUAString(goog.labs.userAgent.testAgents.IE_11);
|
||||
assertTrue(goog.labs.userAgent.engine.isTrident());
|
||||
assertVersion('7.0');
|
||||
assertLowAndHighVersions('6.0', '8.0');
|
||||
}
|
||||
|
||||
function testWebKit() {
|
||||
setGlobalUAString(
|
||||
'Mozilla/5.0 (Linux; U; Android 2.3.5; en-us; HTC Vision Build/GRI40)' +
|
||||
'AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1');
|
||||
assertTrue(goog.labs.userAgent.engine.isWebKit());
|
||||
assertVersion('533.1');
|
||||
assertLowAndHighVersions('533.0', '534.0');
|
||||
|
||||
setGlobalUAString(
|
||||
'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) ' +
|
||||
'AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.370.0 Safari/533.4');
|
||||
assertTrue(goog.labs.userAgent.engine.isWebKit());
|
||||
assertVersion('533.4');
|
||||
assertLowAndHighVersions('533.0', '534.0');
|
||||
|
||||
setGlobalUAString(
|
||||
'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) ' +
|
||||
'AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.370.0 Safari/533.4');
|
||||
assertTrue(goog.labs.userAgent.engine.isWebKit());
|
||||
assertVersion('533.4');
|
||||
assertLowAndHighVersions('533.0', '534.0');
|
||||
|
||||
setGlobalUAString(
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 ' +
|
||||
'(KHTML, like Gecko) Version/5.1.3 Safari/534.53.10');
|
||||
assertTrue(goog.labs.userAgent.engine.isWebKit());
|
||||
assertVersion('534.55.3');
|
||||
assertLowAndHighVersions('534.0', '535.0');
|
||||
|
||||
setGlobalUAString(
|
||||
'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.15 ' +
|
||||
'(KHTML, like Gecko) Chrome/24.0.1295.0 Safari/537.15');
|
||||
assertTrue(goog.labs.userAgent.engine.isWebKit());
|
||||
assertVersion('537.15');
|
||||
assertLowAndHighVersions('537.0', '538.0');
|
||||
}
|
||||
|
||||
function testGecko() {
|
||||
setGlobalUAString(
|
||||
'Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20120716 Firefox/15.0a2');
|
||||
assertTrue(goog.labs.userAgent.engine.isGecko());
|
||||
assertVersion('15.0a2');
|
||||
assertLowAndHighVersions('14.0', '16.0');
|
||||
// This is actually not at V15 because it is alpha 2
|
||||
assertFalse(goog.labs.userAgent.engine.isVersionOrHigher('15'));
|
||||
|
||||
setGlobalUAString(
|
||||
'Mozilla/6.0 (Windows NT 6.2; WOW64; rv:16.0.1) Gecko/20121011 ' +
|
||||
'Firefox/16.0.1');
|
||||
assertTrue(goog.labs.userAgent.engine.isGecko());
|
||||
assertVersion('16.0.1');
|
||||
assertLowAndHighVersions('16.0', '17.0');
|
||||
|
||||
setGlobalUAString('Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) ' +
|
||||
'Gecko/20100101 Firefox/14.0.1');
|
||||
assertTrue(goog.labs.userAgent.engine.isGecko());
|
||||
assertVersion('14.0.1');
|
||||
assertLowAndHighVersions('14.0', '15.0');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
// 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 Closure user agent platform detection.
|
||||
* @see <a href="http://www.useragentstring.com/">User agent strings</a>
|
||||
* For more information on browser brand, rendering engine, or device see the
|
||||
* other sub-namespaces in goog.labs.userAgent (browser, engine, and device
|
||||
* respectively).
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.userAgent.platform');
|
||||
|
||||
goog.require('goog.labs.userAgent.util');
|
||||
goog.require('goog.memoize');
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
/**
|
||||
* Returns the platform string.
|
||||
*
|
||||
* @return {string} The platform string.
|
||||
*/
|
||||
goog.labs.userAgent.platform.getPlatformString = goog.memoize(function() {
|
||||
return goog.global['navigator'] && goog.global['navigator'].platform ?
|
||||
goog.global['navigator'].platform : '';
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Returns the appVersion string.
|
||||
*
|
||||
* @return {string} The appVersion string.
|
||||
*/
|
||||
goog.labs.userAgent.platform.getAppVersion = goog.memoize(function() {
|
||||
return goog.global['navigator'] && goog.global['navigator'].appVersion ?
|
||||
goog.global['navigator'].appVersion : '';
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @return {boolean} Whether the platform contains the given string.
|
||||
* @private
|
||||
*/
|
||||
goog.labs.userAgent.platform.matchPlatform_ = function(str) {
|
||||
var platformString = goog.labs.userAgent.platform.getPlatformString();
|
||||
return goog.string.contains(platformString, str);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the platform is Android.
|
||||
*/
|
||||
goog.labs.userAgent.platform.isAndroid = goog.memoize(
|
||||
goog.partial(goog.labs.userAgent.util.matchUserAgent, 'Android'));
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the platform is iPod.
|
||||
*/
|
||||
goog.labs.userAgent.platform.isIpod = goog.memoize(
|
||||
goog.partial(goog.labs.userAgent.util.matchUserAgent, 'iPod'));
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the platform is iPhone.
|
||||
*/
|
||||
goog.labs.userAgent.platform.isIphone = goog.memoize(function() {
|
||||
return goog.labs.userAgent.util.matchUserAgent('iPhone') &&
|
||||
!goog.labs.userAgent.util.matchUserAgent('iPod');
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the platform is iPad.
|
||||
*/
|
||||
goog.labs.userAgent.platform.isIpad = goog.memoize(
|
||||
goog.partial(goog.labs.userAgent.util.matchUserAgent, 'iPad'));
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the platform is iOS.
|
||||
*/
|
||||
goog.labs.userAgent.platform.isIos = goog.memoize(function() {
|
||||
return goog.labs.userAgent.platform.isIphone() ||
|
||||
goog.labs.userAgent.platform.isIpad() ||
|
||||
goog.labs.userAgent.platform.isIpod();
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the platform is Mac.
|
||||
*/
|
||||
goog.labs.userAgent.platform.isMac = goog.memoize(
|
||||
goog.partial(goog.labs.userAgent.platform.matchPlatform_, 'Mac'));
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the platform is Linux.
|
||||
*/
|
||||
goog.labs.userAgent.platform.isLinux = goog.memoize(
|
||||
goog.partial(goog.labs.userAgent.platform.matchPlatform_, 'Linux'));
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the platform is Windows.
|
||||
*/
|
||||
goog.labs.userAgent.platform.isWindows = goog.memoize(
|
||||
goog.partial(goog.labs.userAgent.platform.matchPlatform_, 'Win'));
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the platform is X11.
|
||||
*/
|
||||
goog.labs.userAgent.platform.isX11 = goog.memoize(function() {
|
||||
var appVersion = goog.labs.userAgent.platform.getAppVersion();
|
||||
return goog.string.contains(appVersion, 'X11');
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the platform is ChromeOS.
|
||||
*/
|
||||
goog.labs.userAgent.platform.isChromeOS = goog.memoize(
|
||||
goog.partial(goog.labs.userAgent.util.matchUserAgent, 'CrOS'));
|
||||
|
||||
|
||||
/**
|
||||
* The version of the platform. We only determine the version for Windows,
|
||||
* Mac, and Chrome OS. It doesn't make much sense on Linux. For Windows, we only
|
||||
* look at the NT version. Non-NT-based versions (e.g. 95, 98, etc.) are given
|
||||
* version 0.0.
|
||||
*
|
||||
* @return {string} The platform version or empty string if version cannot be
|
||||
* determined.
|
||||
*/
|
||||
goog.labs.userAgent.platform.getVersion = function() {
|
||||
var userAgentString = goog.labs.userAgent.util.getUserAgentString();
|
||||
var version = '', re;
|
||||
if (goog.labs.userAgent.platform.isWindows()) {
|
||||
re = /Windows NT ([0-9.]+)/;
|
||||
var match = re.exec(userAgentString);
|
||||
if (match) {
|
||||
version = match[1];
|
||||
} else {
|
||||
version = '0.0';
|
||||
}
|
||||
} else if (goog.labs.userAgent.platform.isMac()) {
|
||||
re = /Mac OS X ([0-9_.]+)/;
|
||||
var match = re.exec(userAgentString);
|
||||
// Note: some old versions of Camino do not report an OSX version.
|
||||
// Default to 10.
|
||||
version = match ? match[1].replace(/_/g, '.') : '10';
|
||||
} else if (goog.labs.userAgent.platform.isAndroid()) {
|
||||
re = /Android\s+([^\);]+)(\)|;)/;
|
||||
var match = re.exec(userAgentString);
|
||||
version = match && match[1];
|
||||
} else if (goog.labs.userAgent.platform.isIos()) {
|
||||
re = /(?:iPhone|iPod|iPad|CPU)\s+OS\s+(\S+)/;
|
||||
var match = re.exec(userAgentString);
|
||||
// Report the version as x.y.z and not x_y_z
|
||||
version = match && match[1].replace(/_/g, '.');
|
||||
} else if (goog.labs.userAgent.platform.isChromeOS()) {
|
||||
re = /(?:CrOS\s+(?:i686|x86_64)\s+([0-9.]+))/;
|
||||
var match = re.exec(userAgentString);
|
||||
version = match && match[1];
|
||||
}
|
||||
return version || '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string|number} version The version to check.
|
||||
* @return {boolean} Whether the browser version is higher or the same as the
|
||||
* given version.
|
||||
*/
|
||||
goog.labs.userAgent.platform.isVersionOrHigher = function(version) {
|
||||
return goog.string.compareVersions(goog.labs.userAgent.platform.getVersion(),
|
||||
version) >= 0;
|
||||
};
|
||||
@@ -0,0 +1,256 @@
|
||||
// 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 Unit tests for goog.labs.userAgent.platform.
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.userAgent.platformTest');
|
||||
|
||||
goog.require('goog.labs.userAgent.platform');
|
||||
goog.require('goog.labs.userAgent.testAgents');
|
||||
goog.require('goog.testing.PropertyReplacer');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
goog.setTestOnly('goog.labs.userAgent.platformTest');
|
||||
|
||||
var propertyReplacer = new goog.testing.PropertyReplacer();
|
||||
|
||||
function setUp() {
|
||||
// disable memoization
|
||||
propertyReplacer.set(goog.memoize, 'ENABLE_MEMOIZE', false);
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
propertyReplacer.reset();
|
||||
}
|
||||
|
||||
function setGlobalUAString(uaString, platform, appVersion) {
|
||||
var mockGlobal = {
|
||||
'navigator': {
|
||||
'userAgent': uaString,
|
||||
'platform': platform,
|
||||
'appVersion': appVersion
|
||||
}
|
||||
};
|
||||
propertyReplacer.set(goog, 'global', mockGlobal);
|
||||
}
|
||||
|
||||
function testAndroid() {
|
||||
var uaString = goog.labs.userAgent.testAgents.ANDROID_BROWSER_233;
|
||||
|
||||
setGlobalUAString(uaString);
|
||||
assertTrue(goog.labs.userAgent.platform.isAndroid());
|
||||
assertVersion('2.3.3');
|
||||
assertVersionBetween('2.3.0', '2.3.5');
|
||||
assertVersionBetween('2.3', '2.4');
|
||||
assertVersionBetween('2', '3');
|
||||
|
||||
uaString = goog.labs.userAgent.testAgents.ANDROID_BROWSER_221;
|
||||
|
||||
setGlobalUAString(uaString);
|
||||
assertTrue(goog.labs.userAgent.platform.isAndroid());
|
||||
assertVersion('2.2.1');
|
||||
assertVersionBetween('2.2.0', '2.2.5');
|
||||
assertVersionBetween('2.2', '2.3');
|
||||
assertVersionBetween('2', '3');
|
||||
|
||||
uaString = goog.labs.userAgent.testAgents.CHROME_ANDROID;
|
||||
|
||||
setGlobalUAString(uaString);
|
||||
assertTrue(goog.labs.userAgent.platform.isAndroid());
|
||||
assertVersion('4.0.2');
|
||||
assertVersionBetween('4.0.0', '4.1.0');
|
||||
assertVersionBetween('4.0', '4.1');
|
||||
assertVersionBetween('4', '5');
|
||||
}
|
||||
|
||||
function testIpod() {
|
||||
var uaString = goog.labs.userAgent.testAgents.SAFARI_IPOD;
|
||||
|
||||
setGlobalUAString(uaString);
|
||||
assertTrue(goog.labs.userAgent.platform.isIpod());
|
||||
assertTrue(goog.labs.userAgent.platform.isIos());
|
||||
assertVersion('');
|
||||
}
|
||||
|
||||
function testIphone() {
|
||||
var uaString = goog.labs.userAgent.testAgents.SAFARI_IPHONE;
|
||||
setGlobalUAString(uaString);
|
||||
assertTrue(goog.labs.userAgent.platform.isIphone());
|
||||
assertTrue(goog.labs.userAgent.platform.isIos());
|
||||
assertVersion('4.2.1');
|
||||
assertVersionBetween('4', '5');
|
||||
assertVersionBetween('4.2', '4.3');
|
||||
|
||||
uaString = goog.labs.userAgent.testAgents.IPHONE_6;
|
||||
setGlobalUAString(uaString);
|
||||
assertTrue(goog.labs.userAgent.platform.isIphone());
|
||||
assertTrue(goog.labs.userAgent.platform.isIos());
|
||||
assertVersion('6.0');
|
||||
assertVersionBetween('5', '7');
|
||||
|
||||
uaString = goog.labs.userAgent.testAgents.SAFARI_IPHONE_32;
|
||||
setGlobalUAString(uaString);
|
||||
assertTrue(goog.labs.userAgent.platform.isIphone());
|
||||
assertTrue(goog.labs.userAgent.platform.isIos());
|
||||
assertVersion('3.2');
|
||||
assertVersionBetween('3', '4');
|
||||
}
|
||||
|
||||
function testIpad() {
|
||||
var uaString = goog.labs.userAgent.testAgents.IPAD_4;
|
||||
|
||||
setGlobalUAString(uaString);
|
||||
assertTrue(goog.labs.userAgent.platform.isIpad());
|
||||
assertTrue(goog.labs.userAgent.platform.isIos());
|
||||
assertVersion('3.2');
|
||||
assertVersionBetween('3', '4');
|
||||
assertVersionBetween('3.1', '4');
|
||||
|
||||
uaString = goog.labs.userAgent.testAgents.IPAD_5;
|
||||
|
||||
setGlobalUAString(uaString);
|
||||
assertTrue(goog.labs.userAgent.platform.isIpad());
|
||||
assertTrue(goog.labs.userAgent.platform.isIos());
|
||||
assertVersion('5.1');
|
||||
assertVersionBetween('5', '6');
|
||||
|
||||
uaString = goog.labs.userAgent.testAgents.IPAD_6;
|
||||
|
||||
setGlobalUAString(uaString);
|
||||
assertTrue(goog.labs.userAgent.platform.isIpad());
|
||||
assertTrue(goog.labs.userAgent.platform.isIos());
|
||||
assertVersion('6.0');
|
||||
assertVersionBetween('5', '7');
|
||||
}
|
||||
|
||||
function testMac() {
|
||||
var uaString = goog.labs.userAgent.testAgents.CHROME_MAC;
|
||||
var platform = 'IntelMac';
|
||||
setGlobalUAString(uaString, platform);
|
||||
assertTrue(goog.labs.userAgent.platform.isMac());
|
||||
assertVersion('10.8.2');
|
||||
assertVersionBetween('10', '11');
|
||||
assertVersionBetween('10.8', '10.9');
|
||||
assertVersionBetween('10.8.1', '10.8.3');
|
||||
|
||||
uaString = goog.labs.userAgent.testAgents.OPERA_MAC;
|
||||
setGlobalUAString(uaString, platform);
|
||||
assertTrue(goog.labs.userAgent.platform.isMac());
|
||||
assertVersion('10.6.8');
|
||||
assertVersionBetween('10', '11');
|
||||
assertVersionBetween('10.6', '10.7');
|
||||
assertVersionBetween('10.6.5', '10.7.0');
|
||||
|
||||
uaString = goog.labs.userAgent.testAgents.SAFARI_MAC;
|
||||
setGlobalUAString(uaString, platform);
|
||||
assertTrue(goog.labs.userAgent.platform.isMac());
|
||||
assertVersionBetween('10', '11');
|
||||
assertVersionBetween('10.6', '10.7');
|
||||
assertVersionBetween('10.6.5', '10.7.0');
|
||||
|
||||
uaString = goog.labs.userAgent.testAgents.FIREFOX_MAC;
|
||||
setGlobalUAString(uaString, platform);
|
||||
assertTrue(goog.labs.userAgent.platform.isMac());
|
||||
assertVersion('11.7.9');
|
||||
assertVersionBetween('11', '12');
|
||||
assertVersionBetween('11.7', '11.8');
|
||||
assertVersionBetween('11.7.9', '11.8.0');
|
||||
}
|
||||
|
||||
function testLinux() {
|
||||
var uaString = goog.labs.userAgent.testAgents.FIREFOX_LINUX;
|
||||
var platform = 'Linux';
|
||||
setGlobalUAString(uaString, platform);
|
||||
assertTrue(goog.labs.userAgent.platform.isLinux());
|
||||
assertVersion('');
|
||||
|
||||
uaString = goog.labs.userAgent.testAgents.CHROME_LINUX;
|
||||
setGlobalUAString(uaString, platform);
|
||||
assertTrue(goog.labs.userAgent.platform.isLinux());
|
||||
assertVersion('');
|
||||
|
||||
uaString = goog.labs.userAgent.testAgents.OPERA_LINUX;
|
||||
setGlobalUAString(uaString, platform);
|
||||
assertTrue(goog.labs.userAgent.platform.isLinux());
|
||||
assertVersion('');
|
||||
}
|
||||
|
||||
function testWindows() {
|
||||
var uaString = goog.labs.userAgent.testAgents.SAFARI_WINDOWS;
|
||||
var platform = 'Win32';
|
||||
setGlobalUAString(uaString, platform);
|
||||
assertTrue(goog.labs.userAgent.platform.isWindows());
|
||||
assertVersion('6.1');
|
||||
assertVersionBetween('6', '7');
|
||||
|
||||
uaString = goog.labs.userAgent.testAgents.IE_10;
|
||||
setGlobalUAString(uaString, platform);
|
||||
assertTrue(goog.labs.userAgent.platform.isWindows());
|
||||
assertVersion('6.1');
|
||||
assertVersionBetween('6', '6.5');
|
||||
|
||||
uaString = goog.labs.userAgent.testAgents.CHROME_25;
|
||||
setGlobalUAString(uaString, platform);
|
||||
assertTrue(goog.labs.userAgent.platform.isWindows());
|
||||
assertVersion('5.1');
|
||||
assertVersionBetween('5', '6');
|
||||
|
||||
uaString = goog.labs.userAgent.testAgents.FIREFOX_WINDOWS;
|
||||
setGlobalUAString(uaString, platform);
|
||||
assertTrue(goog.labs.userAgent.platform.isWindows());
|
||||
assertVersion('6.1');
|
||||
assertVersionBetween('6', '7');
|
||||
|
||||
uaString = goog.labs.userAgent.testAgents.IE_11;
|
||||
setGlobalUAString(uaString, platform);
|
||||
assertTrue(goog.labs.userAgent.platform.isWindows());
|
||||
assertVersion('6.3');
|
||||
assertVersionBetween('6', '6.5');
|
||||
}
|
||||
|
||||
function testX11() {
|
||||
var uaString = goog.labs.userAgent.testAgents.CHROME_LINUX;
|
||||
var platform = 'Linux';
|
||||
var appVersion = goog.labs.userAgent.testAgents.CHROME_LINUX_APPVERVERSION;
|
||||
setGlobalUAString(uaString, platform, appVersion);
|
||||
assertTrue(goog.labs.userAgent.platform.isX11());
|
||||
assertVersion('');
|
||||
}
|
||||
|
||||
function testChromeOS() {
|
||||
var uaString = goog.labs.userAgent.testAgents.CHROME_OS_910;
|
||||
|
||||
setGlobalUAString(uaString);
|
||||
assertTrue(goog.labs.userAgent.platform.isChromeOS());
|
||||
assertVersion('9.10.0');
|
||||
assertVersionBetween('9', '10');
|
||||
|
||||
uaString = goog.labs.userAgent.testAgents.CHROME_OS;
|
||||
|
||||
setGlobalUAString(uaString);
|
||||
assertTrue(goog.labs.userAgent.platform.isChromeOS());
|
||||
assertVersion('3701.62.0');
|
||||
assertVersionBetween('3701', '3702');
|
||||
}
|
||||
|
||||
function assertVersion(version) {
|
||||
assertEquals(version, goog.labs.userAgent.platform.getVersion());
|
||||
}
|
||||
|
||||
function assertVersionBetween(lowVersion, highVersion) {
|
||||
assertTrue(goog.labs.userAgent.platform.isVersionOrHigher(lowVersion));
|
||||
assertFalse(goog.labs.userAgent.platform.isVersionOrHigher(highVersion));
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
// 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 Various User-Agent strings.
|
||||
* See http://go/useragentexamples and http://www.useragentstring.com/ for
|
||||
* examples.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.userAgent.testAgents');
|
||||
goog.setTestOnly('goog.labs.userAgent.testAgents');
|
||||
|
||||
goog.scope(function() {
|
||||
var testAgents = goog.labs.userAgent.testAgents;
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.ANDROID_BROWSER_235 =
|
||||
'Mozilla/5.0 (Linux; U; Android 2.3.5; en-us; ' +
|
||||
'HTC Vision Build/GRI40) AppleWebKit/533.1 (KHTML, like Gecko) ' +
|
||||
'Version/4.0 Mobile Safari/533.1';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.ANDROID_BROWSER_221 =
|
||||
'Mozilla/5.0 (Linux; U; Android 2.2.1; en-ca; LG-P505R Build/FRG83)' +
|
||||
' AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.ANDROID_BROWSER_233 =
|
||||
'Mozilla/5.0 (Linux; U; Android 2.3.3; en-us; HTC_DesireS_S510e' +
|
||||
' Build/GRI40) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0' +
|
||||
' Mobile Safari/533.1';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.ANDROID_BROWSER_403 =
|
||||
'Mozilla/5.0 (Linux; U; Android 4.0.3; de-ch; HTC Sensation Build/IML74K)' +
|
||||
' AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.IE_6 =
|
||||
'Mozilla/5.0 (Windows; U; MSIE 6.0; Windows NT 5.1; SV1;' +
|
||||
'.NET CLR 2.0.50727)';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.IE_8 =
|
||||
'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.IE_8_COMPATIBILITY =
|
||||
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Trident/4.0)';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.IE_9 =
|
||||
'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.IE_9_COMPATIBILITY =
|
||||
'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/5.0)';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.IE_10 =
|
||||
'Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0;' +
|
||||
' InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729;' +
|
||||
' .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.IE_11 =
|
||||
'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv 11.0) like Gecko';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.FIREFOX_19 =
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:19.0) ' +
|
||||
'Gecko/20100101 Firefox/19.0';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.FIREFOX_LINUX =
|
||||
'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:15.0) Gecko/20100101' +
|
||||
' Firefox/15.0.1';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.FIREFOX_MAC =
|
||||
'Mozilla/6.0 (Macintosh; I; Intel Mac OS X 11_7_9; de-LI; rv:1.9b4)' +
|
||||
' Gecko/2012010317 Firefox/10.0a4';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.FIREFOX_WINDOWS =
|
||||
'Mozilla/5.0 (Windows NT 6.1; rv:12.0) Gecko/20120403211507' +
|
||||
' Firefox/14.0.1';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.SAFARI_6 =
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_1) ' +
|
||||
'AppleWebKit/536.25 (KHTML, like Gecko) ' +
|
||||
'Version/6.0 Safari/536.25';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.SAFARI_IPHONE =
|
||||
'Mozilla/5.0 (iPhone; U; ru; CPU iPhone OS 4_2_1 like Mac OS X; ru)' +
|
||||
' AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148a' +
|
||||
' Safari/6533.18.5';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.SAFARI_IPHONE_431 =
|
||||
'Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_1 like Mac OS X; zh-tw)' +
|
||||
' AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8G4' +
|
||||
' Safari/6533.18.5';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.SAFARI_IPHONE_32 =
|
||||
'Mozilla/5.0(iPad; U; CPU iPhone OS 3_2 like Mac OS X; en-us)' +
|
||||
' AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B314' +
|
||||
' Safari/531.21.10';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.SAFARI_IPOD =
|
||||
'Mozila/5.0 (iPod; U; CPU like Mac OS X; en) AppleWebKit/420.1' +
|
||||
' (KHTML, like Gecko) Version/3.0 Mobile/3A101a Safari/419.3';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.SAFARI_MAC =
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13+' +
|
||||
' (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.SAFARI_WINDOWS =
|
||||
'Mozilla/5.0 (Windows; U; Windows NT 6.1; tr-TR) AppleWebKit/533.20.25' +
|
||||
' (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.OPERA_10 =
|
||||
'Opera/9.80 (S60; SymbOS; Opera Mobi/447; U; en) ' +
|
||||
'Presto/2.4.18 Version/10.00';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.OPERA_LINUX =
|
||||
'Opera/9.80 (X11; Linux x86_64; U; fr) Presto/2.9.168 Version/11.50';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.OPERA_MAC =
|
||||
'Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168' +
|
||||
' Version/11.52';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.IPHONE_6 =
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 ' +
|
||||
'like Mac OS X) AppleWebKit/536.26 ' +
|
||||
'(KHTML, like Gecko) Mobile/10A5376e';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.IPAD_4 =
|
||||
'Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us)' +
|
||||
' AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b' +
|
||||
' Safari/531.21.10';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.IPAD_5 =
|
||||
'Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X; en-us) AppleWebKit/534.46' +
|
||||
' (KHTML, like Gecko) Version/5.1 Mobile/9B176 Safari/7534.48.3';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.IPAD_6 =
|
||||
'Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) ' +
|
||||
'AppleWebKit/536.26 (KHTML, like Gecko) ' +
|
||||
'Version/6.0 Mobile/10A403 Safari/8536.25';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.CHROME_25 =
|
||||
'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) ' +
|
||||
'AppleWebKit/535.8 (KHTML, like Gecko) ' +
|
||||
'Chrome/25.0.1000.10 Safari/535.8';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.CHROME_ANDROID =
|
||||
'Mozilla/5.0 (Linux; U; Android 4.0.2; en-us; Galaxy Nexus Build/ICL53F) ' +
|
||||
'AppleWebKit/535.7 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile ' +
|
||||
'Safari/535.7';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.CHROME_ANDROID_TABLET =
|
||||
'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) ' +
|
||||
'AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Safari/535.19';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.CHROME_IPHONE =
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 5_1_1 like Mac OS X; en-us) ' +
|
||||
'AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/22.0.1194.0 Mobile/11E53 ' +
|
||||
'Safari/7534.48.3';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.CHROME_LINUX =
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.31 (KHTML, like Gecko)' +
|
||||
' Chrome/26.0.1410.33 Safari/537.31';
|
||||
|
||||
|
||||
/**
|
||||
* We traditionally use Appversion to detect X11
|
||||
* @const {string}
|
||||
*/
|
||||
testAgents.CHROME_LINUX_APPVERVERSION =
|
||||
'5.0 (X11; Linux x86_64) AppleWebKit/537.31 (KHTML, like Gecko)' +
|
||||
' Chrome/26.0.1410.33 Safari/537.31';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.CHROME_MAC =
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17' +
|
||||
' (KHTML, like Gecko) Chrome/24.0.1309.0 Safari/537.17';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.CHROME_OS =
|
||||
'Mozilla/5.0 (X11; CrOS x86_64 3701.62.0) AppleWebKit/537.31 ' +
|
||||
'(KHTML, like Gecko) Chrome/26.0.1410.40 Safari/537.31';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.CHROME_OS_910 =
|
||||
'Mozilla/5.0 (X11; U; CrOS i686 9.10.0; en-US) AppleWebKit/532.5' +
|
||||
' (KHTML, like Gecko) Chrome/4.0.253.0 Safari/532.5';
|
||||
|
||||
|
||||
/** @const {string} */
|
||||
testAgents.KINDLE_FIRE =
|
||||
'Mozilla/5.0 (Linux; U; Android 4.0.3; en-us; KFTT Build/IML74K)' +
|
||||
' AppleWebKit/535.19 (KHTML, like Gecko) Silk/2.1 Mobile Safari/535.19' +
|
||||
' Silk-Accelerated=true';
|
||||
}); // goog.scope
|
||||
@@ -0,0 +1,89 @@
|
||||
// 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 Utilities used by goog.labs.userAgent tools. These functions
|
||||
* should not be used outside of goog.labs.userAgent.*.
|
||||
*
|
||||
* @visibility {//visibility:private}
|
||||
* @author nnaze@google.com (Nathan Naze)
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.userAgent.util');
|
||||
|
||||
goog.require('goog.memoize');
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
/**
|
||||
* Returns the user agent string.
|
||||
*
|
||||
* @return {string} The user agent string.
|
||||
*/
|
||||
goog.labs.userAgent.util.getUserAgentString = goog.memoize(function() {
|
||||
return goog.global['navigator'] ? goog.global['navigator'].userAgent : '';
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @return {boolean} Whether the user agent contains the given string.
|
||||
*/
|
||||
goog.labs.userAgent.util.matchUserAgent = function(str) {
|
||||
var userAgentString = goog.labs.userAgent.util.getUserAgentString();
|
||||
return goog.string.contains(userAgentString, str);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parses the user agent into tuples for each section.
|
||||
* @param {string} userAgent
|
||||
* @return {!Array.<string>} Tuples of key, version, and the contents of the
|
||||
* parenthetical.
|
||||
*/
|
||||
goog.labs.userAgent.util.extractVersionTuples = function(userAgent) {
|
||||
// Matches each section of a user agent string.
|
||||
// Example UA:
|
||||
// Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us)
|
||||
// AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405
|
||||
// This has three version tuples: Mozilla, AppleWebKit, and Mobile.
|
||||
|
||||
var versionRegExp = new RegExp(
|
||||
// Key. Note that a key may have a space.
|
||||
// (i.e. 'Mobile Safari' in 'Mobile Safari/5.0')
|
||||
'(\\w[\\w ]+)' +
|
||||
|
||||
'/' + // slash
|
||||
'([^\\s]+)' + // version (i.e. '5.0b')
|
||||
'\\s*' + // whitespace
|
||||
'(?:\\((.*?)\\))?', // parenthetical info. parentheses not matched.
|
||||
'g');
|
||||
|
||||
var data = [];
|
||||
var match;
|
||||
|
||||
// Iterate and collect the version tuples. Each iteration will be the
|
||||
// next regex match.
|
||||
while (match = versionRegExp.exec(userAgent)) {
|
||||
data.push([
|
||||
match[1], // key
|
||||
match[2], // value
|
||||
// || undefined as this is not undefined in IE7 and IE8
|
||||
match[3] || undefined // info
|
||||
]);
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// 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 frexcept 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 goog.labs.userAgent.engine.
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.userAgent.utilTest');
|
||||
|
||||
goog.require('goog.labs.userAgent.testAgents');
|
||||
goog.require('goog.labs.userAgent.util');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
goog.setTestOnly('goog.labs.userAgent.utilTest');
|
||||
|
||||
|
||||
/**
|
||||
* Tests parsing a few example UA strings.
|
||||
*/
|
||||
function testExtractVersionTuples() {
|
||||
// Old Android
|
||||
var tuples = goog.labs.userAgent.util.extractVersionTuples(
|
||||
goog.labs.userAgent.testAgents.ANDROID_BROWSER_235);
|
||||
|
||||
assertEquals(4, tuples.length);
|
||||
assertSameElements(
|
||||
['Mozilla', '5.0',
|
||||
'Linux; U; Android 2.3.5; en-us; HTC Vision Build/GRI40'], tuples[0]);
|
||||
assertSameElements(['AppleWebKit', '533.1', 'KHTML, like Gecko'], tuples[1]);
|
||||
assertSameElements(['Version', '4.0', undefined], tuples[2]);
|
||||
assertSameElements(['Mobile Safari', '533.1', undefined], tuples[3]);
|
||||
|
||||
// IE 9
|
||||
tuples = goog.labs.userAgent.util.extractVersionTuples(
|
||||
goog.labs.userAgent.testAgents.IE_9);
|
||||
assertEquals(1, tuples.length);
|
||||
assertSameElements(
|
||||
['Mozilla', '5.0',
|
||||
'compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0'], tuples[0]);
|
||||
|
||||
// Opera
|
||||
tuples = goog.labs.userAgent.util.extractVersionTuples(
|
||||
goog.labs.userAgent.testAgents.OPERA_10);
|
||||
assertEquals(3, tuples.length);
|
||||
assertSameElements(['Opera', '9.80', 'S60; SymbOS; Opera Mobi/447; U; en'],
|
||||
tuples[0]);
|
||||
assertSameElements(['Presto', '2.4.18', undefined], tuples[1]);
|
||||
assertSameElements(['Version', '10.00', undefined], tuples[2]);
|
||||
}
|
||||
Reference in New Issue
Block a user