Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 'use strict'; | 5 'use strict'; |
| 6 | 6 |
| 7 // TODO(vadimt): Remove alerts. | 7 // TODO(vadimt): Remove alerts. |
| 8 | 8 |
| 9 /** | 9 /** |
| 10 * @fileoverview Utility objects and functions for Google Now extension. | 10 * @fileoverview Utility objects and functions for Google Now extension. |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 53 */ | 53 */ |
| 54 function buildTaskManager(areConflicting) { | 54 function buildTaskManager(areConflicting) { |
| 55 /** | 55 /** |
| 56 * Queue of scheduled tasks. The first element, if present, corresponds to the | 56 * Queue of scheduled tasks. The first element, if present, corresponds to the |
| 57 * currently running task. | 57 * currently running task. |
| 58 * @type {Array.<Object.<string, function(function())>>} | 58 * @type {Array.<Object.<string, function(function())>>} |
| 59 */ | 59 */ |
| 60 var queue = []; | 60 var queue = []; |
| 61 | 61 |
| 62 /** | 62 /** |
| 63 * Name of the current step of the currently running task if present, | 63 * Count of unfinished callbacks of the current task. |
| 64 * otherwise, null. For diagnostics only. | 64 * @type {number} |
| 65 * It's set when the task is started and before each asynchronous operation. | |
| 66 */ | 65 */ |
| 67 var stepName = null; | 66 var taskPendingCallbackCount = 0; |
| 67 | |
| 68 /** | |
| 69 * Not yet called required callbacks. Includes both task and non-task | |
| 70 * callbacks. This is a map from unique callback id to the stack at the moment | |
| 71 * when the callback was wrapped. This stack identifies the callback. | |
| 72 * Used only for diagnostics. | |
| 73 * @type {Object.<number, string>} | |
| 74 */ | |
| 75 var pendingCallbacks = {}; | |
| 76 | |
| 77 /** | |
| 78 * True if currently executed code is a part of a task. | |
| 79 * @type {boolean} | |
| 80 */ | |
| 81 var isInTask = false; | |
| 68 | 82 |
| 69 /** | 83 /** |
| 70 * Starts the first queued task. | 84 * Starts the first queued task. |
| 71 */ | 85 */ |
| 72 function startFirst() { | 86 function startFirst() { |
| 73 verify(queue.length >= 1, 'startFirst: queue is empty'); | 87 verify(queue.length >= 1, 'startFirst: queue is empty'); |
| 88 verify(!isInTask, 'startFirst: already in task'); | |
| 89 isInTask = true; | |
| 74 | 90 |
| 75 // Start the oldest queued task, but don't remove it from the queue. | 91 // Start the oldest queued task, but don't remove it from the queue. |
| 76 verify( | 92 verify( |
| 77 stepName == null, | 93 taskPendingCallbackCount == 0, |
| 78 'tasks.startFirst: stepName is not null: ' + stepName + | 94 'tasks.startFirst: still have pending task callbacks: ' + |
| 79 ', queue = ' + JSON.stringify(queue)); | 95 taskPendingCallbackCount + |
| 96 ', queue = ' + JSON.stringify(queue) + | |
| 97 ', pendingCallbacks = ' + JSON.stringify(pendingCallbacks)); | |
| 80 var entry = queue[0]; | 98 var entry = queue[0]; |
| 81 stepName = entry.name + '-initial'; | |
| 82 console.log('Starting task ' + entry.name); | 99 console.log('Starting task ' + entry.name); |
| 83 entry.task(finish); | 100 |
| 101 entry.task(function() {}); // TODO(vadimt): Don't pass parameter. | |
| 102 | |
| 103 verify(isInTask, 'startFirst: not in task at exit'); | |
| 104 isInTask = false; | |
| 105 if (taskPendingCallbackCount == 0) | |
|
skare_
2013/07/22 23:09:54
does this need to be kept separate from pendingCal
vadimt
2013/07/22 23:59:59
pendingCallbacks also includes non-task callbacks.
| |
| 106 finish(); | |
| 84 } | 107 } |
| 85 | 108 |
| 86 /** | 109 /** |
| 87 * Checks if a new task can be added to the task queue. | 110 * Checks if a new task can be added to the task queue. |
| 88 * @param {string} taskName Name of the new task. | 111 * @param {string} taskName Name of the new task. |
| 89 * @return {boolean} Whether the new task can be added. | 112 * @return {boolean} Whether the new task can be added. |
| 90 */ | 113 */ |
| 91 function canQueue(taskName) { | 114 function canQueue(taskName) { |
| 92 for (var i = 0; i < queue.length; ++i) { | 115 for (var i = 0; i < queue.length; ++i) { |
| 93 if (areConflicting(taskName, queue[i].name)) { | 116 if (areConflicting(taskName, queue[i].name)) { |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 118 if (queue.length == 1) { | 141 if (queue.length == 1) { |
| 119 startFirst(); | 142 startFirst(); |
| 120 } | 143 } |
| 121 } | 144 } |
| 122 | 145 |
| 123 /** | 146 /** |
| 124 * Completes the current task and starts the next queued task if available. | 147 * Completes the current task and starts the next queued task if available. |
| 125 */ | 148 */ |
| 126 function finish() { | 149 function finish() { |
| 127 verify(queue.length >= 1, | 150 verify(queue.length >= 1, |
| 128 'tasks.finish: The task queue is empty; step = ' + stepName); | 151 'tasks.finish: The task queue is empty'); |
| 129 console.log('Finishing task ' + queue[0].name); | 152 console.log('Finishing task ' + queue[0].name); |
| 130 queue.shift(); | 153 queue.shift(); |
| 131 stepName = null; | |
| 132 | 154 |
| 133 if (queue.length >= 1) | 155 if (queue.length >= 1) |
| 134 startFirst(); | 156 startFirst(); |
| 135 } | 157 } |
| 136 | 158 |
| 137 /** | |
| 138 * Associates a name with the current step of the task. Used for diagnostics | |
| 139 * only. A task is a chain of asynchronous events; debugSetStepName should be | |
| 140 * called before starting any asynchronous operation. | |
| 141 * @param {string} step Name of new step. | |
| 142 */ | |
| 143 function debugSetStepName(step) { | |
| 144 stepName = step; | |
| 145 } | |
| 146 | |
| 147 // Limiting 1 error report per background page load. | 159 // Limiting 1 error report per background page load. |
| 148 var errorReported = false; | 160 var errorReported = false; |
| 149 | 161 |
| 150 /** | 162 /** |
| 151 * Sends an error report to the server. | 163 * Sends an error report to the server. |
| 152 * @param {Error} error Error to report. | 164 * @param {Error} error Error to report. |
| 153 */ | 165 */ |
| 154 function sendErrorReport(error) { | 166 function sendErrorReport(error) { |
| 155 var filteredStack = error.stack.replace(/.*\n/, '\n'); | 167 var filteredStack = error.stack.replace(/.*\n/, '\n'); |
| 156 var file; | 168 var file; |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 178 '&line=' + encodeURIComponent(line) + | 190 '&line=' + encodeURIComponent(line) + |
| 179 '&trace=' + encodeURIComponent(filteredStack); | 191 '&trace=' + encodeURIComponent(filteredStack); |
| 180 var request = buildServerRequest('jserror'); | 192 var request = buildServerRequest('jserror'); |
| 181 request.onloadend = function(event) { | 193 request.onloadend = function(event) { |
| 182 console.log('sendErrorReport status: ' + request.status); | 194 console.log('sendErrorReport status: ' + request.status); |
| 183 }; | 195 }; |
| 184 request.send(requestParameters); | 196 request.send(requestParameters); |
| 185 } | 197 } |
| 186 | 198 |
| 187 /** | 199 /** |
| 200 * Unique ID of the next callback. | |
| 201 * @type {number} | |
| 202 */ | |
| 203 var nextCallbackId = 0; | |
| 204 | |
| 205 /** | |
| 188 * Adds error processing to an API callback. | 206 * Adds error processing to an API callback. |
| 189 * @param {Function} callback Callback to instrument. | 207 * @param {Function} callback Callback to instrument. |
| 208 * @param {boolean=} opt_dontRequire True if the callback is not required to | |
| 209 * be invoked. | |
| 190 * @return {Function} Instrumented callback. | 210 * @return {Function} Instrumented callback. |
| 191 */ | 211 */ |
| 192 function wrapCallback(callback) { | 212 function wrapCallback(callback, opt_dontRequire) { |
| 213 verify(!(opt_dontRequire && isInTask), 'Unrequired callback in a task.'); | |
| 214 var callbackId = nextCallbackId++; | |
| 215 var isTaskCallback = isInTask; | |
| 216 if (isInTask) | |
| 217 ++taskPendingCallbackCount; | |
| 218 if (!opt_dontRequire) | |
| 219 pendingCallbacks[callbackId] = new Error().stack; | |
| 220 | |
| 193 return function() { | 221 return function() { |
| 194 // This is the wrapper for the callback. | 222 // This is the wrapper for the callback. |
| 195 try { | 223 try { |
| 196 return callback.apply(null, arguments); | 224 if (isTaskCallback) { |
| 225 verify(!isInTask, 'wrapCallback: already in task'); | |
| 226 isInTask = true; | |
| 227 } | |
| 228 if (!opt_dontRequire) | |
| 229 delete pendingCallbacks[callbackId]; | |
| 230 | |
| 231 // Call the original callback. | |
| 232 callback.apply(null, arguments); | |
| 233 | |
| 234 if (isTaskCallback) { | |
| 235 verify(isInTask, 'wrapCallback: not in task at exit'); | |
| 236 isInTask = false; | |
| 237 if (--taskPendingCallbackCount == 0) | |
| 238 finish(); | |
| 239 } | |
| 197 } catch (error) { | 240 } catch (error) { |
| 198 var message = 'Uncaught exception:\n' + error.stack; | 241 var message = 'Uncaught exception:\n' + error.stack; |
| 199 console.error(message); | 242 console.error(message); |
| 200 if (!errorReported) { | 243 if (!errorReported) { |
| 201 errorReported = true; | 244 errorReported = true; |
| 202 chrome.metricsPrivate.getIsCrashReportingEnabled(function(isEnabled) { | 245 chrome.metricsPrivate.getIsCrashReportingEnabled(function(isEnabled) { |
| 203 if (isEnabled) | 246 if (isEnabled) |
| 204 sendErrorReport(error); | 247 sendErrorReport(error); |
| 205 }); | 248 }); |
| 206 alert(message); | 249 alert(message); |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 224 alert('Cannot instrument ' + functionName); | 267 alert('Cannot instrument ' + functionName); |
| 225 | 268 |
| 226 namespace[functionName] = function() { | 269 namespace[functionName] = function() { |
| 227 // This is the wrapper for the API function. Pass the wrapped callback to | 270 // This is the wrapper for the API function. Pass the wrapped callback to |
| 228 // the original function. | 271 // the original function. |
| 229 var callback = arguments[callbackParameter]; | 272 var callback = arguments[callbackParameter]; |
| 230 if (typeof callback != 'function') { | 273 if (typeof callback != 'function') { |
| 231 alert('Argument ' + callbackParameter + ' of ' + functionName + | 274 alert('Argument ' + callbackParameter + ' of ' + functionName + |
| 232 ' is not a function'); | 275 ' is not a function'); |
| 233 } | 276 } |
| 234 arguments[callbackParameter] = wrapCallback(callback); | 277 arguments[callbackParameter] = wrapCallback( |
| 278 callback, functionName == 'addListener'); | |
| 235 return originalFunction.apply(namespace, arguments); | 279 return originalFunction.apply(namespace, arguments); |
| 236 }; | 280 }; |
| 237 } | 281 } |
| 238 | 282 |
| 239 instrumentApiFunction(chrome.alarms.onAlarm, 'addListener', 0); | 283 instrumentApiFunction(chrome.alarms.onAlarm, 'addListener', 0); |
| 240 instrumentApiFunction(chrome.runtime.onSuspend, 'addListener', 0); | 284 instrumentApiFunction(chrome.runtime.onSuspend, 'addListener', 0); |
| 241 | 285 |
| 242 chrome.runtime.onSuspend.addListener(function() { | 286 chrome.runtime.onSuspend.addListener(function() { |
| 287 var stringifiedPendingCallbacks = JSON.stringify(pendingCallbacks); | |
| 243 verify( | 288 verify( |
| 244 queue.length == 0, | 289 queue.length == 0 && stringifiedPendingCallbacks == '{}', |
| 245 'Incomplete task when unloading event page, queue = ' + | 290 'Incomplete task or pending callbacks when unloading event page,' + |
| 246 JSON.stringify(queue) + ', step = ' + stepName); | 291 ' queue = ' + JSON.stringify(queue) + |
| 247 verify( | 292 ', pendingCallbacks = ' + stringifiedPendingCallbacks); |
| 248 stepName == null, | |
| 249 'Step name not null when unloading event page, queue = ' + | |
| 250 JSON.stringify(queue) + ', step = ' + stepName); | |
| 251 }); | 293 }); |
| 252 | 294 |
| 253 return { | 295 return { |
| 254 add: add, | 296 add: add, |
| 255 // TODO(vadimt): Replace with instrumenting callbacks. | 297 debugSetStepName: function() {}, // TODO(vadimt): remove |
|
skare_
2013/07/22 23:09:54
nit: 2 spaces between code and comments.
Could thi
vadimt
2013/07/22 23:59:59
Removing this now would break code or would requir
| |
| 256 debugSetStepName: debugSetStepName, | |
| 257 instrumentApiFunction: instrumentApiFunction, | 298 instrumentApiFunction: instrumentApiFunction, |
| 258 wrapCallback: wrapCallback | 299 wrapCallback: wrapCallback |
| 259 }; | 300 }; |
| 260 } | 301 } |
| 261 | 302 |
| 262 var storage = chrome.storage.local; | 303 var storage = chrome.storage.local; |
| 263 | 304 |
| 264 /** | 305 /** |
| 265 * Builds an object to manage retrying activities with exponential backoff. | 306 * Builds an object to manage retrying activities with exponential backoff. |
| 266 * @param {string} name Name of this attempt manager. | 307 * @param {string} name Name of this attempt manager. |
| (...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 348 if (alarm.name == alarmName) | 389 if (alarm.name == alarmName) |
| 349 attempt(); | 390 attempt(); |
| 350 }); | 391 }); |
| 351 | 392 |
| 352 return { | 393 return { |
| 353 start: start, | 394 start: start, |
| 354 planForNext: planForNext, | 395 planForNext: planForNext, |
| 355 stop: stop | 396 stop: stop |
| 356 }; | 397 }; |
| 357 } | 398 } |
| OLD | NEW |