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 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 54 */ | 54 */ |
| 55 function buildTaskManager(areConflicting) { | 55 function buildTaskManager(areConflicting) { |
| 56 /** | 56 /** |
| 57 * Queue of scheduled tasks. The first element, if present, corresponds to the | 57 * Queue of scheduled tasks. The first element, if present, corresponds to the |
| 58 * currently running task. | 58 * currently running task. |
| 59 * @type {Array.<Object.<string, function(function())>>} | 59 * @type {Array.<Object.<string, function(function())>>} |
| 60 */ | 60 */ |
| 61 var queue = []; | 61 var queue = []; |
| 62 | 62 |
| 63 /** | 63 /** |
| 64 * Name of the current step of the currently running task if present, | 64 * Count of unfinished callbacks of the current task. |
| 65 * otherwise, null. For diagnostics only. | 65 * @type {number} |
| 66 * It's set when the task is started and before each asynchronous operation. | |
| 67 */ | 66 */ |
| 68 var stepName = null; | 67 var taskPendingCallbackCount = 0; |
| 68 | |
| 69 /** | |
| 70 * Required callbacks that are not yet called. Includes both task and non-task | |
| 71 * callbacks. This is a map from unique callback id to the stack at the moment | |
| 72 * when the callback was wrapped. This stack identifies the callback. | |
| 73 * Used only for diagnostics. | |
| 74 * @type {Object.<number, string>} | |
| 75 */ | |
| 76 var pendingCallbacks = {}; | |
| 77 | |
| 78 /** | |
| 79 * True if currently executed code is a part of a task. | |
| 80 * @type {boolean} | |
| 81 */ | |
| 82 var isInTask = false; | |
|
skare_
2013/07/23 21:10:52
maybe "Whether a task is currently running?"
"cur
vadimt
2013/07/23 22:35:51
"Whether a task is currently running" may be confu
| |
| 69 | 83 |
| 70 /** | 84 /** |
| 71 * Starts the first queued task. | 85 * Starts the first queued task. |
| 72 */ | 86 */ |
| 73 function startFirst() { | 87 function startFirst() { |
| 74 verify(queue.length >= 1, 'startFirst: queue is empty'); | 88 verify(queue.length >= 1, 'startFirst: queue is empty'); |
| 89 verify(!isInTask, 'startFirst: already in task'); | |
| 90 isInTask = true; | |
| 75 | 91 |
| 76 // Start the oldest queued task, but don't remove it from the queue. | 92 // Start the oldest queued task, but don't remove it from the queue. |
| 77 verify( | 93 verify( |
| 78 stepName == null, | 94 taskPendingCallbackCount == 0, |
| 79 'tasks.startFirst: stepName is not null: ' + stepName + | 95 'tasks.startFirst: still have pending task callbacks: ' + |
| 80 ', queue = ' + JSON.stringify(queue)); | 96 taskPendingCallbackCount + |
| 97 ', queue = ' + JSON.stringify(queue) + | |
| 98 ', pendingCallbacks = ' + JSON.stringify(pendingCallbacks)); | |
| 81 var entry = queue[0]; | 99 var entry = queue[0]; |
| 82 stepName = entry.name + '-initial'; | |
| 83 console.log('Starting task ' + entry.name); | 100 console.log('Starting task ' + entry.name); |
| 84 entry.task(finish); | 101 |
| 102 entry.task(function() {}); // TODO(vadimt): Don't pass parameter. | |
| 103 | |
| 104 verify(isInTask, 'startFirst: not in task at exit'); | |
| 105 isInTask = false; | |
| 106 if (taskPendingCallbackCount == 0) | |
| 107 finish(); | |
| 85 } | 108 } |
| 86 | 109 |
| 87 /** | 110 /** |
| 88 * Checks if a new task can be added to the task queue. | 111 * Checks if a new task can be added to the task queue. |
| 89 * @param {string} taskName Name of the new task. | 112 * @param {string} taskName Name of the new task. |
| 90 * @return {boolean} Whether the new task can be added. | 113 * @return {boolean} Whether the new task can be added. |
| 91 */ | 114 */ |
| 92 function canQueue(taskName) { | 115 function canQueue(taskName) { |
| 93 for (var i = 0; i < queue.length; ++i) { | 116 for (var i = 0; i < queue.length; ++i) { |
| 94 if (areConflicting(taskName, queue[i].name)) { | 117 if (areConflicting(taskName, queue[i].name)) { |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 119 if (queue.length == 1) { | 142 if (queue.length == 1) { |
| 120 startFirst(); | 143 startFirst(); |
| 121 } | 144 } |
| 122 } | 145 } |
| 123 | 146 |
| 124 /** | 147 /** |
| 125 * Completes the current task and starts the next queued task if available. | 148 * Completes the current task and starts the next queued task if available. |
| 126 */ | 149 */ |
| 127 function finish() { | 150 function finish() { |
| 128 verify(queue.length >= 1, | 151 verify(queue.length >= 1, |
| 129 'tasks.finish: The task queue is empty; step = ' + stepName); | 152 'tasks.finish: The task queue is empty'); |
| 130 console.log('Finishing task ' + queue[0].name); | 153 console.log('Finishing task ' + queue[0].name); |
| 131 queue.shift(); | 154 queue.shift(); |
| 132 stepName = null; | |
| 133 | 155 |
| 134 if (queue.length >= 1) | 156 if (queue.length >= 1) |
| 135 startFirst(); | 157 startFirst(); |
| 136 } | 158 } |
| 137 | 159 |
| 138 /** | |
| 139 * Associates a name with the current step of the task. Used for diagnostics | |
| 140 * only. A task is a chain of asynchronous events; debugSetStepName should be | |
| 141 * called before starting any asynchronous operation. | |
| 142 * @param {string} step Name of new step. | |
| 143 */ | |
| 144 function debugSetStepName(step) { | |
| 145 stepName = step; | |
| 146 } | |
| 147 | |
| 148 // Limiting 1 error report per background page load. | 160 // Limiting 1 error report per background page load. |
| 149 var errorReported = false; | 161 var errorReported = false; |
| 150 | 162 |
| 151 /** | 163 /** |
| 152 * Sends an error report to the server. | 164 * Sends an error report to the server. |
| 153 * @param {Error} error Error to report. | 165 * @param {Error} error Error to report. |
| 154 */ | 166 */ |
| 155 function sendErrorReport(error) { | 167 function sendErrorReport(error) { |
| 156 var filteredStack = error.stack.replace(/.*\n/, '\n'); | 168 var filteredStack = error.stack.replace(/.*\n/, '\n'); |
| 157 var file; | 169 var file; |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 180 '&trace=' + encodeURIComponent(filteredStack); | 192 '&trace=' + encodeURIComponent(filteredStack); |
| 181 var request = buildServerRequest('jserror', | 193 var request = buildServerRequest('jserror', |
| 182 'application/x-www-form-urlencoded'); | 194 'application/x-www-form-urlencoded'); |
| 183 request.onloadend = function(event) { | 195 request.onloadend = function(event) { |
| 184 console.log('sendErrorReport status: ' + request.status); | 196 console.log('sendErrorReport status: ' + request.status); |
| 185 }; | 197 }; |
| 186 request.send(requestParameters); | 198 request.send(requestParameters); |
| 187 } | 199 } |
| 188 | 200 |
| 189 /** | 201 /** |
| 202 * Unique ID of the next callback. | |
| 203 * @type {number} | |
| 204 */ | |
| 205 var nextCallbackId = 0; | |
| 206 | |
| 207 /** | |
| 190 * Adds error processing to an API callback. | 208 * Adds error processing to an API callback. |
| 191 * @param {Function} callback Callback to instrument. | 209 * @param {Function} callback Callback to instrument. |
| 210 * @param {boolean=} opt_dontRequire True if the callback is not required to | |
|
skare_
2013/07/23 21:10:52
more comments or logic flip could help here, just
vadimt
2013/07/23 22:35:51
You may call this method without passing this para
| |
| 211 * be invoked. | |
| 192 * @return {Function} Instrumented callback. | 212 * @return {Function} Instrumented callback. |
| 193 */ | 213 */ |
| 194 function wrapCallback(callback) { | 214 function wrapCallback(callback, opt_dontRequire) { |
| 215 verify(!(opt_dontRequire && isInTask), 'Unrequired callback in a task.'); | |
| 216 var callbackId = nextCallbackId++; | |
| 217 var isTaskCallback = isInTask; | |
| 218 if (isInTask) | |
| 219 ++taskPendingCallbackCount; | |
| 220 if (!opt_dontRequire) | |
| 221 pendingCallbacks[callbackId] = new Error().stack; | |
| 222 | |
| 195 return function() { | 223 return function() { |
| 196 // This is the wrapper for the callback. | 224 // This is the wrapper for the callback. |
| 197 try { | 225 try { |
| 198 return callback.apply(null, arguments); | 226 if (isTaskCallback) { |
| 227 verify(!isInTask, 'wrapCallback: already in task'); | |
| 228 isInTask = true; | |
| 229 } | |
| 230 if (!opt_dontRequire) | |
| 231 delete pendingCallbacks[callbackId]; | |
| 232 | |
| 233 // Call the original callback. | |
| 234 callback.apply(null, arguments); | |
| 235 | |
| 236 if (isTaskCallback) { | |
| 237 verify(isInTask, 'wrapCallback: not in task at exit'); | |
| 238 isInTask = false; | |
| 239 if (--taskPendingCallbackCount == 0) | |
| 240 finish(); | |
| 241 } | |
| 199 } catch (error) { | 242 } catch (error) { |
| 200 var message = 'Uncaught exception:\n' + error.stack; | 243 var message = 'Uncaught exception:\n' + error.stack; |
| 201 console.error(message); | 244 console.error(message); |
| 202 if (!errorReported) { | 245 if (!errorReported) { |
| 203 errorReported = true; | 246 errorReported = true; |
| 204 chrome.metricsPrivate.getIsCrashReportingEnabled(function(isEnabled) { | 247 chrome.metricsPrivate.getIsCrashReportingEnabled(function(isEnabled) { |
| 205 if (isEnabled) | 248 if (isEnabled) |
| 206 sendErrorReport(error); | 249 sendErrorReport(error); |
| 207 }); | 250 }); |
| 208 alert(message); | 251 alert(message); |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 226 alert('Cannot instrument ' + functionName); | 269 alert('Cannot instrument ' + functionName); |
| 227 | 270 |
| 228 namespace[functionName] = function() { | 271 namespace[functionName] = function() { |
| 229 // This is the wrapper for the API function. Pass the wrapped callback to | 272 // This is the wrapper for the API function. Pass the wrapped callback to |
| 230 // the original function. | 273 // the original function. |
| 231 var callback = arguments[callbackParameter]; | 274 var callback = arguments[callbackParameter]; |
| 232 if (typeof callback != 'function') { | 275 if (typeof callback != 'function') { |
| 233 alert('Argument ' + callbackParameter + ' of ' + functionName + | 276 alert('Argument ' + callbackParameter + ' of ' + functionName + |
| 234 ' is not a function'); | 277 ' is not a function'); |
| 235 } | 278 } |
| 236 arguments[callbackParameter] = wrapCallback(callback); | 279 arguments[callbackParameter] = wrapCallback( |
| 280 callback, functionName == 'addListener'); | |
| 237 return originalFunction.apply(namespace, arguments); | 281 return originalFunction.apply(namespace, arguments); |
| 238 }; | 282 }; |
| 239 } | 283 } |
| 240 | 284 |
| 241 instrumentApiFunction(chrome.alarms.onAlarm, 'addListener', 0); | 285 instrumentApiFunction(chrome.alarms.onAlarm, 'addListener', 0); |
| 242 instrumentApiFunction(chrome.runtime.onSuspend, 'addListener', 0); | 286 instrumentApiFunction(chrome.runtime.onSuspend, 'addListener', 0); |
| 243 | 287 |
| 244 chrome.runtime.onSuspend.addListener(function() { | 288 chrome.runtime.onSuspend.addListener(function() { |
| 289 var stringifiedPendingCallbacks = JSON.stringify(pendingCallbacks); | |
| 245 verify( | 290 verify( |
| 246 queue.length == 0, | 291 queue.length == 0 && stringifiedPendingCallbacks == '{}', |
| 247 'Incomplete task when unloading event page, queue = ' + | 292 'Incomplete task or pending callbacks when unloading event page,' + |
| 248 JSON.stringify(queue) + ', step = ' + stepName); | 293 ' queue = ' + JSON.stringify(queue) + |
| 249 verify( | 294 ', pendingCallbacks = ' + stringifiedPendingCallbacks); |
| 250 stepName == null, | |
| 251 'Step name not null when unloading event page, queue = ' + | |
| 252 JSON.stringify(queue) + ', step = ' + stepName); | |
| 253 }); | 295 }); |
| 254 | 296 |
| 255 return { | 297 return { |
| 256 add: add, | 298 add: add, |
| 257 // TODO(vadimt): Replace with instrumenting callbacks. | 299 debugSetStepName: function() {}, // TODO(vadimt): remove |
| 258 debugSetStepName: debugSetStepName, | |
| 259 instrumentApiFunction: instrumentApiFunction, | 300 instrumentApiFunction: instrumentApiFunction, |
| 260 wrapCallback: wrapCallback | 301 wrapCallback: wrapCallback |
| 261 }; | 302 }; |
| 262 } | 303 } |
| 263 | 304 |
| 264 var storage = chrome.storage.local; | 305 var storage = chrome.storage.local; |
| 265 | 306 |
| 266 /** | 307 /** |
| 267 * Builds an object to manage retrying activities with exponential backoff. | 308 * Builds an object to manage retrying activities with exponential backoff. |
| 268 * @param {string} name Name of this attempt manager. | 309 * @param {string} name Name of this attempt manager. |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 331 chrome.alarms.clear(alarmName); | 372 chrome.alarms.clear(alarmName); |
| 332 storage.remove(currentDelayStorageKey); | 373 storage.remove(currentDelayStorageKey); |
| 333 } | 374 } |
| 334 | 375 |
| 335 /** | 376 /** |
| 336 * Plans for the next attempt. | 377 * Plans for the next attempt. |
| 337 * @param {function()} callback Completion callback. It will be invoked after | 378 * @param {function()} callback Completion callback. It will be invoked after |
| 338 * the planning is done. | 379 * the planning is done. |
| 339 */ | 380 */ |
| 340 function planForNext(callback) { | 381 function planForNext(callback) { |
| 341 tasks.debugSetStepName('planForNext-get-storage'); | 382 tasks.debugSetStepName('planForNext-get-storage'); |
|
rgustafson
2013/07/24 19:53:02
Can we remove from here right now? Or is this also
vadimt
2013/07/24 20:01:00
Done.
| |
| 342 storage.get(currentDelayStorageKey, function(items) { | 383 storage.get(currentDelayStorageKey, function(items) { |
| 343 console.log('planForNext-get-storage ' + JSON.stringify(items)); | 384 console.log('planForNext-get-storage ' + JSON.stringify(items)); |
| 344 scheduleNextAttempt(items[currentDelayStorageKey]); | 385 scheduleNextAttempt(items[currentDelayStorageKey]); |
| 345 callback(); | 386 callback(); |
| 346 }); | 387 }); |
| 347 } | 388 } |
| 348 | 389 |
| 349 chrome.alarms.onAlarm.addListener(function(alarm) { | 390 chrome.alarms.onAlarm.addListener(function(alarm) { |
| 350 if (alarm.name == alarmName) | 391 if (alarm.name == alarmName) |
| 351 attempt(); | 392 attempt(); |
| 352 }); | 393 }); |
| 353 | 394 |
| 354 return { | 395 return { |
| 355 start: start, | 396 start: start, |
| 356 planForNext: planForNext, | 397 planForNext: planForNext, |
| 357 stop: stop | 398 stop: stop |
| 358 }; | 399 }; |
| 359 } | 400 } |
| OLD | NEW |