OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 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 cr.define('extensions', function() { | 5 cr.define('extensions', function() { |
6 'use strict'; | 6 'use strict'; |
7 | 7 |
8 /** | 8 /** |
9 * Clear all the content of a given element. | 9 * Clear all the content of a given element. |
10 * @param {HTMLElement} element The element to be cleared. | 10 * @param {HTMLElement} element The element to be cleared. |
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
109 assert(this.contextUrl_); | 109 assert(this.contextUrl_); |
110 }, | 110 }, |
111 | 111 |
112 /** | 112 /** |
113 * Sets the error for the content. | 113 * Sets the error for the content. |
114 * @param {(RuntimeError|ManifestError)} error The error whose content | 114 * @param {(RuntimeError|ManifestError)} error The error whose content |
115 * should be displayed. | 115 * should be displayed. |
116 * @param {string} extensionUrl The URL associated with this extension. | 116 * @param {string} extensionUrl The URL associated with this extension. |
117 */ | 117 */ |
118 setError: function(error, extensionUrl) { | 118 setError: function(error, extensionUrl) { |
| 119 this.clearError(); |
| 120 |
119 this.error_ = error; | 121 this.error_ = error; |
120 this.extensionUrl_ = extensionUrl; | 122 this.extensionUrl_ = extensionUrl; |
121 this.contextUrl_.textContent = error.contextUrl ? | 123 this.contextUrl_.textContent = error.contextUrl ? |
122 getRelativeUrl(error.contextUrl, this.extensionUrl_) : | 124 getRelativeUrl(error.contextUrl, this.extensionUrl_) : |
123 loadTimeData.getString('extensionErrorOverlayContextUnknown'); | 125 loadTimeData.getString('extensionErrorOverlayContextUnknown'); |
124 this.initStackTrace_(); | 126 this.initStackTrace_(); |
125 }, | 127 }, |
126 | 128 |
127 /** | 129 /** |
128 * Wipe content associated with a specific error. | 130 * Wipe content associated with a specific error. |
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
239 /** | 241 /** |
240 * The content section for runtime errors; this is re-used for all | 242 * The content section for runtime errors; this is re-used for all |
241 * runtime errors and attached/detached from the overlay as needed. | 243 * runtime errors and attached/detached from the overlay as needed. |
242 * @type {RuntimeErrorContent} | 244 * @type {RuntimeErrorContent} |
243 * @private | 245 * @private |
244 */ | 246 */ |
245 this.runtimeErrorContent_ = new RuntimeErrorContent(); | 247 this.runtimeErrorContent_ = new RuntimeErrorContent(); |
246 } | 248 } |
247 | 249 |
248 /** | 250 /** |
249 * Value of ExtensionError::RUNTIME_ERROR enum. | |
250 * @see extensions/browser/extension_error.h | |
251 * @type {number} | |
252 * @const | |
253 * @private | |
254 */ | |
255 ExtensionErrorOverlay.RUNTIME_ERROR_TYPE_ = 1; | |
256 | |
257 /** | |
258 * The manifest filename. | 251 * The manifest filename. |
259 * @type {string} | 252 * @type {string} |
260 * @const | 253 * @const |
261 * @private | 254 * @private |
262 */ | 255 */ |
263 ExtensionErrorOverlay.MANIFEST_FILENAME_ = 'manifest.json'; | 256 ExtensionErrorOverlay.MANIFEST_FILENAME_ = 'manifest.json'; |
264 | 257 |
265 /** | 258 /** |
266 * Determine whether or not chrome can load the source for a given file; this | 259 * Determine whether or not chrome can load the source for a given file; this |
267 * can only be done if the file belongs to the extension. | 260 * can only be done if the file belongs to the extension. |
268 * @param {string} file The file to load. | 261 * @param {string} file The file to load. |
269 * @param {string} extensionUrl The url for the extension, in the form | 262 * @param {string} extensionUrl The url for the extension, in the form |
270 * chrome-extension://<extension-id>/. | 263 * chrome-extension://<extension-id>/. |
271 * @return {boolean} True if the file can be loaded, false otherwise. | 264 * @return {boolean} True if the file can be loaded, false otherwise. |
272 * @private | 265 * @private |
273 */ | 266 */ |
274 ExtensionErrorOverlay.canLoadFileSource = function(file, extensionUrl) { | 267 ExtensionErrorOverlay.canLoadFileSource = function(file, extensionUrl) { |
275 return file.substr(0, extensionUrl.length) == extensionUrl || | 268 return file.substr(0, extensionUrl.length) == extensionUrl || |
276 file.toLowerCase() == ExtensionErrorOverlay.MANIFEST_FILENAME_; | 269 file.toLowerCase() == ExtensionErrorOverlay.MANIFEST_FILENAME_; |
277 }; | 270 }; |
278 | 271 |
279 /** | |
280 * Determine whether or not we can show an overlay with more details for | |
281 * the given extension error. | |
282 * @param {Object} error The extension error. | |
283 * @param {string} extensionUrl The url for the extension, in the form | |
284 * "chrome-extension://<extension-id>/". | |
285 * @return {boolean} True if we can show an overlay for the error, | |
286 * false otherwise. | |
287 */ | |
288 ExtensionErrorOverlay.canShowOverlayForError = function(error, extensionUrl) { | |
289 if (ExtensionErrorOverlay.canLoadFileSource(error.source, extensionUrl)) | |
290 return true; | |
291 | |
292 if (error.stackTrace) { | |
293 for (var i = 0; i < error.stackTrace.length; ++i) { | |
294 if (RuntimeErrorContent.shouldDisplayForUrl(error.stackTrace[i].url)) | |
295 return true; | |
296 } | |
297 } | |
298 | |
299 return false; | |
300 }; | |
301 | |
302 cr.addSingletonGetter(ExtensionErrorOverlay); | 272 cr.addSingletonGetter(ExtensionErrorOverlay); |
303 | 273 |
304 ExtensionErrorOverlay.prototype = { | 274 ExtensionErrorOverlay.prototype = { |
305 /** | 275 /** |
306 * The underlying error whose details are being displayed. | 276 * The underlying error whose details are being displayed. |
307 * @type {?(RuntimeError|ManifestError)} | 277 * @type {?(RuntimeError|ManifestError)} |
308 * @private | 278 * @private |
309 */ | 279 */ |
310 error_: null, | 280 selectedError_: null, |
311 | 281 |
312 /** | 282 /** |
313 * Initialize the page. | 283 * Initialize the page. |
314 * @param {function(HTMLDivElement)} showOverlay The function to show or | 284 * @param {function(HTMLDivElement)} showOverlay The function to show or |
315 * hide the ExtensionErrorOverlay; this should take a single parameter | 285 * hide the ExtensionErrorOverlay; this should take a single parameter |
316 * which is either the overlay Div if the overlay should be displayed, | 286 * which is either the overlay Div if the overlay should be displayed, |
317 * or null if the overlay should be hidden. | 287 * or null if the overlay should be hidden. |
318 */ | 288 */ |
319 initializePage: function(showOverlay) { | 289 initializePage: function(showOverlay) { |
320 var overlay = $('overlay'); | 290 var overlay = $('overlay'); |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
369 * Handles a click on the dismiss ("OK" or close) buttons. | 339 * Handles a click on the dismiss ("OK" or close) buttons. |
370 * @param {Event} e The click event. | 340 * @param {Event} e The click event. |
371 * @private | 341 * @private |
372 */ | 342 */ |
373 handleDismiss_: function(e) { | 343 handleDismiss_: function(e) { |
374 this.setVisible(false); | 344 this.setVisible(false); |
375 | 345 |
376 // There's a chance that the overlay receives multiple dismiss events; in | 346 // There's a chance that the overlay receives multiple dismiss events; in |
377 // this case, handle it gracefully and return (since all necessary work | 347 // this case, handle it gracefully and return (since all necessary work |
378 // will already have been done). | 348 // will already have been done). |
379 if (!this.error_) | 349 if (!this.selectedError_) |
380 return; | 350 return; |
381 | 351 |
382 // Remove all previous content. | 352 // Remove all previous content. |
383 this.codeDiv_.clear(); | 353 this.codeDiv_.clear(); |
384 | 354 |
385 this.openDevtoolsButton_.hidden = true; | 355 this.overlayDiv_.querySelector('.extension-error-list').onRemoved(); |
386 | 356 |
387 if (this.error_.type == ExtensionErrorOverlay.RUNTIME_ERROR_TYPE_) { | 357 this.clearRuntimeContent_(); |
388 this.overlayDiv_.querySelector('.content-area').removeChild( | 358 |
| 359 this.selectedError_ = null; |
| 360 }, |
| 361 |
| 362 /** |
| 363 * Clears the current content. |
| 364 * @private |
| 365 */ |
| 366 clearRuntimeContent_: function() { |
| 367 if (this.runtimeErrorContent_.parentNode) { |
| 368 this.runtimeErrorContent_.parentNode.removeChild( |
389 this.runtimeErrorContent_); | 369 this.runtimeErrorContent_); |
390 this.runtimeErrorContent_.clearError(); | 370 this.runtimeErrorContent_.clearError(); |
391 } | 371 } |
392 | 372 this.openDevtoolsButton_.hidden = true; |
393 this.error_ = null; | |
394 }, | 373 }, |
395 | 374 |
396 /** | 375 /** |
397 * Associate an error with the overlay. This will set the error for the | 376 * Sets the active error for the overlay. |
398 * overlay, and, if possible, will populate the code section of the overlay | 377 * @param {?(ManifestError|RuntimeError)} error The error to make active. |
399 * with the relevant file, load the stack trace, and generate links for | |
400 * opening devtools (the latter two only happen for runtime errors). | |
401 * @param {(RuntimeError|ManifestError)} error The error to show in the | |
402 * overlay. | |
403 * @param {string} extensionUrl The URL of the extension, in the form | |
404 * "chrome-extension://<extension_id>". | |
405 * TODO(dbeam): add URL externs and re-enable typechecking in this method. | 378 * TODO(dbeam): add URL externs and re-enable typechecking in this method. |
406 * @suppress {missingProperties} | 379 * @suppress {missingProperties} |
| 380 * @private |
407 */ | 381 */ |
408 setErrorAndShowOverlay: function(error, extensionUrl) { | 382 setActiveError_: function(error) { |
409 this.error_ = error; | 383 this.selectedError_ = error; |
410 | 384 |
411 if (this.error_.type == ExtensionErrorOverlay.RUNTIME_ERROR_TYPE_) { | 385 // If there is no error (this can happen if, e.g., the user deleted all |
412 this.runtimeErrorContent_.setError(this.error_, extensionUrl); | 386 // the errors), then clear the content. |
| 387 if (!error) { |
| 388 this.codeDiv_.populate( |
| 389 null, loadTimeData.getString('extensionErrorNoErrorsCodeMessage')); |
| 390 this.clearRuntimeContent_(); |
| 391 return; |
| 392 } |
| 393 |
| 394 var extensionUrl = 'chrome-extension://' + error.extensionId + '/'; |
| 395 // Set or hide runtime content. |
| 396 if (error.type == chrome.developerPrivate.ErrorType.RUNTIME) { |
| 397 this.runtimeErrorContent_.setError(error, extensionUrl); |
413 this.overlayDiv_.querySelector('.content-area').insertBefore( | 398 this.overlayDiv_.querySelector('.content-area').insertBefore( |
414 this.runtimeErrorContent_, | 399 this.runtimeErrorContent_, |
415 this.codeDiv_.nextSibling); | 400 this.codeDiv_.nextSibling); |
416 this.openDevtoolsButton_.hidden = false; | 401 this.openDevtoolsButton_.hidden = false; |
417 this.openDevtoolsButton_.disabled = !error.canInspect; | 402 this.openDevtoolsButton_.disabled = !error.canInspect; |
| 403 } else { |
| 404 this.clearRuntimeContent_(); |
418 } | 405 } |
419 | 406 |
| 407 // Read the file source to populate the code section, or set it to null if |
| 408 // the file is unreadable. |
420 if (ExtensionErrorOverlay.canLoadFileSource(error.source, extensionUrl)) { | 409 if (ExtensionErrorOverlay.canLoadFileSource(error.source, extensionUrl)) { |
421 // slice(1) because pathname starts with a /. | |
422 var pathname = new URL(error.source).pathname.slice(1); | |
423 | |
424 // Use pathname instead of relativeUrl. | 410 // Use pathname instead of relativeUrl. |
425 var requestFileSourceArgs = {extensionId: error.extensionId, | 411 var requestFileSourceArgs = {extensionId: error.extensionId, |
426 message: error.message, | 412 message: error.message}; |
427 pathSuffix: pathname}; | 413 switch (error.type) { |
428 | 414 case chrome.developerPrivate.ErrorType.MANIFEST: |
429 if (pathname.toLowerCase() == | 415 requestFileSourceArgs.pathSuffix = error.source; |
430 ExtensionErrorOverlay.MANIFEST_FILENAME_) { | 416 requestFileSourceArgs.manifestKey = error.manifestKey; |
431 requestFileSourceArgs.manifestKey = error.manifestKey; | 417 requestFileSourceArgs.manifestSpecific = error.manifestSpecific; |
432 requestFileSourceArgs.manifestSpecific = error.manifestSpecific; | 418 break; |
433 } else { | 419 case chrome.developerPrivate.ErrorType.RUNTIME: |
434 requestFileSourceArgs.lineNumber = | 420 // slice(1) because pathname starts with a /. |
435 error.stackTrace && error.stackTrace[0] ? | 421 var pathname = new URL(error.source).pathname.slice(1); |
436 error.stackTrace[0].lineNumber : 0; | 422 requestFileSourceArgs.pathSuffix = pathname; |
| 423 requestFileSourceArgs.lineNumber = |
| 424 error.stackTrace && error.stackTrace[0] ? |
| 425 error.stackTrace[0].lineNumber : 0; |
| 426 break; |
| 427 default: |
| 428 assertNotReached(); |
437 } | 429 } |
438 this.requestFileSource(requestFileSourceArgs); | 430 this.requestFileSource(requestFileSourceArgs); |
439 } else { | 431 } else { |
440 this.onFileSourceResponse_(null); | 432 this.onFileSourceResponse_(null); |
441 } | 433 } |
442 }, | 434 }, |
443 | 435 |
444 /** | 436 /** |
| 437 * Associate an error with the overlay. This will set the error for the |
| 438 * overlay, and, if possible, will populate the code section of the overlay |
| 439 * with the relevant file, load the stack trace, and generate links for |
| 440 * opening devtools (the latter two only happen for runtime errors). |
| 441 * @param {Array<(RuntimeError|ManifestError)>} errors The error to show in |
| 442 * the overlay. |
| 443 * @param {string} extensionId The id of the extension. |
| 444 * @param {string} extensionName The name of the extension. |
| 445 */ |
| 446 setErrorsAndShowOverlay: function(errors, extensionId, extensionName) { |
| 447 document.querySelector( |
| 448 '#extension-error-overlay .extension-error-overlay-title'). |
| 449 textContent = extensionName; |
| 450 var errorsDiv = this.overlayDiv_.querySelector('.extension-error-list'); |
| 451 var extensionErrors = |
| 452 new extensions.ExtensionErrorList(errors, extensionId); |
| 453 errorsDiv.parentNode.replaceChild(extensionErrors, errorsDiv); |
| 454 extensionErrors.addEventListener('activeExtensionErrorChanged', |
| 455 function(e) { |
| 456 this.setActiveError_(e.detail); |
| 457 }.bind(this)); |
| 458 |
| 459 if (errors.length > 0) |
| 460 this.setActiveError_(errors[0]); |
| 461 this.setVisible(true); |
| 462 }, |
| 463 |
| 464 /** |
445 * Requests a file's source. | 465 * Requests a file's source. |
446 * @param {RequestFileSourceProperties} args The arguments for the call. | 466 * @param {RequestFileSourceProperties} args The arguments for the call. |
447 */ | 467 */ |
448 requestFileSource: function(args) { | 468 requestFileSource: function(args) { |
449 chrome.developerPrivate.requestFileSource( | 469 chrome.developerPrivate.requestFileSource( |
450 args, this.onFileSourceResponse_.bind(this)); | 470 args, this.onFileSourceResponse_.bind(this)); |
451 }, | 471 }, |
452 | 472 |
453 /** | 473 /** |
454 * Set the code to be displayed in the code portion of the overlay. | 474 * Set the code to be displayed in the code portion of the overlay. |
455 * @see ExtensionErrorOverlay.requestFileSourceResponse(). | 475 * @see ExtensionErrorOverlay.requestFileSourceResponse(). |
456 * @param {?RequestFileSourceResponse} response The response from the | 476 * @param {?RequestFileSourceResponse} response The response from the |
457 * request file source call, which will be shown as code. If |response| | 477 * request file source call, which will be shown as code. If |response| |
458 * is null, then a "Could not display code" message will be displayed | 478 * is null, then a "Could not display code" message will be displayed |
459 * instead. | 479 * instead. |
460 */ | 480 */ |
461 onFileSourceResponse_: function(response) { | 481 onFileSourceResponse_: function(response) { |
462 if (response) { | |
463 document.querySelector( | |
464 '#extension-error-overlay .extension-error-overlay-title'). | |
465 textContent = response.title; | |
466 } | |
467 this.codeDiv_.populate( | 482 this.codeDiv_.populate( |
468 response, // ExtensionCode can handle a null response. | 483 response, // ExtensionCode can handle a null response. |
469 loadTimeData.getString('extensionErrorOverlayNoCodeToDisplay')); | 484 loadTimeData.getString('extensionErrorOverlayNoCodeToDisplay')); |
470 this.setVisible(true); | 485 this.setVisible(true); |
471 }, | 486 }, |
472 }; | 487 }; |
473 | 488 |
474 // Export | 489 // Export |
475 return { | 490 return { |
476 ExtensionErrorOverlay: ExtensionErrorOverlay | 491 ExtensionErrorOverlay: ExtensionErrorOverlay |
477 }; | 492 }; |
478 }); | 493 }); |
OLD | NEW |