Add ENABLE_MEDIA_ROUTER define to builds other than Android and iOS.
[chromium-blink-merge.git] / chrome / browser / resources / extensions / extension_error_overlay.js
blobca9a0fc67760cf469dadeeaf790211133b925b0c
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 RuntimeErrorContent.prototype = {
66 __proto__: HTMLDivElement.prototype,
68 /**
69 * The underlying error whose details are being displayed.
70 * @type {?(RuntimeError|ManifestError)}
71 * @private
73 error_: null,
75 /**
76 * The URL associated with this extension, i.e. chrome-extension://<id>/.
77 * @type {?string}
78 * @private
80 extensionUrl_: null,
82 /**
83 * The node of the stack trace which is currently active.
84 * @type {?HTMLElement}
85 * @private
87 currentFrameNode_: null,
89 /**
90 * Initialize the RuntimeErrorContent for the first time.
92 init: function() {
93 /**
94 * The stack trace element in the overlay.
95 * @type {HTMLElement}
96 * @private
98 this.stackTrace_ = /** @type {HTMLElement} */(
99 this.querySelector('.extension-error-overlay-stack-trace-list'));
100 assert(this.stackTrace_);
103 * The context URL element in the overlay.
104 * @type {HTMLElement}
105 * @private
107 this.contextUrl_ = /** @type {HTMLElement} */(
108 this.querySelector('.extension-error-overlay-context-url'));
109 assert(this.contextUrl_);
113 * Sets the error for the content.
114 * @param {(RuntimeError|ManifestError)} error The error whose content
115 * should be displayed.
116 * @param {string} extensionUrl The URL associated with this extension.
118 setError: function(error, extensionUrl) {
119 this.error_ = error;
120 this.extensionUrl_ = extensionUrl;
121 this.contextUrl_.textContent = error.contextUrl ?
122 getRelativeUrl(error.contextUrl, this.extensionUrl_) :
123 loadTimeData.getString('extensionErrorOverlayContextUnknown');
124 this.initStackTrace_();
128 * Wipe content associated with a specific error.
130 clearError: function() {
131 this.error_ = null;
132 this.extensionUrl_ = null;
133 this.currentFrameNode_ = null;
134 clearElement(this.stackTrace_);
135 this.stackTrace_.hidden = true;
139 * Makes |frame| active and deactivates the previously active frame (if
140 * there was one).
141 * @param {HTMLElement} frameNode The frame to activate.
142 * @private
144 setActiveFrame_: function(frameNode) {
145 if (this.currentFrameNode_) {
146 this.currentFrameNode_.classList.remove(
147 RuntimeErrorContent.ACTIVE_CLASS_NAME);
150 this.currentFrameNode_ = frameNode;
151 this.currentFrameNode_.classList.add(
152 RuntimeErrorContent.ACTIVE_CLASS_NAME);
156 * Initialize the stack trace element of the overlay.
157 * @private
159 initStackTrace_: function() {
160 for (var i = 0; i < this.error_.stackTrace.length; ++i) {
161 var frame = this.error_.stackTrace[i];
162 // Don't include any internal calls (e.g., schemaBindings) in the
163 // stack trace.
164 if (!RuntimeErrorContent.shouldDisplayForUrl(frame.url))
165 continue;
167 var frameNode = document.createElement('li');
168 // Attach the index of the frame to which this node refers (since we
169 // may skip some, this isn't a 1-to-1 match).
170 frameNode.indexIntoTrace = i;
172 // The description is a human-readable summation of the frame, in the
173 // form "<relative_url>:<line_number> (function)", e.g.
174 // "myfile.js:25 (myFunction)".
175 var description = getRelativeUrl(frame.url,
176 assert(this.extensionUrl_)) + ':' + frame.lineNumber;
177 if (frame.functionName) {
178 var functionName = frame.functionName == '(anonymous function)' ?
179 loadTimeData.getString('extensionErrorOverlayAnonymousFunction') :
180 frame.functionName;
181 description += ' (' + functionName + ')';
183 frameNode.textContent = description;
185 // When the user clicks on a frame in the stack trace, we should
186 // highlight that overlay in the list, display the appropriate source
187 // code with the line highlighted, and link the "Open DevTools" button
188 // with that frame.
189 frameNode.addEventListener('click', function(frame, frameNode, e) {
190 this.setActiveFrame_(frameNode);
192 // Request the file source with the section highlighted.
193 extensions.ExtensionErrorOverlay.getInstance().requestFileSource(
194 {extensionId: this.error_.extensionId,
195 message: this.error_.message,
196 pathSuffix: getRelativeUrl(frame.url,
197 assert(this.extensionUrl_)),
198 lineNumber: frame.lineNumber});
199 }.bind(this, frame, frameNode));
201 this.stackTrace_.appendChild(frameNode);
204 // Set the current stack frame to the first stack frame and show the
205 // trace, if one exists. (We can't just check error.stackTrace, because
206 // it's possible the trace was purely internal, and we don't show
207 // internal frames.)
208 if (this.stackTrace_.children.length > 0) {
209 this.stackTrace_.hidden = false;
210 this.setActiveFrame_(assertInstanceof(this.stackTrace_.firstChild,
211 HTMLElement));
216 * Open the developer tools for the active stack frame.
218 openDevtools: function() {
219 var stackFrame =
220 this.error_.stackTrace[this.currentFrameNode_.indexIntoTrace];
222 chrome.developerPrivate.openDevTools(
223 {renderProcessId: this.error_.renderProcessId || -1,
224 renderViewId: this.error_.renderViewId || -1,
225 url: stackFrame.url,
226 lineNumber: stackFrame.lineNumber || 0,
227 columnNumber: stackFrame.columnNumber || 0});
232 * The ExtensionErrorOverlay will show the contents of a file which pertains
233 * to the ExtensionError; this is either the manifest file (for manifest
234 * errors) or a source file (for runtime errors). If possible, the portion
235 * of the file which caused the error will be highlighted.
236 * @constructor
238 function ExtensionErrorOverlay() {
240 * The content section for runtime errors; this is re-used for all
241 * runtime errors and attached/detached from the overlay as needed.
242 * @type {RuntimeErrorContent}
243 * @private
245 this.runtimeErrorContent_ = new RuntimeErrorContent();
249 * Value of ExtensionError::RUNTIME_ERROR enum.
250 * @see extensions/browser/extension_error.h
251 * @type {number}
252 * @const
253 * @private
255 ExtensionErrorOverlay.RUNTIME_ERROR_TYPE_ = 1;
258 * The manifest filename.
259 * @type {string}
260 * @const
261 * @private
263 ExtensionErrorOverlay.MANIFEST_FILENAME_ = 'manifest.json';
266 * 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.
268 * @param {string} file The file to load.
269 * @param {string} extensionUrl The url for the extension, in the form
270 * chrome-extension://<extension-id>/.
271 * @return {boolean} True if the file can be loaded, false otherwise.
272 * @private
274 ExtensionErrorOverlay.canLoadFileSource = function(file, extensionUrl) {
275 return file.substr(0, extensionUrl.length) == extensionUrl ||
276 file.toLowerCase() == ExtensionErrorOverlay.MANIFEST_FILENAME_;
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.
288 ExtensionErrorOverlay.canShowOverlayForError = function(error, extensionUrl) {
289 if (ExtensionErrorOverlay.canLoadFileSource(error.source, extensionUrl))
290 return true;
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;
299 return false;
302 cr.addSingletonGetter(ExtensionErrorOverlay);
304 ExtensionErrorOverlay.prototype = {
306 * The underlying error whose details are being displayed.
307 * @type {?(RuntimeError|ManifestError)}
308 * @private
310 error_: null,
313 * Initialize the page.
314 * @param {function(HTMLDivElement)} showOverlay The function to show or
315 * hide the ExtensionErrorOverlay; this should take a single parameter
316 * which is either the overlay Div if the overlay should be displayed,
317 * or null if the overlay should be hidden.
319 initializePage: function(showOverlay) {
320 var overlay = $('overlay');
321 cr.ui.overlay.setupOverlay(overlay);
322 cr.ui.overlay.globalInitialization();
323 overlay.addEventListener('cancelOverlay', this.handleDismiss_.bind(this));
325 $('extension-error-overlay-dismiss').addEventListener(
326 'click', this.handleDismiss_.bind(this));
329 * The element of the full overlay.
330 * @type {HTMLDivElement}
331 * @private
333 this.overlayDiv_ = /** @type {HTMLDivElement} */(
334 $('extension-error-overlay'));
337 * The portion of the overlay which shows the code relating to the error
338 * and the corresponding line numbers.
339 * @type {extensions.ExtensionCode}
340 * @private
342 this.codeDiv_ =
343 new extensions.ExtensionCode($('extension-error-overlay-code'));
346 * The function to show or hide the ExtensionErrorOverlay.
347 * @param {boolean} isVisible Whether the overlay should be visible.
349 this.setVisible = function(isVisible) {
350 showOverlay(isVisible ? this.overlayDiv_ : null);
351 if (isVisible)
352 this.codeDiv_.scrollToError();
356 * The button to open the developer tools (only available for runtime
357 * errors).
358 * @type {HTMLButtonElement}
359 * @private
361 this.openDevtoolsButton_ = /** @type {HTMLButtonElement} */(
362 $('extension-error-overlay-devtools-button'));
363 this.openDevtoolsButton_.addEventListener('click', function() {
364 this.runtimeErrorContent_.openDevtools();
365 }.bind(this));
369 * Handles a click on the dismiss ("OK" or close) buttons.
370 * @param {Event} e The click event.
371 * @private
373 handleDismiss_: function(e) {
374 this.setVisible(false);
376 // There's a chance that the overlay receives multiple dismiss events; in
377 // this case, handle it gracefully and return (since all necessary work
378 // will already have been done).
379 if (!this.error_)
380 return;
382 // Remove all previous content.
383 this.codeDiv_.clear();
385 this.openDevtoolsButton_.hidden = true;
387 if (this.error_.type == ExtensionErrorOverlay.RUNTIME_ERROR_TYPE_) {
388 this.overlayDiv_.querySelector('.content-area').removeChild(
389 this.runtimeErrorContent_);
390 this.runtimeErrorContent_.clearError();
393 this.error_ = null;
397 * Associate an error with the overlay. This will set the error for the
398 * overlay, and, if possible, will populate the code section of the overlay
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>".
406 setErrorAndShowOverlay: function(error, extensionUrl) {
407 this.error_ = error;
409 if (this.error_.type == ExtensionErrorOverlay.RUNTIME_ERROR_TYPE_) {
410 this.runtimeErrorContent_.setError(this.error_, extensionUrl);
411 this.overlayDiv_.querySelector('.content-area').insertBefore(
412 this.runtimeErrorContent_,
413 this.codeDiv_.nextSibling);
414 this.openDevtoolsButton_.hidden = false;
415 this.openDevtoolsButton_.disabled = !error.canInspect;
418 if (ExtensionErrorOverlay.canLoadFileSource(error.source, extensionUrl)) {
419 var relativeUrl = getRelativeUrl(error.source, extensionUrl);
421 var requestFileSourceArgs = {extensionId: error.extensionId,
422 message: error.message,
423 pathSuffix: relativeUrl};
425 if (relativeUrl.toLowerCase() ==
426 ExtensionErrorOverlay.MANIFEST_FILENAME_) {
427 requestFileSourceArgs.manifestKey = error.manifestKey;
428 requestFileSourceArgs.manifestSpecific = error.manifestSpecific;
429 } else {
430 requestFileSourceArgs.lineNumber =
431 error.stackTrace && error.stackTrace[0] ?
432 error.stackTrace[0].lineNumber : 0;
434 this.requestFileSource(requestFileSourceArgs);
435 } else {
436 this.onFileSourceResponse_(null);
441 * Requests a file's source.
442 * @param {RequestFileSourceProperties} args The arguments for the call.
444 requestFileSource: function(args) {
445 chrome.developerPrivate.requestFileSource(
446 args, this.onFileSourceResponse_.bind(this));
450 * Set the code to be displayed in the code portion of the overlay.
451 * @see ExtensionErrorOverlay.requestFileSourceResponse().
452 * @param {?RequestFileSourceResponse} response The response from the
453 * request file source call, which will be shown as code. If |response|
454 * is null, then a "Could not display code" message will be displayed
455 * instead.
457 onFileSourceResponse_: function(response) {
458 if (response) {
459 document.querySelector(
460 '#extension-error-overlay .extension-error-overlay-title').
461 textContent = response.title;
463 this.codeDiv_.populate(
464 response, // ExtensionCode can handle a null response.
465 loadTimeData.getString('extensionErrorOverlayNoCodeToDisplay'));
466 this.setVisible(true);
470 // Export
471 return {
472 ExtensionErrorOverlay: ExtensionErrorOverlay