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 |