Chromium Code Reviews| Index: chrome/browser/resources/google_now/utility.js |
| diff --git a/chrome/browser/resources/google_now/utility.js b/chrome/browser/resources/google_now/utility.js |
| index eb8f5cc0b741c3924fd80dac07c648a57c8f279e..f70f35c379457c9c012927b37092617ec658b7b1 100644 |
| --- a/chrome/browser/resources/google_now/utility.js |
| +++ b/chrome/browser/resources/google_now/utility.js |
| @@ -61,27 +61,50 @@ function buildTaskManager(areConflicting) { |
| var queue = []; |
| /** |
| - * Name of the current step of the currently running task if present, |
| - * otherwise, null. For diagnostics only. |
| - * It's set when the task is started and before each asynchronous operation. |
| + * Count of unfinished callbacks of the current task. |
| + * @type {number} |
| */ |
| - var stepName = null; |
| + var taskPendingCallbackCount = 0; |
| + |
| + /** |
| + * Required callbacks that are not yet called. Includes both task and non-task |
| + * callbacks. This is a map from unique callback id to the stack at the moment |
| + * when the callback was wrapped. This stack identifies the callback. |
| + * Used only for diagnostics. |
| + * @type {Object.<number, string>} |
| + */ |
| + var pendingCallbacks = {}; |
| + |
| + /** |
| + * True if currently executed code is a part of a task. |
| + * @type {boolean} |
| + */ |
| + var isInTask = false; |
| /** |
| * Starts the first queued task. |
| */ |
| function startFirst() { |
| verify(queue.length >= 1, 'startFirst: queue is empty'); |
| + verify(!isInTask, 'startFirst: already in task'); |
| + isInTask = true; |
| // Start the oldest queued task, but don't remove it from the queue. |
| verify( |
| - stepName == null, |
| - 'tasks.startFirst: stepName is not null: ' + stepName + |
| - ', queue = ' + JSON.stringify(queue)); |
| + taskPendingCallbackCount == 0, |
| + 'tasks.startFirst: still have pending task callbacks: ' + |
| + taskPendingCallbackCount + |
| + ', queue = ' + JSON.stringify(queue) + |
| + ', pendingCallbacks = ' + JSON.stringify(pendingCallbacks)); |
| var entry = queue[0]; |
| - stepName = entry.name + '-initial'; |
| console.log('Starting task ' + entry.name); |
| - entry.task(finish); |
| + |
| + entry.task(function() {}); // TODO(vadimt): Don't pass parameter. |
| + |
| + verify(isInTask, 'startFirst: not in task at exit'); |
| + isInTask = false; |
| + if (taskPendingCallbackCount == 0) |
|
arv (Not doing code reviews)
2013/07/24 23:10:19
if (!taskPendingCallbackCount)
vadimt
2013/07/25 00:25:10
Respectfully disagree. It's shorter, but makes cod
|
| + finish(); |
| } |
| /** |
| @@ -126,25 +149,14 @@ function buildTaskManager(areConflicting) { |
| */ |
| function finish() { |
| verify(queue.length >= 1, |
| - 'tasks.finish: The task queue is empty; step = ' + stepName); |
| + 'tasks.finish: The task queue is empty'); |
| console.log('Finishing task ' + queue[0].name); |
| queue.shift(); |
| - stepName = null; |
| if (queue.length >= 1) |
| startFirst(); |
| } |
| - /** |
| - * Associates a name with the current step of the task. Used for diagnostics |
| - * only. A task is a chain of asynchronous events; debugSetStepName should be |
| - * called before starting any asynchronous operation. |
| - * @param {string} step Name of new step. |
| - */ |
| - function debugSetStepName(step) { |
| - stepName = step; |
| - } |
| - |
| // Limiting 1 error report per background page load. |
| var errorReported = false; |
| @@ -187,15 +199,46 @@ function buildTaskManager(areConflicting) { |
| } |
| /** |
| + * Unique ID of the next callback. |
| + * @type {number} |
| + */ |
| + var nextCallbackId = 0; |
| + |
| + /** |
| * Adds error processing to an API callback. |
| * @param {Function} callback Callback to instrument. |
| + * @param {boolean=} opt_dontRequire True if the callback is not required to |
| + * be invoked. |
| * @return {Function} Instrumented callback. |
| */ |
| - function wrapCallback(callback) { |
| + function wrapCallback(callback, opt_dontRequire) { |
| + verify(!(opt_dontRequire && isInTask), 'Unrequired callback in a task.'); |
| + var callbackId = nextCallbackId++; |
| + var isTaskCallback = isInTask; |
| + if (isTaskCallback) |
| + ++taskPendingCallbackCount; |
| + if (!opt_dontRequire) |
| + pendingCallbacks[callbackId] = new Error().stack; |
| + |
| return function() { |
| // This is the wrapper for the callback. |
| try { |
| - return callback.apply(null, arguments); |
| + if (isTaskCallback) { |
| + verify(!isInTask, 'wrapCallback: already in task'); |
| + isInTask = true; |
| + } |
| + if (!opt_dontRequire) |
| + delete pendingCallbacks[callbackId]; |
| + |
| + // Call the original callback. |
| + callback.apply(null, arguments); |
| + |
| + if (isTaskCallback) { |
| + verify(isInTask, 'wrapCallback: not in task at exit'); |
| + isInTask = false; |
| + if (--taskPendingCallbackCount == 0) |
| + finish(); |
| + } |
| } catch (error) { |
| var message = 'Uncaught exception:\n' + error.stack; |
| console.error(message); |
| @@ -233,7 +276,8 @@ function buildTaskManager(areConflicting) { |
| alert('Argument ' + callbackParameter + ' of ' + functionName + |
| ' is not a function'); |
| } |
| - arguments[callbackParameter] = wrapCallback(callback); |
| + arguments[callbackParameter] = wrapCallback( |
| + callback, functionName == 'addListener'); |
| return originalFunction.apply(namespace, arguments); |
| }; |
| } |
| @@ -242,20 +286,17 @@ function buildTaskManager(areConflicting) { |
| instrumentApiFunction(chrome.runtime.onSuspend, 'addListener', 0); |
| chrome.runtime.onSuspend.addListener(function() { |
| + var stringifiedPendingCallbacks = JSON.stringify(pendingCallbacks); |
| verify( |
| - queue.length == 0, |
| - 'Incomplete task when unloading event page, queue = ' + |
| - JSON.stringify(queue) + ', step = ' + stepName); |
| - verify( |
| - stepName == null, |
| - 'Step name not null when unloading event page, queue = ' + |
| - JSON.stringify(queue) + ', step = ' + stepName); |
| + queue.length == 0 && stringifiedPendingCallbacks == '{}', |
|
arv (Not doing code reviews)
2013/07/24 23:10:19
!queue.length
vadimt
2013/07/25 00:25:10
Same.
|
| + 'Incomplete task or pending callbacks when unloading event page,' + |
| + ' queue = ' + JSON.stringify(queue) + |
| + ', pendingCallbacks = ' + stringifiedPendingCallbacks); |
| }); |
| return { |
| add: add, |
| - // TODO(vadimt): Replace with instrumenting callbacks. |
| - debugSetStepName: debugSetStepName, |
| + debugSetStepName: function() {}, // TODO(vadimt): remove |
| instrumentApiFunction: instrumentApiFunction, |
| wrapCallback: wrapCallback |
| }; |
| @@ -338,7 +379,6 @@ function buildAttemptManager( |
| * the planning is done. |
| */ |
| function planForNext(callback) { |
| - tasks.debugSetStepName('planForNext-get-storage'); |
| storage.get(currentDelayStorageKey, function(items) { |
| console.log('planForNext-get-storage ' + JSON.stringify(items)); |
| scheduleNextAttempt(items[currentDelayStorageKey]); |