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 |