[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / resources / extensions / extension_error_overlay.js
blobafdcf56d2e64354e7a7ec2d41800ed06c5a3b9d9
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
3 // found in the LICENSE file.
5 cr.define('extensions', function() {
6 'use strict';
8 /**
9 * Clear all the content of a given element.
10 * @param {HTMLElement} element The element to be cleared.
12 function clearElement(element) {
13 while (element.firstChild)
14 element.removeChild(element.firstChild);
17 /**
18 * Get the url relative to the main extension url. If the url is
19 * unassociated with the extension, this will be the full url.
20 * @param {string} url The url to make relative.
21 * @param {string} extensionUrl The url for the extension resources, in the
22 * form "chrome-etxension://<extension_id>/".
23 * @return {string} The url relative to the host.
25 function getRelativeUrl(url, extensionUrl) {
26 return url.substring(0, extensionUrl.length) == extensionUrl ?
27 url.substring(extensionUrl.length) : url;
30 /**
31 * The RuntimeErrorContent manages all content specifically associated with
32 * runtime errors; this includes stack frames and the context url.
33 * @constructor
34 * @extends {HTMLDivElement}
36 function RuntimeErrorContent() {
37 var contentArea = $('template-collection-extension-error-overlay').
38 querySelector('.extension-error-overlay-runtime-content').
39 cloneNode(true);
40 contentArea.__proto__ = RuntimeErrorContent.prototype;
41 contentArea.init();
42 return contentArea;
45 /**
46 * The name of the "active" class specific to extension errors (so as to
47 * not conflict with other rules).
48 * @type {string}
49 * @const
51 RuntimeErrorContent.ACTIVE_CLASS_NAME = 'extension-error-active';
53 /**
54 * Determine whether or not we should display the url to the user. We don't
55 * want to include any of our own code in stack traces.
56 * @param {string} url The url in question.
57 * @return {boolean} True if the url should be displayed, and false
58 * otherwise (i.e., if it is an internal script).
60 RuntimeErrorContent.shouldDisplayForUrl = function(url) {
61 // All our internal scripts are in the 'extensions::' namespace.
62 return !/^extensions::/.test(url);
65 /**
66 * Send a call to chrome to open the developer tools for an error.
67 * This will call either the bound function in ExtensionErrorHandler or the
68 * API function from developerPrivate, depending on whether this is being
69 * used in the native chrome:extensions page or the Apps Developer Tool.
70 * @see chrome/browser/ui/webui/extensions/extension_error_ui_util.h
71 * @param {Object} args The arguments to pass to openDevTools.
72 * @private
74 RuntimeErrorContent.openDevtools_ = function(args) {
75 if (chrome.send)
76 chrome.send('extensionErrorOpenDevTools', [args]);
77 else if (chrome.developerPrivate)
78 chrome.developerPrivate.openDevTools(args);
79 else
80 assert(false, 'Cannot call either openDevTools function.');
83 RuntimeErrorContent.prototype = {
84 __proto__: HTMLDivElement.prototype,
86 /**
87 * The underlying error whose details are being displayed.
88 * @type {Object}
89 * @private
91 error_: undefined,
93 /**
94 * The URL associated with this extension, i.e. chrome-extension://<id>/.
95 * @type {string}
96 * @private
98 extensionUrl_: undefined,
101 * The node of the stack trace which is currently active.
102 * @type {HTMLElement}
103 * @private
105 currentFrameNode_: undefined,
108 * Initialize the RuntimeErrorContent for the first time.
110 init: function() {
112 * The stack trace element in the overlay.
113 * @type {HTMLElement}
114 * @private
116 this.stackTrace_ =
117 this.querySelector('.extension-error-overlay-stack-trace-list');
118 assert(this.stackTrace_);
121 * The context URL element in the overlay.
122 * @type {HTMLElement}
123 * @private
125 this.contextUrl_ =
126 this.querySelector('.extension-error-overlay-context-url');
127 assert(this.contextUrl_);
131 * Sets the error for the content.
132 * @param {Object} error The error whose content should be displayed.
133 * @param {string} extensionUrl The URL associated with this extension.
135 setError: function(error, extensionUrl) {
136 this.error_ = error;
137 this.extensionUrl_ = extensionUrl;
138 this.contextUrl_.textContent = error.contextUrl ?
139 getRelativeUrl(error.contextUrl, this.extensionUrl_) :
140 loadTimeData.getString('extensionErrorOverlayContextUnknown');
141 this.initStackTrace_();
145 * Wipe content associated with a specific error.
147 clearError: function() {
148 this.error_ = undefined;
149 this.extensionUrl_ = undefined;
150 this.currentFrameNode_ = undefined;
151 clearElement(this.stackTrace_);
152 this.stackTrace_.hidden = true;
156 * Makes |frame| active and deactivates the previously active frame (if
157 * there was one).
158 * @param {HTMLElement} frame The frame to activate.
159 * @private
161 setActiveFrame_: function(frameNode) {
162 if (this.currentFrameNode_) {
163 this.currentFrameNode_.classList.remove(
164 RuntimeErrorContent.ACTIVE_CLASS_NAME);
167 this.currentFrameNode_ = frameNode;
168 this.currentFrameNode_.classList.add(
169 RuntimeErrorContent.ACTIVE_CLASS_NAME);
173 * Initialize the stack trace element of the overlay.
174 * @private
176 initStackTrace_: function() {
177 for (var i = 0; i < this.error_.stackTrace.length; ++i) {
178 var frame = this.error_.stackTrace[i];
179 // Don't include any internal calls (e.g., schemaBindings) in the
180 // stack trace.
181 if (!RuntimeErrorContent.shouldDisplayForUrl(frame.url))
182 continue;
184 var frameNode = document.createElement('li');
185 // Attach the index of the frame to which this node refers (since we
186 // may skip some, this isn't a 1-to-1 match).
187 frameNode.indexIntoTrace = i;
189 // The description is a human-readable summation of the frame, in the
190 // form "<relative_url>:<line_number> (function)", e.g.
191 // "myfile.js:25 (myFunction)".
192 var description = getRelativeUrl(frame.url, this.extensionUrl_) +
193 ':' + frame.lineNumber;
194 if (frame.functionName) {
195 var functionName = frame.functionName == '(anonymous function)' ?
196 loadTimeData.getString('extensionErrorOverlayAnonymousFunction') :
197 frame.functionName;
198 description += ' (' + functionName + ')';
200 frameNode.textContent = description;
202 // When the user clicks on a frame in the stack trace, we should
203 // highlight that overlay in the list, display the appropriate source
204 // code with the line highlighted, and link the "Open DevTools" button
205 // with that frame.
206 frameNode.addEventListener('click', function(frame, frameNode, e) {
207 if (this.currStackFrame_ == frameNode)
208 return;
210 this.setActiveFrame_(frameNode);
212 // Request the file source with the section highlighted; this will
213 // call ExtensionErrorOverlay.requestFileSourceResponse() when
214 // completed, which in turn calls setCode().
215 ExtensionErrorOverlay.requestFileSource(
216 {extensionId: this.error_.extensionId,
217 message: this.error_.message,
218 pathSuffix: getRelativeUrl(frame.url, this.extensionUrl_),
219 lineNumber: frame.lineNumber});
220 }.bind(this, frame, frameNode));
222 this.stackTrace_.appendChild(frameNode);
225 // Set the current stack frame to the first stack frame and show the
226 // trace, if one exists. (We can't just check error.stackTrace, because
227 // it's possible the trace was purely internal, and we don't show
228 // internal frames.)
229 if (this.stackTrace_.children.length > 0) {
230 this.stackTrace_.hidden = false;
231 this.setActiveFrame_(this.stackTrace_.firstChild);
236 * Open the developer tools for the active stack frame.
238 openDevtools: function() {
239 var stackFrame =
240 this.error_.stackTrace[this.currentFrameNode_.indexIntoTrace];
242 RuntimeErrorContent.openDevtools_(
243 {renderProcessId: this.error_.renderProcessId,
244 renderViewId: this.error_.renderViewId,
245 url: stackFrame.url,
246 lineNumber: stackFrame.lineNumber || 0,
247 columnNumber: stackFrame.columnNumber || 0});
252 * The ExtensionErrorOverlay will show the contents of a file which pertains
253 * to the ExtensionError; this is either the manifest file (for manifest
254 * errors) or a source file (for runtime errors). If possible, the portion
255 * of the file which caused the error will be highlighted.
256 * @constructor
258 function ExtensionErrorOverlay() {
260 * The content section for runtime errors; this is re-used for all
261 * runtime errors and attached/detached from the overlay as needed.
262 * @type {RuntimeErrorContent}
263 * @private
265 this.runtimeErrorContent_ = new RuntimeErrorContent();
269 * Value of ExtensionError::RUNTIME_ERROR enum.
270 * @see extensions/browser/extension_error.h
271 * @type {number}
272 * @const
273 * @private
275 ExtensionErrorOverlay.RUNTIME_ERROR_TYPE_ = 1;
278 * The manifest filename.
279 * @type {string}
280 * @const
281 * @private
283 ExtensionErrorOverlay.MANIFEST_FILENAME_ = 'manifest.json';
286 * Determine whether or not chrome can load the source for a given file; this
287 * can only be done if the file belongs to the extension.
288 * @param {string} file The file to load.
289 * @param {string} extensionUrl The url for the extension, in the form
290 * chrome-extension://<extension-id>/.
291 * @return {boolean} True if the file can be loaded, false otherwise.
292 * @private
294 ExtensionErrorOverlay.canLoadFileSource = function(file, extensionUrl) {
295 return file.substr(0, extensionUrl.length) == extensionUrl ||
296 file.toLowerCase() == ExtensionErrorOverlay.MANIFEST_FILENAME_;
300 * Determine whether or not we can show an overlay with more details for
301 * the given extension error.
302 * @param {Object} error The extension error.
303 * @param {string} extensionUrl The url for the extension, in the form
304 * "chrome-extension://<extension-id>/".
305 * @return {boolean} True if we can show an overlay for the error,
306 * false otherwise.
308 ExtensionErrorOverlay.canShowOverlayForError = function(error, extensionUrl) {
309 if (ExtensionErrorOverlay.canLoadFileSource(error.source, extensionUrl))
310 return true;
312 if (error.stackTrace) {
313 for (var i = 0; i < error.stackTrace.length; ++i) {
314 if (RuntimeErrorContent.shouldDisplayForUrl(error.stackTrace[i].url))
315 return true;
319 return false;
323 * Send a call to chrome to request the source of a given file.
324 * This will call either the bound function in ExtensionErrorHandler or the
325 * API function from developerPrivate, depending on whether this is being
326 * used in the native chrome:extensions page or the Apps Developer Tool.
327 * @see chrome/browser/ui/webui/extensions/extension_error_ui_util.h
328 * @param {Object} args The arguments to pass to requestFileSource.
330 ExtensionErrorOverlay.requestFileSource = function(args) {
331 if (chrome.send) {
332 chrome.send('extensionErrorRequestFileSource', [args]);
333 } else if (chrome.developerPrivate) {
334 chrome.developerPrivate.requestFileSource(args, function(result) {
335 extensions.ExtensionErrorOverlay.requestFileSourceResponse(result);
337 } else {
338 assert(false, 'Cannot call either requestFileSource function.');
342 cr.addSingletonGetter(ExtensionErrorOverlay);
344 ExtensionErrorOverlay.prototype = {
346 * The underlying error whose details are being displayed.
347 * @type {Object}
348 * @private
350 error_: undefined,
353 * Initialize the page.
354 * @param {function(HTMLDivElement)} showOverlay The function to show or
355 * hide the ExtensionErrorOverlay; this should take a single parameter
356 * which is either the overlay Div if the overlay should be displayed,
357 * or null if the overlay should be hidden.
359 initializePage: function(showOverlay) {
360 var overlay = $('overlay');
361 cr.ui.overlay.setupOverlay(overlay);
362 cr.ui.overlay.globalInitialization();
363 overlay.addEventListener('cancelOverlay', this.handleDismiss_.bind(this));
365 $('extension-error-overlay-dismiss').addEventListener(
366 'click', this.handleDismiss_.bind(this));
369 * The element of the full overlay.
370 * @type {HTMLDivElement}
371 * @private
373 this.overlayDiv_ = $('extension-error-overlay');
376 * The portion of the overlay which shows the code relating to the error
377 * and the corresponding line numbers.
378 * @type {ExtensionCode}
379 * @private
381 this.codeDiv_ =
382 new extensions.ExtensionCode($('extension-error-overlay-code'));
385 * The function to show or hide the ExtensionErrorOverlay.
386 * @type {function}
387 * @param {boolean} isVisible Whether the overlay should be visible.
389 this.setVisible = function(isVisible) {
390 showOverlay(isVisible ? this.overlayDiv_ : null);
391 if (isVisible)
392 this.codeDiv_.scrollToError();
396 * The button to open the developer tools (only available for runtime
397 * errors).
398 * @type {HTMLButtonElement}
399 * @private
401 this.openDevtoolsButton_ = $('extension-error-overlay-devtools-button');
402 this.openDevtoolsButton_.addEventListener('click', function() {
403 this.runtimeErrorContent_.openDevtools();
404 }.bind(this));
408 * Handles a click on the dismiss ("OK" or close) buttons.
409 * @param {Event} e The click event.
410 * @private
412 handleDismiss_: function(e) {
413 this.setVisible(false);
415 // There's a chance that the overlay receives multiple dismiss events; in
416 // this case, handle it gracefully and return (since all necessary work
417 // will already have been done).
418 if (!this.error_)
419 return;
421 // Remove all previous content.
422 this.codeDiv_.clear();
424 this.openDevtoolsButton_.hidden = true;
426 if (this.error_.type == ExtensionErrorOverlay.RUNTIME_ERROR_TYPE_) {
427 this.overlayDiv_.querySelector('.content-area').removeChild(
428 this.runtimeErrorContent_);
429 this.runtimeErrorContent_.clearError();
432 this.error_ = undefined;
436 * Associate an error with the overlay. This will set the error for the
437 * overlay, and, if possible, will populate the code section of the overlay
438 * with the relevant file, load the stack trace, and generate links for
439 * opening devtools (the latter two only happen for runtime errors).
440 * @param {Object} error The error to show in the overlay.
441 * @param {string} extensionUrl The URL of the extension, in the form
442 * "chrome-extension://<extension_id>".
444 setErrorAndShowOverlay: function(error, extensionUrl) {
445 this.error_ = error;
447 if (this.error_.type == ExtensionErrorOverlay.RUNTIME_ERROR_TYPE_) {
448 this.runtimeErrorContent_.setError(this.error_, extensionUrl);
449 this.overlayDiv_.querySelector('.content-area').insertBefore(
450 this.runtimeErrorContent_,
451 this.codeDiv_.nextSibling);
452 this.openDevtoolsButton_.hidden = false;
453 this.openDevtoolsButton_.disabled = !error.canInspect;
456 if (ExtensionErrorOverlay.canLoadFileSource(error.source, extensionUrl)) {
457 var relativeUrl = getRelativeUrl(error.source, extensionUrl);
459 var requestFileSourceArgs = {extensionId: error.extensionId,
460 message: error.message,
461 pathSuffix: relativeUrl};
463 if (relativeUrl.toLowerCase() ==
464 ExtensionErrorOverlay.MANIFEST_FILENAME_) {
465 requestFileSourceArgs.manifestKey = error.manifestKey;
466 requestFileSourceArgs.manifestSpecific = error.manifestSpecific;
467 } else {
468 requestFileSourceArgs.lineNumber =
469 error.stackTrace && error.stackTrace[0] ?
470 error.stackTrace[0].lineNumber : 0;
472 ExtensionErrorOverlay.requestFileSource(requestFileSourceArgs);
473 } else {
474 ExtensionErrorOverlay.requestFileSourceResponse(null);
479 * Set the code to be displayed in the code portion of the overlay.
480 * @see ExtensionErrorOverlay.requestFileSourceResponse().
481 * @param {?Object} code The code to be displayed. If |code| is null, then
482 * a "Could not display code" message will be displayed instead.
484 setCode: function(code) {
485 document.querySelector(
486 '#extension-error-overlay .extension-error-overlay-title').
487 textContent = code.title;
489 this.codeDiv_.populate(
490 code,
491 loadTimeData.getString('extensionErrorOverlayNoCodeToDisplay'));
496 * Called by the ExtensionErrorHandler responding to the request for a file's
497 * source. Populate the content area of the overlay and display the overlay.
498 * @param {Object?} result An object with four strings - the title,
499 * beforeHighlight, afterHighlight, and highlight. The three 'highlight'
500 * strings represent three portions of the file's content to display - the
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.
504 ExtensionErrorOverlay.requestFileSourceResponse = function(result) {
505 var overlay = extensions.ExtensionErrorOverlay.getInstance();
506 overlay.setCode(result);
507 overlay.setVisible(true);
510 // Export
511 return {
512 ExtensionErrorOverlay: ExtensionErrorOverlay