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 /** | |
6 * The type of the stack trace object. The definition is based on | |
7 * extensions/browser/extension_error.cc:RuntimeError::ToValue(). | |
8 * @typedef {{columnNumber: number, | |
9 * functionName: string, | |
10 * lineNumber: number, | |
11 * url: string}} | |
12 */ | |
13 var StackTrace; | |
14 | |
15 /** | |
16 * The type of the extension error trace object. The definition is based on | |
17 * extensions/browser/extension_error.cc:RuntimeError::ToValue(). | |
18 * @typedef {{canInspect: (boolean|undefined), | |
19 * contextUrl: (string|undefined), | |
20 * extensionId: string, | |
21 * fromIncognito: boolean, | |
22 * level: number, | |
23 * manifestKey: string, | |
24 * manifestSpecific: string, | |
25 * message: string, | |
26 * renderProcessId: (number|undefined), | |
27 * renderViewId: (number|undefined), | |
28 * source: string, | |
29 * stackTrace: (Array.<StackTrace>|undefined), | |
30 * type: number}} | |
31 */ | |
32 var RuntimeError; | |
33 | |
34 cr.define('extensions', function() { | 5 cr.define('extensions', function() { |
35 'use strict'; | 6 'use strict'; |
36 | 7 |
37 /** | 8 /** |
38 * Clear all the content of a given element. | 9 * Clear all the content of a given element. |
39 * @param {HTMLElement} element The element to be cleared. | 10 * @param {HTMLElement} element The element to be cleared. |
40 */ | 11 */ |
41 function clearElement(element) { | 12 function clearElement(element) { |
42 while (element.firstChild) | 13 while (element.firstChild) |
43 element.removeChild(element.firstChild); | 14 element.removeChild(element.firstChild); |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
107 chrome.developerPrivate.openDevTools(args); | 78 chrome.developerPrivate.openDevTools(args); |
108 else | 79 else |
109 assertNotReached('Cannot call either openDevTools function.'); | 80 assertNotReached('Cannot call either openDevTools function.'); |
110 }; | 81 }; |
111 | 82 |
112 RuntimeErrorContent.prototype = { | 83 RuntimeErrorContent.prototype = { |
113 __proto__: HTMLDivElement.prototype, | 84 __proto__: HTMLDivElement.prototype, |
114 | 85 |
115 /** | 86 /** |
116 * The underlying error whose details are being displayed. | 87 * The underlying error whose details are being displayed. |
117 * @type {?RuntimeError} | 88 * @type {Object} |
118 * @private | 89 * @private |
119 */ | 90 */ |
120 error_: null, | 91 error_: undefined, |
121 | 92 |
122 /** | 93 /** |
123 * The URL associated with this extension, i.e. chrome-extension://<id>/. | 94 * The URL associated with this extension, i.e. chrome-extension://<id>/. |
124 * @type {?string} | 95 * @type {string} |
125 * @private | 96 * @private |
126 */ | 97 */ |
127 extensionUrl_: null, | 98 extensionUrl_: undefined, |
128 | 99 |
129 /** | 100 /** |
130 * The node of the stack trace which is currently active. | 101 * The node of the stack trace which is currently active. |
131 * @type {?HTMLElement} | 102 * @type {HTMLElement} |
132 * @private | 103 * @private |
133 */ | 104 */ |
134 currentFrameNode_: null, | 105 currentFrameNode_: undefined, |
135 | 106 |
136 /** | 107 /** |
137 * Initialize the RuntimeErrorContent for the first time. | 108 * Initialize the RuntimeErrorContent for the first time. |
138 */ | 109 */ |
139 init: function() { | 110 init: function() { |
140 /** | 111 /** |
141 * The stack trace element in the overlay. | 112 * The stack trace element in the overlay. |
142 * @type {HTMLElement} | 113 * @type {HTMLElement} |
143 * @private | 114 * @private |
144 */ | 115 */ |
145 this.stackTrace_ = /** @type {HTMLElement} */( | 116 this.stackTrace_ = |
146 this.querySelector('.extension-error-overlay-stack-trace-list')); | 117 this.querySelector('.extension-error-overlay-stack-trace-list'); |
147 assert(this.stackTrace_); | 118 assert(this.stackTrace_); |
148 | 119 |
149 /** | 120 /** |
150 * The context URL element in the overlay. | 121 * The context URL element in the overlay. |
151 * @type {HTMLElement} | 122 * @type {HTMLElement} |
152 * @private | 123 * @private |
153 */ | 124 */ |
154 this.contextUrl_ = /** @type {HTMLElement} */( | 125 this.contextUrl_ = |
155 this.querySelector('.extension-error-overlay-context-url')); | 126 this.querySelector('.extension-error-overlay-context-url'); |
156 assert(this.contextUrl_); | 127 assert(this.contextUrl_); |
157 }, | 128 }, |
158 | 129 |
159 /** | 130 /** |
160 * Sets the error for the content. | 131 * Sets the error for the content. |
161 * @param {RuntimeError} error The error whose content should | 132 * @param {Object} error The error whose content should be displayed. |
162 * be displayed. | |
163 * @param {string} extensionUrl The URL associated with this extension. | 133 * @param {string} extensionUrl The URL associated with this extension. |
164 */ | 134 */ |
165 setError: function(error, extensionUrl) { | 135 setError: function(error, extensionUrl) { |
166 this.error_ = error; | 136 this.error_ = error; |
167 this.extensionUrl_ = extensionUrl; | 137 this.extensionUrl_ = extensionUrl; |
168 this.contextUrl_.textContent = error.contextUrl ? | 138 this.contextUrl_.textContent = error.contextUrl ? |
169 getRelativeUrl(error.contextUrl, this.extensionUrl_) : | 139 getRelativeUrl(error.contextUrl, this.extensionUrl_) : |
170 loadTimeData.getString('extensionErrorOverlayContextUnknown'); | 140 loadTimeData.getString('extensionErrorOverlayContextUnknown'); |
171 this.initStackTrace_(); | 141 this.initStackTrace_(); |
172 }, | 142 }, |
173 | 143 |
174 /** | 144 /** |
175 * Wipe content associated with a specific error. | 145 * Wipe content associated with a specific error. |
176 */ | 146 */ |
177 clearError: function() { | 147 clearError: function() { |
178 this.error_ = null; | 148 this.error_ = undefined; |
179 this.extensionUrl_ = null; | 149 this.extensionUrl_ = undefined; |
180 this.currentFrameNode_ = null; | 150 this.currentFrameNode_ = undefined; |
181 clearElement(this.stackTrace_); | 151 clearElement(this.stackTrace_); |
182 this.stackTrace_.hidden = true; | 152 this.stackTrace_.hidden = true; |
183 }, | 153 }, |
184 | 154 |
185 /** | 155 /** |
186 * Makes |frame| active and deactivates the previously active frame (if | 156 * Makes |frame| active and deactivates the previously active frame (if |
187 * there was one). | 157 * there was one). |
188 * @param {HTMLElement} frameNode The frame to activate. | 158 * @param {HTMLElement} frame The frame to activate. |
189 * @private | 159 * @private |
190 */ | 160 */ |
191 setActiveFrame_: function(frameNode) { | 161 setActiveFrame_: function(frameNode) { |
192 if (this.currentFrameNode_) { | 162 if (this.currentFrameNode_) { |
193 this.currentFrameNode_.classList.remove( | 163 this.currentFrameNode_.classList.remove( |
194 RuntimeErrorContent.ACTIVE_CLASS_NAME); | 164 RuntimeErrorContent.ACTIVE_CLASS_NAME); |
195 } | 165 } |
196 | 166 |
197 this.currentFrameNode_ = frameNode; | 167 this.currentFrameNode_ = frameNode; |
198 this.currentFrameNode_.classList.add( | 168 this.currentFrameNode_.classList.add( |
(...skipping 13 matching lines...) Expand all Loading... |
212 continue; | 182 continue; |
213 | 183 |
214 var frameNode = document.createElement('li'); | 184 var frameNode = document.createElement('li'); |
215 // Attach the index of the frame to which this node refers (since we | 185 // Attach the index of the frame to which this node refers (since we |
216 // may skip some, this isn't a 1-to-1 match). | 186 // may skip some, this isn't a 1-to-1 match). |
217 frameNode.indexIntoTrace = i; | 187 frameNode.indexIntoTrace = i; |
218 | 188 |
219 // The description is a human-readable summation of the frame, in the | 189 // The description is a human-readable summation of the frame, in the |
220 // form "<relative_url>:<line_number> (function)", e.g. | 190 // form "<relative_url>:<line_number> (function)", e.g. |
221 // "myfile.js:25 (myFunction)". | 191 // "myfile.js:25 (myFunction)". |
222 var description = getRelativeUrl(frame.url, | 192 var description = getRelativeUrl(frame.url, this.extensionUrl_) + |
223 assert(this.extensionUrl_)) + ':' + frame.lineNumber; | 193 ':' + frame.lineNumber; |
224 if (frame.functionName) { | 194 if (frame.functionName) { |
225 var functionName = frame.functionName == '(anonymous function)' ? | 195 var functionName = frame.functionName == '(anonymous function)' ? |
226 loadTimeData.getString('extensionErrorOverlayAnonymousFunction') : | 196 loadTimeData.getString('extensionErrorOverlayAnonymousFunction') : |
227 frame.functionName; | 197 frame.functionName; |
228 description += ' (' + functionName + ')'; | 198 description += ' (' + functionName + ')'; |
229 } | 199 } |
230 frameNode.textContent = description; | 200 frameNode.textContent = description; |
231 | 201 |
232 // When the user clicks on a frame in the stack trace, we should | 202 // When the user clicks on a frame in the stack trace, we should |
233 // highlight that overlay in the list, display the appropriate source | 203 // highlight that overlay in the list, display the appropriate source |
234 // code with the line highlighted, and link the "Open DevTools" button | 204 // code with the line highlighted, and link the "Open DevTools" button |
235 // with that frame. | 205 // with that frame. |
236 frameNode.addEventListener('click', function(frame, frameNode, e) { | 206 frameNode.addEventListener('click', function(frame, frameNode, e) { |
| 207 if (this.currStackFrame_ == frameNode) |
| 208 return; |
| 209 |
237 this.setActiveFrame_(frameNode); | 210 this.setActiveFrame_(frameNode); |
238 | 211 |
239 // Request the file source with the section highlighted; this will | 212 // Request the file source with the section highlighted; this will |
240 // call ExtensionErrorOverlay.requestFileSourceResponse() when | 213 // call ExtensionErrorOverlay.requestFileSourceResponse() when |
241 // completed, which in turn calls setCode(). | 214 // completed, which in turn calls setCode(). |
242 ExtensionErrorOverlay.requestFileSource( | 215 ExtensionErrorOverlay.requestFileSource( |
243 {extensionId: this.error_.extensionId, | 216 {extensionId: this.error_.extensionId, |
244 message: this.error_.message, | 217 message: this.error_.message, |
245 pathSuffix: getRelativeUrl(frame.url, this.extensionUrl_), | 218 pathSuffix: getRelativeUrl(frame.url, this.extensionUrl_), |
246 lineNumber: frame.lineNumber}); | 219 lineNumber: frame.lineNumber}); |
247 }.bind(this, frame, frameNode)); | 220 }.bind(this, frame, frameNode)); |
248 | 221 |
249 this.stackTrace_.appendChild(frameNode); | 222 this.stackTrace_.appendChild(frameNode); |
250 } | 223 } |
251 | 224 |
252 // Set the current stack frame to the first stack frame and show the | 225 // Set the current stack frame to the first stack frame and show the |
253 // trace, if one exists. (We can't just check error.stackTrace, because | 226 // trace, if one exists. (We can't just check error.stackTrace, because |
254 // it's possible the trace was purely internal, and we don't show | 227 // it's possible the trace was purely internal, and we don't show |
255 // internal frames.) | 228 // internal frames.) |
256 if (this.stackTrace_.children.length > 0) { | 229 if (this.stackTrace_.children.length > 0) { |
257 this.stackTrace_.hidden = false; | 230 this.stackTrace_.hidden = false; |
258 this.setActiveFrame_(assertInstanceof(this.stackTrace_.firstChild, | 231 this.setActiveFrame_(this.stackTrace_.firstChild); |
259 HTMLElement)); | |
260 } | 232 } |
261 }, | 233 }, |
262 | 234 |
263 /** | 235 /** |
264 * Open the developer tools for the active stack frame. | 236 * Open the developer tools for the active stack frame. |
265 */ | 237 */ |
266 openDevtools: function() { | 238 openDevtools: function() { |
267 var stackFrame = | 239 var stackFrame = |
268 this.error_.stackTrace[this.currentFrameNode_.indexIntoTrace]; | 240 this.error_.stackTrace[this.currentFrameNode_.indexIntoTrace]; |
269 | 241 |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
365 } else { | 337 } else { |
366 assertNotReached('Cannot call either requestFileSource function.'); | 338 assertNotReached('Cannot call either requestFileSource function.'); |
367 } | 339 } |
368 }; | 340 }; |
369 | 341 |
370 cr.addSingletonGetter(ExtensionErrorOverlay); | 342 cr.addSingletonGetter(ExtensionErrorOverlay); |
371 | 343 |
372 ExtensionErrorOverlay.prototype = { | 344 ExtensionErrorOverlay.prototype = { |
373 /** | 345 /** |
374 * The underlying error whose details are being displayed. | 346 * The underlying error whose details are being displayed. |
375 * @type {?RuntimeError} | 347 * @type {Object} |
376 * @private | 348 * @private |
377 */ | 349 */ |
378 error_: null, | 350 error_: undefined, |
379 | 351 |
380 /** | 352 /** |
381 * Initialize the page. | 353 * Initialize the page. |
382 * @param {function(HTMLDivElement)} showOverlay The function to show or | 354 * @param {function(HTMLDivElement)} showOverlay The function to show or |
383 * hide the ExtensionErrorOverlay; this should take a single parameter | 355 * hide the ExtensionErrorOverlay; this should take a single parameter |
384 * which is either the overlay Div if the overlay should be displayed, | 356 * which is either the overlay Div if the overlay should be displayed, |
385 * or null if the overlay should be hidden. | 357 * or null if the overlay should be hidden. |
386 */ | 358 */ |
387 initializePage: function(showOverlay) { | 359 initializePage: function(showOverlay) { |
388 var overlay = $('overlay'); | 360 var overlay = $('overlay'); |
389 cr.ui.overlay.setupOverlay(overlay); | 361 cr.ui.overlay.setupOverlay(overlay); |
390 cr.ui.overlay.globalInitialization(); | 362 cr.ui.overlay.globalInitialization(); |
391 overlay.addEventListener('cancelOverlay', this.handleDismiss_.bind(this)); | 363 overlay.addEventListener('cancelOverlay', this.handleDismiss_.bind(this)); |
392 | 364 |
393 $('extension-error-overlay-dismiss').addEventListener( | 365 $('extension-error-overlay-dismiss').addEventListener( |
394 'click', this.handleDismiss_.bind(this)); | 366 'click', this.handleDismiss_.bind(this)); |
395 | 367 |
396 /** | 368 /** |
397 * The element of the full overlay. | 369 * The element of the full overlay. |
398 * @type {HTMLDivElement} | 370 * @type {HTMLDivElement} |
399 * @private | 371 * @private |
400 */ | 372 */ |
401 this.overlayDiv_ = /** @type {HTMLDivElement} */( | 373 this.overlayDiv_ = $('extension-error-overlay'); |
402 $('extension-error-overlay')); | |
403 | 374 |
404 /** | 375 /** |
405 * The portion of the overlay which shows the code relating to the error | 376 * The portion of the overlay which shows the code relating to the error |
406 * and the corresponding line numbers. | 377 * and the corresponding line numbers. |
407 * @type {extensions.ExtensionCode} | 378 * @type {ExtensionCode} |
408 * @private | 379 * @private |
409 */ | 380 */ |
410 this.codeDiv_ = | 381 this.codeDiv_ = |
411 new extensions.ExtensionCode($('extension-error-overlay-code')); | 382 new extensions.ExtensionCode($('extension-error-overlay-code')); |
412 | 383 |
413 /** | 384 /** |
414 * The function to show or hide the ExtensionErrorOverlay. | 385 * The function to show or hide the ExtensionErrorOverlay. |
| 386 * @type {function} |
415 * @param {boolean} isVisible Whether the overlay should be visible. | 387 * @param {boolean} isVisible Whether the overlay should be visible. |
416 */ | 388 */ |
417 this.setVisible = function(isVisible) { | 389 this.setVisible = function(isVisible) { |
418 showOverlay(isVisible ? this.overlayDiv_ : null); | 390 showOverlay(isVisible ? this.overlayDiv_ : null); |
419 if (isVisible) | 391 if (isVisible) |
420 this.codeDiv_.scrollToError(); | 392 this.codeDiv_.scrollToError(); |
421 }; | 393 }; |
422 | 394 |
423 /** | 395 /** |
424 * The button to open the developer tools (only available for runtime | 396 * The button to open the developer tools (only available for runtime |
425 * errors). | 397 * errors). |
426 * @type {HTMLButtonElement} | 398 * @type {HTMLButtonElement} |
427 * @private | 399 * @private |
428 */ | 400 */ |
429 this.openDevtoolsButton_ = /** @type {HTMLButtonElement} */( | 401 this.openDevtoolsButton_ = $('extension-error-overlay-devtools-button'); |
430 $('extension-error-overlay-devtools-button')); | |
431 this.openDevtoolsButton_.addEventListener('click', function() { | 402 this.openDevtoolsButton_.addEventListener('click', function() { |
432 this.runtimeErrorContent_.openDevtools(); | 403 this.runtimeErrorContent_.openDevtools(); |
433 }.bind(this)); | 404 }.bind(this)); |
434 }, | 405 }, |
435 | 406 |
436 /** | 407 /** |
437 * Handles a click on the dismiss ("OK" or close) buttons. | 408 * Handles a click on the dismiss ("OK" or close) buttons. |
438 * @param {Event} e The click event. | 409 * @param {Event} e The click event. |
439 * @private | 410 * @private |
440 */ | 411 */ |
(...skipping 10 matching lines...) Expand all Loading... |
451 this.codeDiv_.clear(); | 422 this.codeDiv_.clear(); |
452 | 423 |
453 this.openDevtoolsButton_.hidden = true; | 424 this.openDevtoolsButton_.hidden = true; |
454 | 425 |
455 if (this.error_.type == ExtensionErrorOverlay.RUNTIME_ERROR_TYPE_) { | 426 if (this.error_.type == ExtensionErrorOverlay.RUNTIME_ERROR_TYPE_) { |
456 this.overlayDiv_.querySelector('.content-area').removeChild( | 427 this.overlayDiv_.querySelector('.content-area').removeChild( |
457 this.runtimeErrorContent_); | 428 this.runtimeErrorContent_); |
458 this.runtimeErrorContent_.clearError(); | 429 this.runtimeErrorContent_.clearError(); |
459 } | 430 } |
460 | 431 |
461 this.error_ = null; | 432 this.error_ = undefined; |
462 }, | 433 }, |
463 | 434 |
464 /** | 435 /** |
465 * Associate an error with the overlay. This will set the error for the | 436 * Associate an error with the overlay. This will set the error for the |
466 * overlay, and, if possible, will populate the code section of the overlay | 437 * overlay, and, if possible, will populate the code section of the overlay |
467 * with the relevant file, load the stack trace, and generate links for | 438 * with the relevant file, load the stack trace, and generate links for |
468 * opening devtools (the latter two only happen for runtime errors). | 439 * opening devtools (the latter two only happen for runtime errors). |
469 * @param {RuntimeError} error The error to show in the overlay. | 440 * @param {Object} error The error to show in the overlay. |
470 * @param {string} extensionUrl The URL of the extension, in the form | 441 * @param {string} extensionUrl The URL of the extension, in the form |
471 * "chrome-extension://<extension_id>". | 442 * "chrome-extension://<extension_id>". |
472 */ | 443 */ |
473 setErrorAndShowOverlay: function(error, extensionUrl) { | 444 setErrorAndShowOverlay: function(error, extensionUrl) { |
474 this.error_ = error; | 445 this.error_ = error; |
475 | 446 |
476 if (this.error_.type == ExtensionErrorOverlay.RUNTIME_ERROR_TYPE_) { | 447 if (this.error_.type == ExtensionErrorOverlay.RUNTIME_ERROR_TYPE_) { |
477 this.runtimeErrorContent_.setError(this.error_, extensionUrl); | 448 this.runtimeErrorContent_.setError(this.error_, extensionUrl); |
478 this.overlayDiv_.querySelector('.content-area').insertBefore( | 449 this.overlayDiv_.querySelector('.content-area').insertBefore( |
479 this.runtimeErrorContent_, | 450 this.runtimeErrorContent_, |
(...skipping 17 matching lines...) Expand all Loading... |
497 requestFileSourceArgs.lineNumber = | 468 requestFileSourceArgs.lineNumber = |
498 error.stackTrace && error.stackTrace[0] ? | 469 error.stackTrace && error.stackTrace[0] ? |
499 error.stackTrace[0].lineNumber : 0; | 470 error.stackTrace[0].lineNumber : 0; |
500 } | 471 } |
501 ExtensionErrorOverlay.requestFileSource(requestFileSourceArgs); | 472 ExtensionErrorOverlay.requestFileSource(requestFileSourceArgs); |
502 } else { | 473 } else { |
503 ExtensionErrorOverlay.requestFileSourceResponse(null); | 474 ExtensionErrorOverlay.requestFileSourceResponse(null); |
504 } | 475 } |
505 }, | 476 }, |
506 | 477 |
507 | |
508 /** | 478 /** |
509 * Set the code to be displayed in the code portion of the overlay. | 479 * Set the code to be displayed in the code portion of the overlay. |
510 * @see ExtensionErrorOverlay.requestFileSourceResponse(). | 480 * @see ExtensionErrorOverlay.requestFileSourceResponse(). |
511 * @param {?ExtensionHighlight} code The code to be displayed. If |code| is | 481 * @param {?Object} code The code to be displayed. If |code| is null, then |
512 * null, then | |
513 * a "Could not display code" message will be displayed instead. | 482 * a "Could not display code" message will be displayed instead. |
514 */ | 483 */ |
515 setCode: function(code) { | 484 setCode: function(code) { |
516 document.querySelector( | 485 document.querySelector( |
517 '#extension-error-overlay .extension-error-overlay-title'). | 486 '#extension-error-overlay .extension-error-overlay-title'). |
518 textContent = code.title; | 487 textContent = code.title; |
519 | 488 |
520 this.codeDiv_.populate( | 489 this.codeDiv_.populate( |
521 code, | 490 code, |
522 loadTimeData.getString('extensionErrorOverlayNoCodeToDisplay')); | 491 loadTimeData.getString('extensionErrorOverlayNoCodeToDisplay')); |
523 }, | 492 }, |
524 }; | 493 }; |
525 | 494 |
526 /** | 495 /** |
527 * Called by the ExtensionErrorHandler responding to the request for a file's | 496 * Called by the ExtensionErrorHandler responding to the request for a file's |
528 * source. Populate the content area of the overlay and display the overlay. | 497 * source. Populate the content area of the overlay and display the overlay. |
529 * @param {?ExtensionHighlight} result The three 'highlight' strings represent | 498 * @param {Object?} result An object with four strings - the title, |
530 * three portions of the file's content to display - the portion which is | 499 * beforeHighlight, afterHighlight, and highlight. The three 'highlight' |
531 * most relevant and should be emphasized (highlight), and the parts both | 500 * strings represent three portions of the file's content to display - the |
532 * before and after this portion. These may be empty. | 501 * portion which is most relevant and should be emphasized (highlight), |
| 502 * and the parts both before and after this portion. These may be empty. |
533 */ | 503 */ |
534 ExtensionErrorOverlay.requestFileSourceResponse = function(result) { | 504 ExtensionErrorOverlay.requestFileSourceResponse = function(result) { |
535 var overlay = extensions.ExtensionErrorOverlay.getInstance(); | 505 var overlay = extensions.ExtensionErrorOverlay.getInstance(); |
536 overlay.setCode(result); | 506 overlay.setCode(result); |
537 overlay.setVisible(true); | 507 overlay.setVisible(true); |
538 }; | 508 }; |
539 | 509 |
540 // Export | 510 // Export |
541 return { | 511 return { |
542 ExtensionErrorOverlay: ExtensionErrorOverlay | 512 ExtensionErrorOverlay: ExtensionErrorOverlay |
543 }; | 513 }; |
544 }); | 514 }); |
OLD | NEW |