Adding mapbox-gl branch
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
// Copyright 2012 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview A delayed callback that pegs to the next animation frame
|
||||
* instead of a user-configurable timeout.
|
||||
*
|
||||
* @author nicksantos@google.com (Nick Santos)
|
||||
*/
|
||||
|
||||
goog.provide('goog.async.AnimationDelay');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.functions');
|
||||
|
||||
|
||||
|
||||
// TODO(nicksantos): Should we factor out the common code between this and
|
||||
// goog.async.Delay? I'm not sure if there's enough code for this to really
|
||||
// make sense. Subclassing seems like the wrong approach for a variety of
|
||||
// reasons. Maybe there should be a common interface?
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A delayed callback that pegs to the next animation frame
|
||||
* instead of a user configurable timeout. By design, this should have
|
||||
* the same interface as goog.async.Delay.
|
||||
*
|
||||
* Uses requestAnimationFrame and friends when available, but falls
|
||||
* back to a timeout of goog.async.AnimationDelay.TIMEOUT.
|
||||
*
|
||||
* For more on requestAnimationFrame and how you can use it to create smoother
|
||||
* animations, see:
|
||||
* @see http://paulirish.com/2011/requestanimationframe-for-smart-animating/
|
||||
*
|
||||
* @param {function(number)} listener Function to call when the delay completes.
|
||||
* Will be passed the timestamp when it's called, in unix ms.
|
||||
* @param {Window=} opt_window The window object to execute the delay in.
|
||||
* Defaults to the global object.
|
||||
* @param {Object=} opt_handler The object scope to invoke the function in.
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
* @final
|
||||
*/
|
||||
goog.async.AnimationDelay = function(listener, opt_window, opt_handler) {
|
||||
goog.async.AnimationDelay.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* The function that will be invoked after a delay.
|
||||
* @type {function(number)}
|
||||
* @private
|
||||
*/
|
||||
this.listener_ = listener;
|
||||
|
||||
/**
|
||||
* The object context to invoke the callback in.
|
||||
* @type {Object|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.handler_ = opt_handler;
|
||||
|
||||
/**
|
||||
* @type {Window}
|
||||
* @private
|
||||
*/
|
||||
this.win_ = opt_window || window;
|
||||
|
||||
/**
|
||||
* Cached callback function invoked when the delay finishes.
|
||||
* @type {function()}
|
||||
* @private
|
||||
*/
|
||||
this.callback_ = goog.bind(this.doAction_, this);
|
||||
};
|
||||
goog.inherits(goog.async.AnimationDelay, goog.Disposable);
|
||||
|
||||
|
||||
/**
|
||||
* Identifier of the active delay timeout, or event listener,
|
||||
* or null when inactive.
|
||||
* @type {goog.events.Key|number|null}
|
||||
* @private
|
||||
*/
|
||||
goog.async.AnimationDelay.prototype.id_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* If we're using dom listeners.
|
||||
* @type {?boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.async.AnimationDelay.prototype.usingListeners_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Default wait timeout for animations (in milliseconds). Only used for timed
|
||||
* animation, which uses a timer (setTimeout) to schedule animation.
|
||||
*
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
goog.async.AnimationDelay.TIMEOUT = 20;
|
||||
|
||||
|
||||
/**
|
||||
* Name of event received from the requestAnimationFrame in Firefox.
|
||||
*
|
||||
* @type {string}
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
goog.async.AnimationDelay.MOZ_BEFORE_PAINT_EVENT_ = 'MozBeforePaint';
|
||||
|
||||
|
||||
/**
|
||||
* Starts the delay timer. The provided listener function will be called
|
||||
* before the next animation frame.
|
||||
*/
|
||||
goog.async.AnimationDelay.prototype.start = function() {
|
||||
this.stop();
|
||||
this.usingListeners_ = false;
|
||||
|
||||
var raf = this.getRaf_();
|
||||
var cancelRaf = this.getCancelRaf_();
|
||||
if (raf && !cancelRaf && this.win_.mozRequestAnimationFrame) {
|
||||
// Because Firefox (Gecko) runs animation in separate threads, it also saves
|
||||
// time by running the requestAnimationFrame callbacks in that same thread.
|
||||
// Sadly this breaks the assumption of implicit thread-safety in JS, and can
|
||||
// thus create thread-based inconsistencies on counters etc.
|
||||
//
|
||||
// Calling cycleAnimations_ using the MozBeforePaint event instead of as
|
||||
// callback fixes this.
|
||||
//
|
||||
// Trigger this condition only if the mozRequestAnimationFrame is available,
|
||||
// but not the W3C requestAnimationFrame function (as in draft) or the
|
||||
// equivalent cancel functions.
|
||||
this.id_ = goog.events.listen(
|
||||
this.win_,
|
||||
goog.async.AnimationDelay.MOZ_BEFORE_PAINT_EVENT_,
|
||||
this.callback_);
|
||||
this.win_.mozRequestAnimationFrame(null);
|
||||
this.usingListeners_ = true;
|
||||
} else if (raf && cancelRaf) {
|
||||
this.id_ = raf.call(this.win_, this.callback_);
|
||||
} else {
|
||||
this.id_ = this.win_.setTimeout(
|
||||
// Prior to Firefox 13, Gecko passed a non-standard parameter
|
||||
// to the callback that we want to ignore.
|
||||
goog.functions.lock(this.callback_),
|
||||
goog.async.AnimationDelay.TIMEOUT);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stops the delay timer if it is active. No action is taken if the timer is not
|
||||
* in use.
|
||||
*/
|
||||
goog.async.AnimationDelay.prototype.stop = function() {
|
||||
if (this.isActive()) {
|
||||
var raf = this.getRaf_();
|
||||
var cancelRaf = this.getCancelRaf_();
|
||||
if (raf && !cancelRaf && this.win_.mozRequestAnimationFrame) {
|
||||
goog.events.unlistenByKey(this.id_);
|
||||
} else if (raf && cancelRaf) {
|
||||
cancelRaf.call(this.win_, /** @type {number} */ (this.id_));
|
||||
} else {
|
||||
this.win_.clearTimeout(/** @type {number} */ (this.id_));
|
||||
}
|
||||
}
|
||||
this.id_ = null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fires delay's action even if timer has already gone off or has not been
|
||||
* started yet; guarantees action firing. Stops the delay timer.
|
||||
*/
|
||||
goog.async.AnimationDelay.prototype.fire = function() {
|
||||
this.stop();
|
||||
this.doAction_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fires delay's action only if timer is currently active. Stops the delay
|
||||
* timer.
|
||||
*/
|
||||
goog.async.AnimationDelay.prototype.fireIfActive = function() {
|
||||
if (this.isActive()) {
|
||||
this.fire();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} True if the delay is currently active, false otherwise.
|
||||
*/
|
||||
goog.async.AnimationDelay.prototype.isActive = function() {
|
||||
return this.id_ != null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Invokes the callback function after the delay successfully completes.
|
||||
* @private
|
||||
*/
|
||||
goog.async.AnimationDelay.prototype.doAction_ = function() {
|
||||
if (this.usingListeners_ && this.id_) {
|
||||
goog.events.unlistenByKey(this.id_);
|
||||
}
|
||||
this.id_ = null;
|
||||
|
||||
// We are not using the timestamp returned by requestAnimationFrame
|
||||
// because it may be either a Date.now-style time or a
|
||||
// high-resolution time (depending on browser implementation). Using
|
||||
// goog.now() will ensure that the timestamp used is consistent and
|
||||
// compatible with goog.fx.Animation.
|
||||
this.listener_.call(this.handler_, goog.now());
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.async.AnimationDelay.prototype.disposeInternal = function() {
|
||||
this.stop();
|
||||
goog.async.AnimationDelay.base(this, 'disposeInternal');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {?function(function(number)): number} The requestAnimationFrame
|
||||
* function, or null if not available on this browser.
|
||||
* @private
|
||||
*/
|
||||
goog.async.AnimationDelay.prototype.getRaf_ = function() {
|
||||
var win = this.win_;
|
||||
return win.requestAnimationFrame ||
|
||||
win.webkitRequestAnimationFrame ||
|
||||
win.mozRequestAnimationFrame ||
|
||||
win.oRequestAnimationFrame ||
|
||||
win.msRequestAnimationFrame ||
|
||||
null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {?function(number): number} The cancelAnimationFrame function,
|
||||
* or null if not available on this browser.
|
||||
* @private
|
||||
*/
|
||||
goog.async.AnimationDelay.prototype.getCancelRaf_ = function() {
|
||||
var win = this.win_;
|
||||
return win.cancelAnimationFrame ||
|
||||
win.cancelRequestAnimationFrame ||
|
||||
win.webkitCancelRequestAnimationFrame ||
|
||||
win.mozCancelRequestAnimationFrame ||
|
||||
win.oCancelRequestAnimationFrame ||
|
||||
win.msCancelRequestAnimationFrame ||
|
||||
null;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2012 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
|
||||
Author: nicksantos@google.com (Nick Santos)
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
JsUnit tests for goog.async.AnimationDelay
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.async.AnimationDelayTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2012 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
goog.provide('goog.async.AnimationDelayTest');
|
||||
goog.setTestOnly('goog.async.AnimationDelayTest');
|
||||
|
||||
goog.require('goog.async.AnimationDelay');
|
||||
goog.require('goog.testing.AsyncTestCase');
|
||||
goog.require('goog.testing.PropertyReplacer');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.recordFunction');
|
||||
|
||||
var testCase = goog.testing.AsyncTestCase.createAndInstall();
|
||||
var stubs = new goog.testing.PropertyReplacer();
|
||||
|
||||
function tearDown() {
|
||||
stubs.reset();
|
||||
}
|
||||
|
||||
function testStart() {
|
||||
var callCount = 0;
|
||||
var start = goog.now();
|
||||
var delay = new goog.async.AnimationDelay(function(end) {
|
||||
callCount++;
|
||||
});
|
||||
|
||||
delay.start();
|
||||
testCase.waitForAsync('waiting for delay');
|
||||
|
||||
window.setTimeout(function() {
|
||||
testCase.continueTesting();
|
||||
assertEquals(1, callCount);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function testStop() {
|
||||
var callCount = 0;
|
||||
var start = goog.now();
|
||||
var delay = new goog.async.AnimationDelay(function(end) {
|
||||
callCount++;
|
||||
});
|
||||
|
||||
delay.start();
|
||||
testCase.waitForAsync('waiting for delay');
|
||||
delay.stop();
|
||||
|
||||
window.setTimeout(function() {
|
||||
testCase.continueTesting();
|
||||
assertEquals(0, callCount);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function testAlwaysUseGoogNowForHandlerTimestamp() {
|
||||
var expectedValue = 12345.1;
|
||||
stubs.set(goog, 'now', function() {
|
||||
return expectedValue;
|
||||
});
|
||||
|
||||
var handler = goog.testing.recordFunction(function(timestamp) {
|
||||
assertEquals(expectedValue, timestamp);
|
||||
});
|
||||
var delay = new goog.async.AnimationDelay(handler);
|
||||
|
||||
delay.start();
|
||||
testCase.waitForAsync('waiting for delay');
|
||||
|
||||
window.setTimeout(function() {
|
||||
testCase.continueTesting();
|
||||
assertEquals(1, handler.getCallCount());
|
||||
}, 500);
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Defines a class useful for handling functions that must be
|
||||
* invoked later when some condition holds. Examples include deferred function
|
||||
* calls that return a boolean flag whether it succedeed or not.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* function deferred() {
|
||||
* var succeeded = false;
|
||||
* // ... custom code
|
||||
* return succeeded;
|
||||
* }
|
||||
*
|
||||
* var deferredCall = new goog.async.ConditionalDelay(deferred);
|
||||
* deferredCall.onSuccess = function() {
|
||||
* alert('Success: The deferred function has been successfully executed.');
|
||||
* }
|
||||
* deferredCall.onFailure = function() {
|
||||
* alert('Failure: Time limit exceeded.');
|
||||
* }
|
||||
*
|
||||
* // Call the deferred() every 100 msec until it returns true,
|
||||
* // or 5 seconds pass.
|
||||
* deferredCall.start(100, 5000);
|
||||
*
|
||||
* // Stop the deferred function call (does nothing if it's not active).
|
||||
* deferredCall.stop();
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.async.ConditionalDelay');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.async.Delay');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A ConditionalDelay object invokes the associated function after a specified
|
||||
* interval delay and checks its return value. If the function returns
|
||||
* {@code true} the conditional delay is cancelled and {@see #onSuccess}
|
||||
* is called. Otherwise this object keeps to invoke the deferred function until
|
||||
* either it returns {@code true} or the timeout is exceeded. In the latter case
|
||||
* the {@see #onFailure} method will be called.
|
||||
*
|
||||
* The interval duration and timeout can be specified each time the delay is
|
||||
* started. Calling start on an active delay will reset the timer.
|
||||
*
|
||||
* @param {function():boolean} listener Function to call when the delay
|
||||
* completes. Should return a value that type-converts to {@code true} if
|
||||
* the call succeeded and this delay should be stopped.
|
||||
* @param {Object=} opt_handler The object scope to invoke the function in.
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
*/
|
||||
goog.async.ConditionalDelay = function(listener, opt_handler) {
|
||||
goog.Disposable.call(this);
|
||||
|
||||
/**
|
||||
* The function that will be invoked after a delay.
|
||||
* @type {function():boolean}
|
||||
* @private
|
||||
*/
|
||||
this.listener_ = listener;
|
||||
|
||||
/**
|
||||
* The object context to invoke the callback in.
|
||||
* @type {Object|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.handler_ = opt_handler;
|
||||
|
||||
/**
|
||||
* The underlying goog.async.Delay delegate object.
|
||||
* @type {goog.async.Delay}
|
||||
* @private
|
||||
*/
|
||||
this.delay_ = new goog.async.Delay(
|
||||
goog.bind(this.onTick_, this), 0 /*interval*/, this /*scope*/);
|
||||
};
|
||||
goog.inherits(goog.async.ConditionalDelay, goog.Disposable);
|
||||
|
||||
|
||||
/**
|
||||
* The delay interval in milliseconds to between the calls to the callback.
|
||||
* Note, that the callback may be invoked earlier than this interval if the
|
||||
* timeout is exceeded.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.async.ConditionalDelay.prototype.interval_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* The timeout timestamp until which the delay is to be executed.
|
||||
* A negative value means no timeout.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.async.ConditionalDelay.prototype.runUntil_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* True if the listener has been executed, and it returned {@code true}.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.async.ConditionalDelay.prototype.isDone_ = false;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.async.ConditionalDelay.prototype.disposeInternal = function() {
|
||||
this.delay_.dispose();
|
||||
delete this.listener_;
|
||||
delete this.handler_;
|
||||
goog.async.ConditionalDelay.superClass_.disposeInternal.call(this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Starts the delay timer. The provided listener function will be called
|
||||
* repeatedly after the specified interval until the function returns
|
||||
* {@code true} or the timeout is exceeded. Calling start on an active timer
|
||||
* will stop the timer first.
|
||||
* @param {number=} opt_interval The time interval between the function
|
||||
* invocations (in milliseconds). Default is 0.
|
||||
* @param {number=} opt_timeout The timeout interval (in milliseconds). Takes
|
||||
* precedence over the {@code opt_interval}, i.e. if the timeout is less
|
||||
* than the invocation interval, the function will be called when the
|
||||
* timeout is exceeded. A negative value means no timeout. Default is 0.
|
||||
*/
|
||||
goog.async.ConditionalDelay.prototype.start = function(opt_interval,
|
||||
opt_timeout) {
|
||||
this.stop();
|
||||
this.isDone_ = false;
|
||||
|
||||
var timeout = opt_timeout || 0;
|
||||
this.interval_ = Math.max(opt_interval || 0, 0);
|
||||
this.runUntil_ = timeout < 0 ? -1 : (goog.now() + timeout);
|
||||
this.delay_.start(
|
||||
timeout < 0 ? this.interval_ : Math.min(this.interval_, timeout));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stops the delay timer if it is active. No action is taken if the timer is not
|
||||
* in use.
|
||||
*/
|
||||
goog.async.ConditionalDelay.prototype.stop = function() {
|
||||
this.delay_.stop();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} True if the delay is currently active, false otherwise.
|
||||
*/
|
||||
goog.async.ConditionalDelay.prototype.isActive = function() {
|
||||
return this.delay_.isActive();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} True if the listener has been executed and returned
|
||||
* {@code true} since the last call to {@see #start}.
|
||||
*/
|
||||
goog.async.ConditionalDelay.prototype.isDone = function() {
|
||||
return this.isDone_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Called when the listener has been successfully executed and returned
|
||||
* {@code true}. The {@see #isDone} method should return {@code true} by now.
|
||||
* Designed for inheritance, should be overridden by subclasses or on the
|
||||
* instances if they care.
|
||||
*/
|
||||
goog.async.ConditionalDelay.prototype.onSuccess = function() {
|
||||
// Do nothing by default.
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Called when this delayed call is cancelled because the timeout has been
|
||||
* exceeded, and the listener has never returned {@code true}.
|
||||
* Designed for inheritance, should be overridden by subclasses or on the
|
||||
* instances if they care.
|
||||
*/
|
||||
goog.async.ConditionalDelay.prototype.onFailure = function() {
|
||||
// Do nothing by default.
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A callback function for the underlying {@code goog.async.Delay} object. When
|
||||
* executed the listener function is called, and if it returns {@code true}
|
||||
* the delay is stopped and the {@see #onSuccess} method is invoked.
|
||||
* If the timeout is exceeded the delay is stopped and the
|
||||
* {@see #onFailure} method is called.
|
||||
* @private
|
||||
*/
|
||||
goog.async.ConditionalDelay.prototype.onTick_ = function() {
|
||||
var successful = this.listener_.call(this.handler_);
|
||||
if (successful) {
|
||||
this.isDone_ = true;
|
||||
this.onSuccess();
|
||||
} else {
|
||||
// Try to reschedule the task.
|
||||
if (this.runUntil_ < 0) {
|
||||
// No timeout.
|
||||
this.delay_.start(this.interval_);
|
||||
} else {
|
||||
var timeLeft = this.runUntil_ - goog.now();
|
||||
if (timeLeft <= 0) {
|
||||
this.onFailure();
|
||||
} else {
|
||||
this.delay_.start(Math.min(this.interval_, timeLeft));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.async.ConditionalDelay
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.async.ConditionalDelayTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,222 @@
|
||||
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
goog.provide('goog.async.ConditionalDelayTest');
|
||||
goog.setTestOnly('goog.async.ConditionalDelayTest');
|
||||
|
||||
goog.require('goog.async.ConditionalDelay');
|
||||
goog.require('goog.testing.MockClock');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var invoked = false;
|
||||
var delay = null;
|
||||
var clock = null;
|
||||
var returnValue = true;
|
||||
var onSuccessCalled = false;
|
||||
var onFailureCalled = false;
|
||||
|
||||
|
||||
function callback() {
|
||||
invoked = true;
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
|
||||
function setUp() {
|
||||
clock = new goog.testing.MockClock(true);
|
||||
invoked = false;
|
||||
returnValue = true;
|
||||
onSuccessCalled = false;
|
||||
onFailureCalled = false;
|
||||
delay = new goog.async.ConditionalDelay(callback);
|
||||
delay.onSuccess = function() {
|
||||
onSuccessCalled = true;
|
||||
};
|
||||
delay.onFailure = function() {
|
||||
onFailureCalled = true;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function tearDown() {
|
||||
clock.dispose();
|
||||
delay.dispose();
|
||||
}
|
||||
|
||||
|
||||
function testDelay() {
|
||||
delay.start(200, 200);
|
||||
assertFalse(invoked);
|
||||
|
||||
clock.tick(100);
|
||||
assertFalse(invoked);
|
||||
|
||||
clock.tick(100);
|
||||
assertTrue(invoked);
|
||||
}
|
||||
|
||||
|
||||
function testStop() {
|
||||
delay.start(200, 500);
|
||||
assertTrue(delay.isActive());
|
||||
|
||||
clock.tick(100);
|
||||
assertFalse(invoked);
|
||||
|
||||
delay.stop();
|
||||
clock.tick(100);
|
||||
assertFalse(invoked);
|
||||
|
||||
assertFalse(delay.isActive());
|
||||
}
|
||||
|
||||
|
||||
function testIsActive() {
|
||||
assertFalse(delay.isActive());
|
||||
delay.start(200, 200);
|
||||
assertTrue(delay.isActive());
|
||||
clock.tick(200);
|
||||
assertFalse(delay.isActive());
|
||||
}
|
||||
|
||||
|
||||
function testRestart() {
|
||||
delay.start(200, 50000);
|
||||
clock.tick(100);
|
||||
|
||||
delay.stop();
|
||||
assertFalse(invoked);
|
||||
|
||||
delay.start(200, 50000);
|
||||
clock.tick(199);
|
||||
assertFalse(invoked);
|
||||
|
||||
clock.tick(1);
|
||||
assertTrue(invoked);
|
||||
|
||||
invoked = false;
|
||||
delay.start(200, 200);
|
||||
clock.tick(200);
|
||||
assertTrue(invoked);
|
||||
|
||||
assertFalse(delay.isActive());
|
||||
}
|
||||
|
||||
|
||||
function testDispose() {
|
||||
delay.start(200, 200);
|
||||
delay.dispose();
|
||||
assertTrue(delay.isDisposed());
|
||||
|
||||
clock.tick(500);
|
||||
assertFalse(invoked);
|
||||
}
|
||||
|
||||
|
||||
function testConditionalDelay_Success() {
|
||||
returnValue = false;
|
||||
delay.start(100, 300);
|
||||
|
||||
clock.tick(99);
|
||||
assertFalse(invoked);
|
||||
clock.tick(1);
|
||||
assertTrue(invoked);
|
||||
|
||||
assertTrue(delay.isActive());
|
||||
assertFalse(delay.isDone());
|
||||
assertFalse(onSuccessCalled);
|
||||
assertFalse(onFailureCalled);
|
||||
|
||||
returnValue = true;
|
||||
|
||||
invoked = false;
|
||||
clock.tick(100);
|
||||
assertTrue(invoked);
|
||||
|
||||
assertFalse(delay.isActive());
|
||||
assertTrue(delay.isDone());
|
||||
assertTrue(onSuccessCalled);
|
||||
assertFalse(onFailureCalled);
|
||||
|
||||
invoked = false;
|
||||
clock.tick(200);
|
||||
assertFalse(invoked);
|
||||
}
|
||||
|
||||
|
||||
function testConditionalDelay_Failure() {
|
||||
returnValue = false;
|
||||
delay.start(100, 300);
|
||||
|
||||
clock.tick(99);
|
||||
assertFalse(invoked);
|
||||
clock.tick(1);
|
||||
assertTrue(invoked);
|
||||
|
||||
assertTrue(delay.isActive());
|
||||
assertFalse(delay.isDone());
|
||||
assertFalse(onSuccessCalled);
|
||||
assertFalse(onFailureCalled);
|
||||
|
||||
invoked = false;
|
||||
clock.tick(100);
|
||||
assertTrue(invoked);
|
||||
assertFalse(onSuccessCalled);
|
||||
assertFalse(onFailureCalled);
|
||||
|
||||
invoked = false;
|
||||
clock.tick(90);
|
||||
assertFalse(invoked);
|
||||
clock.tick(10);
|
||||
assertTrue(invoked);
|
||||
|
||||
assertFalse(delay.isActive());
|
||||
assertFalse(delay.isDone());
|
||||
assertFalse(onSuccessCalled);
|
||||
assertTrue(onFailureCalled);
|
||||
}
|
||||
|
||||
|
||||
function testInfiniteDelay() {
|
||||
returnValue = false;
|
||||
delay.start(100, -1);
|
||||
|
||||
// Test in a big enough loop.
|
||||
for (var i = 0; i < 1000; ++i) {
|
||||
clock.tick(80);
|
||||
assertTrue(delay.isActive());
|
||||
assertFalse(delay.isDone());
|
||||
assertFalse(onSuccessCalled);
|
||||
assertFalse(onFailureCalled);
|
||||
}
|
||||
|
||||
delay.stop();
|
||||
assertFalse(delay.isActive());
|
||||
assertFalse(delay.isDone());
|
||||
assertFalse(onSuccessCalled);
|
||||
assertFalse(onFailureCalled);
|
||||
}
|
||||
|
||||
function testCallbackScope() {
|
||||
var callbackCalled = false;
|
||||
var scopeObject = {};
|
||||
function internalCallback() {
|
||||
assertEquals(this, scopeObject);
|
||||
callbackCalled = true;
|
||||
return true;
|
||||
}
|
||||
delay = new goog.async.ConditionalDelay(internalCallback, scopeObject);
|
||||
delay.start(200, 200);
|
||||
clock.tick(201);
|
||||
assertTrue(callbackCalled);
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Defines a class useful for handling functions that must be
|
||||
* invoked after a delay, especially when that delay is frequently restarted.
|
||||
* Examples include delaying before displaying a tooltip, menu hysteresis,
|
||||
* idle timers, etc.
|
||||
* @author brenneman@google.com (Shawn Brenneman)
|
||||
* @see ../demos/timers.html
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.Delay');
|
||||
goog.provide('goog.async.Delay');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.Timer');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A Delay object invokes the associated function after a specified delay. The
|
||||
* interval duration can be specified once in the constructor, or can be defined
|
||||
* each time the delay is started. Calling start on an active delay will reset
|
||||
* the timer.
|
||||
*
|
||||
* @param {Function} listener Function to call when the delay completes.
|
||||
* @param {number=} opt_interval The default length of the invocation delay (in
|
||||
* milliseconds).
|
||||
* @param {Object=} opt_handler The object scope to invoke the function in.
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
* @final
|
||||
*/
|
||||
goog.async.Delay = function(listener, opt_interval, opt_handler) {
|
||||
goog.Disposable.call(this);
|
||||
|
||||
/**
|
||||
* The function that will be invoked after a delay.
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
this.listener_ = listener;
|
||||
|
||||
/**
|
||||
* The default amount of time to delay before invoking the callback.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.interval_ = opt_interval || 0;
|
||||
|
||||
/**
|
||||
* The object context to invoke the callback in.
|
||||
* @type {Object|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.handler_ = opt_handler;
|
||||
|
||||
|
||||
/**
|
||||
* Cached callback function invoked when the delay finishes.
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
this.callback_ = goog.bind(this.doAction_, this);
|
||||
};
|
||||
goog.inherits(goog.async.Delay, goog.Disposable);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A deprecated alias.
|
||||
* @deprecated Use goog.async.Delay instead.
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.Delay = goog.async.Delay;
|
||||
|
||||
|
||||
/**
|
||||
* Identifier of the active delay timeout, or 0 when inactive.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.async.Delay.prototype.id_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Disposes of the object, cancelling the timeout if it is still outstanding and
|
||||
* removing all object references.
|
||||
* @override
|
||||
* @protected
|
||||
*/
|
||||
goog.async.Delay.prototype.disposeInternal = function() {
|
||||
goog.async.Delay.superClass_.disposeInternal.call(this);
|
||||
this.stop();
|
||||
delete this.listener_;
|
||||
delete this.handler_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Starts the delay timer. The provided listener function will be called after
|
||||
* the specified interval. Calling start on an active timer will reset the
|
||||
* delay interval.
|
||||
* @param {number=} opt_interval If specified, overrides the object's default
|
||||
* interval with this one (in milliseconds).
|
||||
*/
|
||||
goog.async.Delay.prototype.start = function(opt_interval) {
|
||||
this.stop();
|
||||
this.id_ = goog.Timer.callOnce(
|
||||
this.callback_,
|
||||
goog.isDef(opt_interval) ? opt_interval : this.interval_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stops the delay timer if it is active. No action is taken if the timer is not
|
||||
* in use.
|
||||
*/
|
||||
goog.async.Delay.prototype.stop = function() {
|
||||
if (this.isActive()) {
|
||||
goog.Timer.clear(this.id_);
|
||||
}
|
||||
this.id_ = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fires delay's action even if timer has already gone off or has not been
|
||||
* started yet; guarantees action firing. Stops the delay timer.
|
||||
*/
|
||||
goog.async.Delay.prototype.fire = function() {
|
||||
this.stop();
|
||||
this.doAction_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fires delay's action only if timer is currently active. Stops the delay
|
||||
* timer.
|
||||
*/
|
||||
goog.async.Delay.prototype.fireIfActive = function() {
|
||||
if (this.isActive()) {
|
||||
this.fire();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} True if the delay is currently active, false otherwise.
|
||||
*/
|
||||
goog.async.Delay.prototype.isActive = function() {
|
||||
return this.id_ != 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Invokes the callback function after the delay successfully completes.
|
||||
* @private
|
||||
*/
|
||||
goog.async.Delay.prototype.doAction_ = function() {
|
||||
this.id_ = 0;
|
||||
if (this.listener_) {
|
||||
this.listener_.call(this.handler_);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.async.Delay
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.async.DelayTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
goog.provide('goog.async.DelayTest');
|
||||
goog.setTestOnly('goog.async.DelayTest');
|
||||
|
||||
goog.require('goog.async.Delay');
|
||||
goog.require('goog.testing.MockClock');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var invoked = false;
|
||||
var delay = null;
|
||||
var clock = null;
|
||||
|
||||
|
||||
function callback() {
|
||||
invoked = true;
|
||||
}
|
||||
|
||||
|
||||
function setUp() {
|
||||
clock = new goog.testing.MockClock(true);
|
||||
invoked = false;
|
||||
delay = new goog.async.Delay(callback, 200);
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
clock.dispose();
|
||||
delay.dispose();
|
||||
}
|
||||
|
||||
|
||||
function testDelay() {
|
||||
delay.start();
|
||||
assertFalse(invoked);
|
||||
|
||||
clock.tick(100);
|
||||
assertFalse(invoked);
|
||||
|
||||
clock.tick(100);
|
||||
assertTrue(invoked);
|
||||
}
|
||||
|
||||
|
||||
function testStop() {
|
||||
delay.start();
|
||||
|
||||
clock.tick(100);
|
||||
assertFalse(invoked);
|
||||
|
||||
delay.stop();
|
||||
clock.tick(100);
|
||||
assertFalse(invoked);
|
||||
}
|
||||
|
||||
|
||||
function testIsActive() {
|
||||
assertFalse(delay.isActive());
|
||||
delay.start();
|
||||
assertTrue(delay.isActive());
|
||||
clock.tick(200);
|
||||
assertFalse(delay.isActive());
|
||||
}
|
||||
|
||||
|
||||
function testRestart() {
|
||||
delay.start();
|
||||
clock.tick(100);
|
||||
|
||||
delay.stop();
|
||||
assertFalse(invoked);
|
||||
|
||||
delay.start();
|
||||
clock.tick(199);
|
||||
assertFalse(invoked);
|
||||
|
||||
clock.tick(1);
|
||||
assertTrue(invoked);
|
||||
|
||||
invoked = false;
|
||||
delay.start();
|
||||
clock.tick(200);
|
||||
assertTrue(invoked);
|
||||
}
|
||||
|
||||
|
||||
function testOverride() {
|
||||
delay.start(50);
|
||||
clock.tick(49);
|
||||
assertFalse(invoked);
|
||||
|
||||
clock.tick(1);
|
||||
assertTrue(invoked);
|
||||
}
|
||||
|
||||
|
||||
function testDispose() {
|
||||
delay.start();
|
||||
delay.dispose();
|
||||
assertTrue(delay.isDisposed());
|
||||
|
||||
clock.tick(500);
|
||||
assertFalse(invoked);
|
||||
}
|
||||
|
||||
|
||||
function testFire() {
|
||||
delay.start();
|
||||
|
||||
clock.tick(50);
|
||||
delay.fire();
|
||||
assertTrue(invoked);
|
||||
assertFalse(delay.isActive());
|
||||
|
||||
invoked = false;
|
||||
clock.tick(200);
|
||||
assertFalse('Delay fired early with fire call, timeout should have been ' +
|
||||
'cleared', invoked);
|
||||
}
|
||||
|
||||
function testFireIfActive() {
|
||||
delay.fireIfActive();
|
||||
assertFalse(invoked);
|
||||
|
||||
delay.start();
|
||||
delay.fireIfActive();
|
||||
assertTrue(invoked);
|
||||
invoked = false;
|
||||
clock.tick(300);
|
||||
assertFalse('Delay fired early with fireIfActive, timeout should have been ' +
|
||||
'cleared', invoked);
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Provides a function to schedule running a function as soon
|
||||
* as possible after the current JS execution stops and yields to the event
|
||||
* loop.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.async.nextTick');
|
||||
goog.provide('goog.async.throwException');
|
||||
|
||||
goog.require('goog.debug.entryPointRegistry');
|
||||
goog.require('goog.functions');
|
||||
goog.require('goog.labs.userAgent.browser');
|
||||
|
||||
|
||||
/**
|
||||
* Throw an item without interrupting the current execution context. For
|
||||
* example, if processing a group of items in a loop, sometimes it is useful
|
||||
* to report an error while still allowing the rest of the batch to be
|
||||
* processed.
|
||||
* @param {*} exception
|
||||
*/
|
||||
goog.async.throwException = function(exception) {
|
||||
// Each throw needs to be in its own context.
|
||||
goog.global.setTimeout(function() { throw exception; }, 0);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fires the provided callbacks as soon as possible after the current JS
|
||||
* execution context. setTimeout(…, 0) takes at least 4ms when called from
|
||||
* within another setTimeout(…, 0) for legacy reasons.
|
||||
*
|
||||
* This will not schedule the callback as a microtask (i.e. a task that can
|
||||
* preempt user input or networking callbacks). It is meant to emulate what
|
||||
* setTimeout(_, 0) would do if it were not throttled. If you desire microtask
|
||||
* behavior, use {@see goog.Promise} instead.
|
||||
*
|
||||
* @param {function(this:SCOPE)} callback Callback function to fire as soon as
|
||||
* possible.
|
||||
* @param {SCOPE=} opt_context Object in whose scope to call the listener.
|
||||
* @param {boolean=} opt_useSetImmediate Avoid the IE workaround that
|
||||
* ensures correctness at the cost of speed. See comments for details.
|
||||
* @template SCOPE
|
||||
*/
|
||||
goog.async.nextTick = function(callback, opt_context, opt_useSetImmediate) {
|
||||
var cb = callback;
|
||||
if (opt_context) {
|
||||
cb = goog.bind(callback, opt_context);
|
||||
}
|
||||
cb = goog.async.nextTick.wrapCallback_(cb);
|
||||
// window.setImmediate was introduced and currently only supported by IE10+,
|
||||
// but due to a bug in the implementation it is not guaranteed that
|
||||
// setImmediate is faster than setTimeout nor that setImmediate N is before
|
||||
// setImmediate N+1. That is why we do not use the native version if
|
||||
// available. We do, however, call setImmediate if it is a normal function
|
||||
// because that indicates that it has been replaced by goog.testing.MockClock
|
||||
// which we do want to support.
|
||||
// See
|
||||
// http://connect.microsoft.com/IE/feedback/details/801823/setimmediate-and-messagechannel-are-broken-in-ie10
|
||||
//
|
||||
// Note we do allow callers to also request setImmediate if they are willing
|
||||
// to accept the possible tradeoffs of incorrectness in exchange for speed.
|
||||
// The IE fallback of readystate change is much slower.
|
||||
if (goog.isFunction(goog.global.setImmediate) &&
|
||||
(opt_useSetImmediate || !goog.global.Window ||
|
||||
goog.global.Window.prototype.setImmediate != goog.global.setImmediate)) {
|
||||
goog.global.setImmediate(cb);
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for and cache the custom fallback version of setImmediate.
|
||||
if (!goog.async.nextTick.setImmediate_) {
|
||||
goog.async.nextTick.setImmediate_ =
|
||||
goog.async.nextTick.getSetImmediateEmulator_();
|
||||
}
|
||||
goog.async.nextTick.setImmediate_(cb);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cache for the setImmediate implementation.
|
||||
* @type {function(function())}
|
||||
* @private
|
||||
*/
|
||||
goog.async.nextTick.setImmediate_;
|
||||
|
||||
|
||||
/**
|
||||
* Determines the best possible implementation to run a function as soon as
|
||||
* the JS event loop is idle.
|
||||
* @return {function(function())} The "setImmediate" implementation.
|
||||
* @private
|
||||
*/
|
||||
goog.async.nextTick.getSetImmediateEmulator_ = function() {
|
||||
// Create a private message channel and use it to postMessage empty messages
|
||||
// to ourselves.
|
||||
var Channel = goog.global['MessageChannel'];
|
||||
// If MessageChannel is not available and we are in a browser, implement
|
||||
// an iframe based polyfill in browsers that have postMessage and
|
||||
// document.addEventListener. The latter excludes IE8 because it has a
|
||||
// synchronous postMessage implementation.
|
||||
if (typeof Channel === 'undefined' && typeof window !== 'undefined' &&
|
||||
window.postMessage && window.addEventListener) {
|
||||
/** @constructor */
|
||||
Channel = function() {
|
||||
// Make an empty, invisible iframe.
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.style.display = 'none';
|
||||
iframe.src = '';
|
||||
document.documentElement.appendChild(iframe);
|
||||
var win = iframe.contentWindow;
|
||||
var doc = win.document;
|
||||
doc.open();
|
||||
doc.write('');
|
||||
doc.close();
|
||||
// Do not post anything sensitive over this channel, as the workaround for
|
||||
// pages with file: origin could allow that information to be modified or
|
||||
// intercepted.
|
||||
var message = 'callImmediate' + Math.random();
|
||||
// The same origin policy rejects attempts to postMessage from file: urls
|
||||
// unless the origin is '*'.
|
||||
// TODO(b/16335441): Use '*' origin for data: and other similar protocols.
|
||||
var origin = win.location.protocol == 'file:' ?
|
||||
'*' : win.location.protocol + '//' + win.location.host;
|
||||
var onmessage = goog.bind(function(e) {
|
||||
// Validate origin and message to make sure that this message was
|
||||
// intended for us. If the origin is set to '*' (see above) only the
|
||||
// message needs to match since, for example, '*' != 'file://'. Allowing
|
||||
// the wildcard is ok, as we are not concerned with security here.
|
||||
if ((origin != '*' && e.origin != origin) || e.data != message) {
|
||||
return;
|
||||
}
|
||||
this['port1'].onmessage();
|
||||
}, this);
|
||||
win.addEventListener('message', onmessage, false);
|
||||
this['port1'] = {};
|
||||
this['port2'] = {
|
||||
postMessage: function() {
|
||||
win.postMessage(message, origin);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
if (typeof Channel !== 'undefined' &&
|
||||
(!goog.labs.userAgent.browser.isIE())) {
|
||||
// Exclude all of IE due to
|
||||
// http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/
|
||||
// which allows starving postMessage with a busy setTimeout loop.
|
||||
// This currently affects IE10 and IE11 which would otherwise be able
|
||||
// to use the postMessage based fallbacks.
|
||||
var channel = new Channel();
|
||||
// Use a fifo linked list to call callbacks in the right order.
|
||||
var head = {};
|
||||
var tail = head;
|
||||
channel['port1'].onmessage = function() {
|
||||
if (goog.isDef(head.next)) {
|
||||
head = head.next;
|
||||
var cb = head.cb;
|
||||
head.cb = null;
|
||||
cb();
|
||||
}
|
||||
};
|
||||
return function(cb) {
|
||||
tail.next = {
|
||||
cb: cb
|
||||
};
|
||||
tail = tail.next;
|
||||
channel['port2'].postMessage(0);
|
||||
};
|
||||
}
|
||||
// Implementation for IE6+: Script elements fire an asynchronous
|
||||
// onreadystatechange event when inserted into the DOM.
|
||||
if (typeof document !== 'undefined' && 'onreadystatechange' in
|
||||
document.createElement('script')) {
|
||||
return function(cb) {
|
||||
var script = document.createElement('script');
|
||||
script.onreadystatechange = function() {
|
||||
// Clean up and call the callback.
|
||||
script.onreadystatechange = null;
|
||||
script.parentNode.removeChild(script);
|
||||
script = null;
|
||||
cb();
|
||||
cb = null;
|
||||
};
|
||||
document.documentElement.appendChild(script);
|
||||
};
|
||||
}
|
||||
// Fall back to setTimeout with 0. In browsers this creates a delay of 5ms
|
||||
// or more.
|
||||
return function(cb) {
|
||||
goog.global.setTimeout(cb, 0);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper function that is overrided to protect callbacks with entry point
|
||||
* monitor if the application monitors entry points.
|
||||
* @param {function()} callback Callback function to fire as soon as possible.
|
||||
* @return {function()} The wrapped callback.
|
||||
* @private
|
||||
*/
|
||||
goog.async.nextTick.wrapCallback_ = goog.functions.identity;
|
||||
|
||||
|
||||
// Register the callback function as an entry point, so that it can be
|
||||
// monitored for exception handling, etc. This has to be done in this file
|
||||
// since it requires special code to handle all browsers.
|
||||
goog.debug.entryPointRegistry.register(
|
||||
/**
|
||||
* @param {function(!Function): !Function} transformer The transforming
|
||||
* function.
|
||||
*/
|
||||
function(transformer) {
|
||||
goog.async.nextTick.wrapCallback_ = transformer;
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2013 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.async.nextTick
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.async.nextTickTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,243 @@
|
||||
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
goog.provide('goog.async.nextTickTest');
|
||||
goog.setTestOnly('goog.async.nextTickTest');
|
||||
|
||||
goog.require('goog.async.nextTick');
|
||||
goog.require('goog.debug.ErrorHandler');
|
||||
goog.require('goog.debug.entryPointRegistry');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.labs.userAgent.browser');
|
||||
goog.require('goog.testing.AsyncTestCase');
|
||||
goog.require('goog.testing.MockClock');
|
||||
goog.require('goog.testing.PropertyReplacer');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall(
|
||||
'asyncNextTickTest');
|
||||
|
||||
var clock;
|
||||
var propertyReplacer = new goog.testing.PropertyReplacer();
|
||||
|
||||
function setUp() {
|
||||
clock = null;
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
if (clock) {
|
||||
clock.uninstall();
|
||||
}
|
||||
goog.async.nextTick.setImmediate_ = undefined;
|
||||
propertyReplacer.reset();
|
||||
}
|
||||
|
||||
|
||||
function testNextTick() {
|
||||
var c = 0;
|
||||
var max = 100;
|
||||
var async = true;
|
||||
var counterStep = function(i) {
|
||||
async = false;
|
||||
assertEquals('Order correct', i, c);
|
||||
c++;
|
||||
if (c === max) {
|
||||
asyncTestCase.continueTesting();
|
||||
}
|
||||
};
|
||||
for (var i = 0; i < max; i++) {
|
||||
goog.async.nextTick(goog.partial(counterStep, i));
|
||||
}
|
||||
assertTrue(async);
|
||||
asyncTestCase.waitForAsync('Wait for callback');
|
||||
}
|
||||
|
||||
|
||||
function testNextTickSetImmediate() {
|
||||
var c = 0;
|
||||
var max = 100;
|
||||
var async = true;
|
||||
var counterStep = function(i) {
|
||||
async = false;
|
||||
assertEquals('Order correct', i, c);
|
||||
c++;
|
||||
if (c === max) {
|
||||
asyncTestCase.continueTesting();
|
||||
}
|
||||
};
|
||||
for (var i = 0; i < max; i++) {
|
||||
goog.async.nextTick(goog.partial(counterStep, i), undefined,
|
||||
/* opt_useSetImmediate */ true);
|
||||
}
|
||||
assertTrue(async);
|
||||
asyncTestCase.waitForAsync('Wait for callback');
|
||||
}
|
||||
|
||||
function testNextTickContext() {
|
||||
var context = {};
|
||||
var c = 0;
|
||||
var max = 10;
|
||||
var async = true;
|
||||
var counterStep = function(i) {
|
||||
async = false;
|
||||
assertEquals('Order correct', i, c);
|
||||
assertEquals(context, this);
|
||||
c++;
|
||||
if (c === max) {
|
||||
asyncTestCase.continueTesting();
|
||||
}
|
||||
};
|
||||
for (var i = 0; i < max; i++) {
|
||||
goog.async.nextTick(goog.partial(counterStep, i), context);
|
||||
}
|
||||
assertTrue(async);
|
||||
asyncTestCase.waitForAsync('Wait for callback');
|
||||
}
|
||||
|
||||
|
||||
function testNextTickMockClock() {
|
||||
clock = new goog.testing.MockClock(true);
|
||||
var result = '';
|
||||
goog.async.nextTick(function() {
|
||||
result += 'a';
|
||||
});
|
||||
goog.async.nextTick(function() {
|
||||
result += 'b';
|
||||
});
|
||||
goog.async.nextTick(function() {
|
||||
result += 'c';
|
||||
});
|
||||
assertEquals('', result);
|
||||
clock.tick(0);
|
||||
assertEquals('abc', result);
|
||||
}
|
||||
|
||||
|
||||
function testNextTickDoesntSwallowError() {
|
||||
var before = window.onerror;
|
||||
var sentinel = 'sentinel';
|
||||
window.onerror = function(e) {
|
||||
e = '' + e;
|
||||
// Don't test for contents in IE7, which does not preserve the exception
|
||||
// message.
|
||||
if (e.indexOf('Exception thrown and not caught') == -1) {
|
||||
assertContains(sentinel, e);
|
||||
}
|
||||
window.onerror = before;
|
||||
asyncTestCase.continueTesting();
|
||||
return false;
|
||||
};
|
||||
goog.async.nextTick(function() {
|
||||
throw sentinel;
|
||||
});
|
||||
asyncTestCase.waitForAsync('Wait for error');
|
||||
}
|
||||
|
||||
|
||||
function testNextTickProtectEntryPoint() {
|
||||
var errorHandlerCallbackCalled = false;
|
||||
var errorHandler = new goog.debug.ErrorHandler(function() {
|
||||
errorHandlerCallbackCalled = true;
|
||||
});
|
||||
|
||||
// This is only testing wrapping the callback with the protected entry point,
|
||||
// so it's okay to replace this function with a fake.
|
||||
goog.async.nextTick.setImmediate_ = function(cb) {
|
||||
try {
|
||||
cb();
|
||||
fail('The callback should have thrown an error.');
|
||||
} catch (e) {
|
||||
assertTrue(
|
||||
e instanceof goog.debug.ErrorHandler.ProtectedFunctionError);
|
||||
}
|
||||
asyncTestCase.continueTesting();
|
||||
};
|
||||
var origSetImmediate;
|
||||
if (window.setImmediate) {
|
||||
origSetImmediate = window.setImmediate;
|
||||
window.setImmediate = goog.async.nextTick.setImmediate_;
|
||||
}
|
||||
goog.debug.entryPointRegistry.monitorAll(errorHandler);
|
||||
|
||||
function thrower() {
|
||||
throw Error('This should be caught by the protected function.');
|
||||
}
|
||||
asyncTestCase.waitForAsync('Wait for callback');
|
||||
goog.async.nextTick(thrower);
|
||||
window.setImmediate = origSetImmediate;
|
||||
}
|
||||
|
||||
|
||||
function testNextTick_notStarvedBySetTimeout() {
|
||||
// This test will timeout when affected by
|
||||
// http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/
|
||||
// This test would fail without the fix introduced in cl/72472221
|
||||
// It keeps scheduling 0 timeouts and a single nextTick. If the nextTick
|
||||
// ever fires, the IE specific problem does not occur.
|
||||
var timeout;
|
||||
function busy() {
|
||||
timeout = setTimeout(function() {
|
||||
busy();
|
||||
}, 0);
|
||||
}
|
||||
busy();
|
||||
goog.async.nextTick(function() {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
asyncTestCase.waitForAsync('Waiting not to starve');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test a scenario in which the iframe used by the postMessage polyfill gets a
|
||||
* message that does not have match what is expected. In this case, the polyfill
|
||||
* should not try to invoke a callback (which would result in an error because
|
||||
* there would be no callbacks in the linked list).
|
||||
*/
|
||||
function testPostMessagePolyfillDoesNotPumpCallbackQueueIfMessageIsIncorrect() {
|
||||
// IE does not use the postMessage polyfill.
|
||||
if (goog.labs.userAgent.browser.isIE()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Force postMessage pollyfill for setImmediate.
|
||||
propertyReplacer.set(window, 'setImmediate', undefined);
|
||||
propertyReplacer.set(window, 'MessageChannel', undefined);
|
||||
|
||||
var callbackCalled = false;
|
||||
goog.async.nextTick(function() {
|
||||
callbackCalled = true;
|
||||
});
|
||||
|
||||
var frame = document.getElementsByTagName('iframe')[0];
|
||||
frame.contentWindow.postMessage('bogus message',
|
||||
window.location.protocol + '//' + window.location.host);
|
||||
|
||||
var error = null;
|
||||
frame.contentWindow.onerror = function(e) {
|
||||
error = e;
|
||||
};
|
||||
|
||||
setTimeout(function() {
|
||||
assert('Callback should have been called.', callbackCalled);
|
||||
assertNull('An unexpected error was thrown.', error);
|
||||
goog.dom.removeNode(frame);
|
||||
asyncTestCase.continueTesting();
|
||||
}, 0);
|
||||
|
||||
asyncTestCase.waitForAsync('Waiting for callbacks to be invoked.');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
goog.provide('goog.async.run');
|
||||
|
||||
goog.require('goog.async.nextTick');
|
||||
goog.require('goog.async.throwException');
|
||||
goog.require('goog.testing.watchers');
|
||||
|
||||
|
||||
/**
|
||||
* Fires the provided callback just before the current callstack unwinds, or as
|
||||
* soon as possible after the current JS execution context.
|
||||
* @param {function(this:THIS)} callback
|
||||
* @param {THIS=} opt_context Object to use as the "this value" when calling
|
||||
* the provided function.
|
||||
* @template THIS
|
||||
*/
|
||||
goog.async.run = function(callback, opt_context) {
|
||||
if (!goog.async.run.schedule_) {
|
||||
goog.async.run.initializeRunner_();
|
||||
}
|
||||
if (!goog.async.run.workQueueScheduled_) {
|
||||
// Nothing is currently scheduled, schedule it now.
|
||||
goog.async.run.schedule_();
|
||||
goog.async.run.workQueueScheduled_ = true;
|
||||
}
|
||||
|
||||
goog.async.run.workQueue_.push(
|
||||
new goog.async.run.WorkItem_(callback, opt_context));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the function to use to process the work queue.
|
||||
* @private
|
||||
*/
|
||||
goog.async.run.initializeRunner_ = function() {
|
||||
// If native Promises are available in the browser, just schedule the callback
|
||||
// on a fulfilled promise, which is specified to be async, but as fast as
|
||||
// possible.
|
||||
if (goog.global.Promise && goog.global.Promise.resolve) {
|
||||
var promise = goog.global.Promise.resolve();
|
||||
goog.async.run.schedule_ = function() {
|
||||
promise.then(goog.async.run.processWorkQueue);
|
||||
};
|
||||
} else {
|
||||
goog.async.run.schedule_ = function() {
|
||||
goog.async.nextTick(goog.async.run.processWorkQueue);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Forces goog.async.run to use nextTick instead of Promise.
|
||||
*
|
||||
* This should only be done in unit tests. It's useful because MockClock
|
||||
* replaces nextTick, but not the browser Promise implementation, so it allows
|
||||
* Promise-based code to be tested with MockClock.
|
||||
*/
|
||||
goog.async.run.forceNextTick = function() {
|
||||
goog.async.run.schedule_ = function() {
|
||||
goog.async.nextTick(goog.async.run.processWorkQueue);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The function used to schedule work asynchronousely.
|
||||
* @private {function()}
|
||||
*/
|
||||
goog.async.run.schedule_;
|
||||
|
||||
|
||||
/** @private {boolean} */
|
||||
goog.async.run.workQueueScheduled_ = false;
|
||||
|
||||
|
||||
/** @private {!Array<!goog.async.run.WorkItem_>} */
|
||||
goog.async.run.workQueue_ = [];
|
||||
|
||||
|
||||
if (goog.DEBUG) {
|
||||
/**
|
||||
* Reset the event queue.
|
||||
* @private
|
||||
*/
|
||||
goog.async.run.resetQueue_ = function() {
|
||||
goog.async.run.workQueueScheduled_ = false;
|
||||
goog.async.run.workQueue_ = [];
|
||||
};
|
||||
|
||||
// If there is a clock implemenation in use for testing
|
||||
// and it is reset, reset the queue.
|
||||
goog.testing.watchers.watchClockReset(goog.async.run.resetQueue_);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Run any pending goog.async.run work items. This function is not intended
|
||||
* for general use, but for use by entry point handlers to run items ahead of
|
||||
* goog.async.nextTick.
|
||||
*/
|
||||
goog.async.run.processWorkQueue = function() {
|
||||
// NOTE: additional work queue items may be pushed while processing.
|
||||
while (goog.async.run.workQueue_.length) {
|
||||
// Don't let the work queue grow indefinitely.
|
||||
var workItems = goog.async.run.workQueue_;
|
||||
goog.async.run.workQueue_ = [];
|
||||
for (var i = 0; i < workItems.length; i++) {
|
||||
var workItem = workItems[i];
|
||||
try {
|
||||
workItem.fn.call(workItem.scope);
|
||||
} catch (e) {
|
||||
goog.async.throwException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// There are no more work items, reset the work queue.
|
||||
goog.async.run.workQueueScheduled_ = false;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @final
|
||||
* @struct
|
||||
* @private
|
||||
*
|
||||
* @param {function()} fn
|
||||
* @param {Object|null|undefined} scope
|
||||
*/
|
||||
goog.async.run.WorkItem_ = function(fn, scope) {
|
||||
/** @const */ this.fn = fn;
|
||||
/** @const */ this.scope = scope;
|
||||
};
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
goog.provide('goog.async.runTest');
|
||||
|
||||
goog.require('goog.async.run');
|
||||
goog.require('goog.testing.MockClock');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.recordFunction');
|
||||
|
||||
goog.setTestOnly('goog.async.runTest');
|
||||
|
||||
|
||||
var mockClock;
|
||||
var futureCallback1, futureCallback2;
|
||||
|
||||
function setUpPage() {
|
||||
mockClock = new goog.testing.MockClock();
|
||||
mockClock.install();
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
mockClock.reset();
|
||||
futureCallback1 = new goog.testing.recordFunction();
|
||||
futureCallback2 = new goog.testing.recordFunction();
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
futureCallback1 = null;
|
||||
futureCallback2 = null;
|
||||
}
|
||||
|
||||
function tearDownPage() {
|
||||
mockClock.uninstall();
|
||||
goog.dispose(mockClock);
|
||||
}
|
||||
|
||||
function testCalledAsync() {
|
||||
goog.async.run(futureCallback1);
|
||||
goog.async.run(futureCallback2);
|
||||
|
||||
assertEquals(0, futureCallback1.getCallCount());
|
||||
assertEquals(0, futureCallback2.getCallCount());
|
||||
|
||||
// but the callbacks are scheduled...
|
||||
mockClock.tick();
|
||||
|
||||
// and called.
|
||||
assertEquals(1, futureCallback1.getCallCount());
|
||||
assertEquals(1, futureCallback2.getCallCount());
|
||||
|
||||
// and aren't called a second time.
|
||||
assertEquals(1, futureCallback1.getCallCount());
|
||||
assertEquals(1, futureCallback2.getCallCount());
|
||||
}
|
||||
|
||||
function testSequenceCalledInOrder() {
|
||||
futureCallback1 = new goog.testing.recordFunction(
|
||||
function() {
|
||||
// called before futureCallback2
|
||||
assertEquals(0, futureCallback2.getCallCount());
|
||||
});
|
||||
futureCallback2 = new goog.testing.recordFunction(
|
||||
function() {
|
||||
// called after futureCallback1
|
||||
assertEquals(1, futureCallback1.getCallCount());
|
||||
});
|
||||
goog.async.run(futureCallback1);
|
||||
goog.async.run(futureCallback2);
|
||||
|
||||
// goog.async.run doesn't call the top callback immediately.
|
||||
assertEquals(0, futureCallback1.getCallCount());
|
||||
|
||||
// but the callbacks are scheduled...
|
||||
mockClock.tick();
|
||||
|
||||
// and called during the same "tick".
|
||||
assertEquals(1, futureCallback1.getCallCount());
|
||||
assertEquals(1, futureCallback2.getCallCount());
|
||||
}
|
||||
|
||||
function testSequenceScheduledTwice() {
|
||||
goog.async.run(futureCallback1);
|
||||
goog.async.run(futureCallback1);
|
||||
|
||||
// goog.async.run doesn't call the top callback immediately.
|
||||
assertEquals(0, futureCallback1.getCallCount());
|
||||
|
||||
// but the callbacks are scheduled...
|
||||
mockClock.tick();
|
||||
|
||||
// and called twice during the same "tick".
|
||||
assertEquals(2, futureCallback1.getCallCount());
|
||||
}
|
||||
|
||||
function testSequenceCalledSync() {
|
||||
futureCallback1 = new goog.testing.recordFunction(
|
||||
function() {
|
||||
goog.async.run(futureCallback2);
|
||||
// goog.async.run doesn't call the inner callback immediately.
|
||||
assertEquals(0, futureCallback2.getCallCount());
|
||||
});
|
||||
goog.async.run(futureCallback1);
|
||||
|
||||
// goog.async.run doesn't call the top callback immediately.
|
||||
assertEquals(0, futureCallback1.getCallCount());
|
||||
|
||||
// but the callbacks are scheduled...
|
||||
mockClock.tick();
|
||||
|
||||
// and called during the same "tick".
|
||||
assertEquals(1, futureCallback1.getCallCount());
|
||||
assertEquals(1, futureCallback2.getCallCount());
|
||||
}
|
||||
|
||||
function testScope() {
|
||||
var aScope = {};
|
||||
goog.async.run(futureCallback1);
|
||||
goog.async.run(futureCallback2, aScope);
|
||||
|
||||
// the callbacks are scheduled...
|
||||
mockClock.tick();
|
||||
|
||||
// and called.
|
||||
assertEquals(1, futureCallback1.getCallCount());
|
||||
assertEquals(1, futureCallback2.getCallCount());
|
||||
|
||||
// and get the correct scope.
|
||||
var last1 = futureCallback1.popLastCall();
|
||||
assertEquals(0, last1.getArguments().length);
|
||||
assertEquals(goog.global, last1.getThis());
|
||||
|
||||
var last2 = futureCallback2.popLastCall();
|
||||
assertEquals(0, last2.getArguments().length);
|
||||
assertEquals(aScope, last2.getThis());
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the goog.async.Throttle class.
|
||||
*
|
||||
* @see ../demos/timers.html
|
||||
*/
|
||||
|
||||
goog.provide('goog.Throttle');
|
||||
goog.provide('goog.async.Throttle');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.Timer');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Throttle will perform an action that is passed in no more than once
|
||||
* per interval (specified in milliseconds). If it gets multiple signals
|
||||
* to perform the action while it is waiting, it will only perform the action
|
||||
* once at the end of the interval.
|
||||
* @param {function(this: T)} listener Function to callback when the action is
|
||||
* triggered.
|
||||
* @param {number} interval Interval over which to throttle. The listener can
|
||||
* only be called once per interval.
|
||||
* @param {T=} opt_handler Object in whose scope to call the listener.
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
* @final
|
||||
* @template T
|
||||
*/
|
||||
goog.async.Throttle = function(listener, interval, opt_handler) {
|
||||
goog.Disposable.call(this);
|
||||
|
||||
/**
|
||||
* Function to callback
|
||||
* @type {function(this: T)}
|
||||
* @private
|
||||
*/
|
||||
this.listener_ = listener;
|
||||
|
||||
/**
|
||||
* Interval for the throttle time
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.interval_ = interval;
|
||||
|
||||
/**
|
||||
* "this" context for the listener
|
||||
* @type {Object|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.handler_ = opt_handler;
|
||||
|
||||
/**
|
||||
* Cached callback function invoked after the throttle timeout completes
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
this.callback_ = goog.bind(this.onTimer_, this);
|
||||
};
|
||||
goog.inherits(goog.async.Throttle, goog.Disposable);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A deprecated alias.
|
||||
* @deprecated Use goog.async.Throttle instead.
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.Throttle = goog.async.Throttle;
|
||||
|
||||
|
||||
/**
|
||||
* Indicates that the action is pending and needs to be fired.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.async.Throttle.prototype.shouldFire_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Indicates the count of nested pauses currently in effect on the throttle.
|
||||
* When this count is not zero, fired actions will be postponed until the
|
||||
* throttle is resumed enough times to drop the pause count to zero.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.async.Throttle.prototype.pauseCount_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Timer for scheduling the next callback
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
goog.async.Throttle.prototype.timer_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Notifies the throttle that the action has happened. It will throttle the call
|
||||
* so that the callback is not called too often according to the interval
|
||||
* parameter passed to the constructor.
|
||||
*/
|
||||
goog.async.Throttle.prototype.fire = function() {
|
||||
if (!this.timer_ && !this.pauseCount_) {
|
||||
this.doAction_();
|
||||
} else {
|
||||
this.shouldFire_ = true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cancels any pending action callback. The throttle can be restarted by
|
||||
* calling {@link #fire}.
|
||||
*/
|
||||
goog.async.Throttle.prototype.stop = function() {
|
||||
if (this.timer_) {
|
||||
goog.Timer.clear(this.timer_);
|
||||
this.timer_ = null;
|
||||
this.shouldFire_ = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Pauses the throttle. All pending and future action callbacks will be
|
||||
* delayed until the throttle is resumed. Pauses can be nested.
|
||||
*/
|
||||
goog.async.Throttle.prototype.pause = function() {
|
||||
this.pauseCount_++;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resumes the throttle. If doing so drops the pausing count to zero, pending
|
||||
* action callbacks will be executed as soon as possible, but still no sooner
|
||||
* than an interval's delay after the previous call. Future action callbacks
|
||||
* will be executed as normal.
|
||||
*/
|
||||
goog.async.Throttle.prototype.resume = function() {
|
||||
this.pauseCount_--;
|
||||
if (!this.pauseCount_ && this.shouldFire_ && !this.timer_) {
|
||||
this.shouldFire_ = false;
|
||||
this.doAction_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.async.Throttle.prototype.disposeInternal = function() {
|
||||
goog.async.Throttle.superClass_.disposeInternal.call(this);
|
||||
this.stop();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for the timer to fire the throttle
|
||||
* @private
|
||||
*/
|
||||
goog.async.Throttle.prototype.onTimer_ = function() {
|
||||
this.timer_ = null;
|
||||
|
||||
if (this.shouldFire_ && !this.pauseCount_) {
|
||||
this.shouldFire_ = false;
|
||||
this.doAction_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls the callback
|
||||
* @private
|
||||
*/
|
||||
goog.async.Throttle.prototype.doAction_ = function() {
|
||||
this.timer_ = goog.Timer.callOnce(this.callback_, this.interval_);
|
||||
this.listener_.call(this.handler_);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.async.Throttle
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.async.ThrottleTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
goog.provide('goog.async.ThrottleTest');
|
||||
goog.setTestOnly('goog.async.ThrottleTest');
|
||||
|
||||
goog.require('goog.async.Throttle');
|
||||
goog.require('goog.testing.MockClock');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
function testThrottle() {
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
|
||||
var callBackCount = 0;
|
||||
var callBackFunction = function() {
|
||||
callBackCount++;
|
||||
};
|
||||
|
||||
var throttle = new goog.async.Throttle(callBackFunction, 100);
|
||||
assertEquals(0, callBackCount);
|
||||
throttle.fire();
|
||||
assertEquals(1, callBackCount);
|
||||
throttle.fire();
|
||||
assertEquals(1, callBackCount);
|
||||
throttle.fire();
|
||||
throttle.fire();
|
||||
assertEquals(1, callBackCount);
|
||||
clock.tick(101);
|
||||
assertEquals(2, callBackCount);
|
||||
clock.tick(101);
|
||||
assertEquals(2, callBackCount);
|
||||
|
||||
throttle.fire();
|
||||
assertEquals(3, callBackCount);
|
||||
throttle.fire();
|
||||
assertEquals(3, callBackCount);
|
||||
throttle.stop();
|
||||
clock.tick(101);
|
||||
assertEquals(3, callBackCount);
|
||||
throttle.fire();
|
||||
assertEquals(4, callBackCount);
|
||||
clock.tick(101);
|
||||
assertEquals(4, callBackCount);
|
||||
|
||||
throttle.fire();
|
||||
throttle.fire();
|
||||
assertEquals(5, callBackCount);
|
||||
throttle.pause();
|
||||
throttle.resume();
|
||||
assertEquals(5, callBackCount);
|
||||
throttle.pause();
|
||||
clock.tick(101);
|
||||
assertEquals(5, callBackCount);
|
||||
throttle.resume();
|
||||
assertEquals(6, callBackCount);
|
||||
clock.tick(101);
|
||||
assertEquals(6, callBackCount);
|
||||
throttle.pause();
|
||||
throttle.fire();
|
||||
assertEquals(6, callBackCount);
|
||||
clock.tick(101);
|
||||
assertEquals(6, callBackCount);
|
||||
throttle.resume();
|
||||
assertEquals(7, callBackCount);
|
||||
|
||||
throttle.pause();
|
||||
throttle.pause();
|
||||
clock.tick(101);
|
||||
throttle.fire();
|
||||
throttle.resume();
|
||||
assertEquals(7, callBackCount);
|
||||
throttle.resume();
|
||||
assertEquals(8, callBackCount);
|
||||
|
||||
throttle.pause();
|
||||
throttle.pause();
|
||||
throttle.fire();
|
||||
throttle.resume();
|
||||
clock.tick(101);
|
||||
assertEquals(8, callBackCount);
|
||||
throttle.resume();
|
||||
assertEquals(9, callBackCount);
|
||||
|
||||
clock.uninstall();
|
||||
}
|
||||
Reference in New Issue
Block a user