summaryrefslogtreecommitdiffstats
path: root/chromium/chrome/browser/resources/extensions/extension_error_overlay.js
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/browser/resources/extensions/extension_error_overlay.js')
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_error_overlay.js487
1 files changed, 458 insertions, 29 deletions
diff --git a/chromium/chrome/browser/resources/extensions/extension_error_overlay.js b/chromium/chrome/browser/resources/extensions/extension_error_overlay.js
index 3e8087b5ab8..afdcf56d2e6 100644
--- a/chromium/chrome/browser/resources/extensions/extension_error_overlay.js
+++ b/chromium/chrome/browser/resources/extensions/extension_error_overlay.js
@@ -6,6 +6,249 @@ cr.define('extensions', function() {
'use strict';
/**
+ * Clear all the content of a given element.
+ * @param {HTMLElement} element The element to be cleared.
+ */
+ function clearElement(element) {
+ while (element.firstChild)
+ element.removeChild(element.firstChild);
+ }
+
+ /**
+ * Get the url relative to the main extension url. If the url is
+ * unassociated with the extension, this will be the full url.
+ * @param {string} url The url to make relative.
+ * @param {string} extensionUrl The url for the extension resources, in the
+ * form "chrome-etxension://<extension_id>/".
+ * @return {string} The url relative to the host.
+ */
+ function getRelativeUrl(url, extensionUrl) {
+ return url.substring(0, extensionUrl.length) == extensionUrl ?
+ url.substring(extensionUrl.length) : url;
+ }
+
+ /**
+ * The RuntimeErrorContent manages all content specifically associated with
+ * runtime errors; this includes stack frames and the context url.
+ * @constructor
+ * @extends {HTMLDivElement}
+ */
+ function RuntimeErrorContent() {
+ var contentArea = $('template-collection-extension-error-overlay').
+ querySelector('.extension-error-overlay-runtime-content').
+ cloneNode(true);
+ contentArea.__proto__ = RuntimeErrorContent.prototype;
+ contentArea.init();
+ return contentArea;
+ }
+
+ /**
+ * The name of the "active" class specific to extension errors (so as to
+ * not conflict with other rules).
+ * @type {string}
+ * @const
+ */
+ RuntimeErrorContent.ACTIVE_CLASS_NAME = 'extension-error-active';
+
+ /**
+ * Determine whether or not we should display the url to the user. We don't
+ * want to include any of our own code in stack traces.
+ * @param {string} url The url in question.
+ * @return {boolean} True if the url should be displayed, and false
+ * otherwise (i.e., if it is an internal script).
+ */
+ RuntimeErrorContent.shouldDisplayForUrl = function(url) {
+ // All our internal scripts are in the 'extensions::' namespace.
+ return !/^extensions::/.test(url);
+ };
+
+ /**
+ * Send a call to chrome to open the developer tools for an error.
+ * This will call either the bound function in ExtensionErrorHandler or the
+ * API function from developerPrivate, depending on whether this is being
+ * used in the native chrome:extensions page or the Apps Developer Tool.
+ * @see chrome/browser/ui/webui/extensions/extension_error_ui_util.h
+ * @param {Object} args The arguments to pass to openDevTools.
+ * @private
+ */
+ RuntimeErrorContent.openDevtools_ = function(args) {
+ if (chrome.send)
+ chrome.send('extensionErrorOpenDevTools', [args]);
+ else if (chrome.developerPrivate)
+ chrome.developerPrivate.openDevTools(args);
+ else
+ assert(false, 'Cannot call either openDevTools function.');
+ };
+
+ RuntimeErrorContent.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ /**
+ * The underlying error whose details are being displayed.
+ * @type {Object}
+ * @private
+ */
+ error_: undefined,
+
+ /**
+ * The URL associated with this extension, i.e. chrome-extension://<id>/.
+ * @type {string}
+ * @private
+ */
+ extensionUrl_: undefined,
+
+ /**
+ * The node of the stack trace which is currently active.
+ * @type {HTMLElement}
+ * @private
+ */
+ currentFrameNode_: undefined,
+
+ /**
+ * Initialize the RuntimeErrorContent for the first time.
+ */
+ init: function() {
+ /**
+ * The stack trace element in the overlay.
+ * @type {HTMLElement}
+ * @private
+ */
+ this.stackTrace_ =
+ this.querySelector('.extension-error-overlay-stack-trace-list');
+ assert(this.stackTrace_);
+
+ /**
+ * The context URL element in the overlay.
+ * @type {HTMLElement}
+ * @private
+ */
+ this.contextUrl_ =
+ this.querySelector('.extension-error-overlay-context-url');
+ assert(this.contextUrl_);
+ },
+
+ /**
+ * Sets the error for the content.
+ * @param {Object} error The error whose content should be displayed.
+ * @param {string} extensionUrl The URL associated with this extension.
+ */
+ setError: function(error, extensionUrl) {
+ this.error_ = error;
+ this.extensionUrl_ = extensionUrl;
+ this.contextUrl_.textContent = error.contextUrl ?
+ getRelativeUrl(error.contextUrl, this.extensionUrl_) :
+ loadTimeData.getString('extensionErrorOverlayContextUnknown');
+ this.initStackTrace_();
+ },
+
+ /**
+ * Wipe content associated with a specific error.
+ */
+ clearError: function() {
+ this.error_ = undefined;
+ this.extensionUrl_ = undefined;
+ this.currentFrameNode_ = undefined;
+ clearElement(this.stackTrace_);
+ this.stackTrace_.hidden = true;
+ },
+
+ /**
+ * Makes |frame| active and deactivates the previously active frame (if
+ * there was one).
+ * @param {HTMLElement} frame The frame to activate.
+ * @private
+ */
+ setActiveFrame_: function(frameNode) {
+ if (this.currentFrameNode_) {
+ this.currentFrameNode_.classList.remove(
+ RuntimeErrorContent.ACTIVE_CLASS_NAME);
+ }
+
+ this.currentFrameNode_ = frameNode;
+ this.currentFrameNode_.classList.add(
+ RuntimeErrorContent.ACTIVE_CLASS_NAME);
+ },
+
+ /**
+ * Initialize the stack trace element of the overlay.
+ * @private
+ */
+ initStackTrace_: function() {
+ for (var i = 0; i < this.error_.stackTrace.length; ++i) {
+ var frame = this.error_.stackTrace[i];
+ // Don't include any internal calls (e.g., schemaBindings) in the
+ // stack trace.
+ if (!RuntimeErrorContent.shouldDisplayForUrl(frame.url))
+ continue;
+
+ var frameNode = document.createElement('li');
+ // Attach the index of the frame to which this node refers (since we
+ // may skip some, this isn't a 1-to-1 match).
+ frameNode.indexIntoTrace = i;
+
+ // The description is a human-readable summation of the frame, in the
+ // form "<relative_url>:<line_number> (function)", e.g.
+ // "myfile.js:25 (myFunction)".
+ var description = getRelativeUrl(frame.url, this.extensionUrl_) +
+ ':' + frame.lineNumber;
+ if (frame.functionName) {
+ var functionName = frame.functionName == '(anonymous function)' ?
+ loadTimeData.getString('extensionErrorOverlayAnonymousFunction') :
+ frame.functionName;
+ description += ' (' + functionName + ')';
+ }
+ frameNode.textContent = description;
+
+ // When the user clicks on a frame in the stack trace, we should
+ // highlight that overlay in the list, display the appropriate source
+ // code with the line highlighted, and link the "Open DevTools" button
+ // with that frame.
+ frameNode.addEventListener('click', function(frame, frameNode, e) {
+ if (this.currStackFrame_ == frameNode)
+ return;
+
+ this.setActiveFrame_(frameNode);
+
+ // Request the file source with the section highlighted; this will
+ // call ExtensionErrorOverlay.requestFileSourceResponse() when
+ // completed, which in turn calls setCode().
+ ExtensionErrorOverlay.requestFileSource(
+ {extensionId: this.error_.extensionId,
+ message: this.error_.message,
+ pathSuffix: getRelativeUrl(frame.url, this.extensionUrl_),
+ lineNumber: frame.lineNumber});
+ }.bind(this, frame, frameNode));
+
+ this.stackTrace_.appendChild(frameNode);
+ }
+
+ // Set the current stack frame to the first stack frame and show the
+ // trace, if one exists. (We can't just check error.stackTrace, because
+ // it's possible the trace was purely internal, and we don't show
+ // internal frames.)
+ if (this.stackTrace_.children.length > 0) {
+ this.stackTrace_.hidden = false;
+ this.setActiveFrame_(this.stackTrace_.firstChild);
+ }
+ },
+
+ /**
+ * Open the developer tools for the active stack frame.
+ */
+ openDevtools: function() {
+ var stackFrame =
+ this.error_.stackTrace[this.currentFrameNode_.indexIntoTrace];
+
+ RuntimeErrorContent.openDevtools_(
+ {renderProcessId: this.error_.renderProcessId,
+ renderViewId: this.error_.renderViewId,
+ url: stackFrame.url,
+ lineNumber: stackFrame.lineNumber || 0,
+ columnNumber: stackFrame.columnNumber || 0});
+ }
+ };
+
+ /**
* The ExtensionErrorOverlay will show the contents of a file which pertains
* to the ExtensionError; this is either the manifest file (for manifest
* errors) or a source file (for runtime errors). If possible, the portion
@@ -13,15 +256,107 @@ cr.define('extensions', function() {
* @constructor
*/
function ExtensionErrorOverlay() {
+ /**
+ * The content section for runtime errors; this is re-used for all
+ * runtime errors and attached/detached from the overlay as needed.
+ * @type {RuntimeErrorContent}
+ * @private
+ */
+ this.runtimeErrorContent_ = new RuntimeErrorContent();
}
+ /**
+ * Value of ExtensionError::RUNTIME_ERROR enum.
+ * @see extensions/browser/extension_error.h
+ * @type {number}
+ * @const
+ * @private
+ */
+ ExtensionErrorOverlay.RUNTIME_ERROR_TYPE_ = 1;
+
+ /**
+ * The manifest filename.
+ * @type {string}
+ * @const
+ * @private
+ */
+ ExtensionErrorOverlay.MANIFEST_FILENAME_ = 'manifest.json';
+
+ /**
+ * Determine whether or not chrome can load the source for a given file; this
+ * can only be done if the file belongs to the extension.
+ * @param {string} file The file to load.
+ * @param {string} extensionUrl The url for the extension, in the form
+ * chrome-extension://<extension-id>/.
+ * @return {boolean} True if the file can be loaded, false otherwise.
+ * @private
+ */
+ ExtensionErrorOverlay.canLoadFileSource = function(file, extensionUrl) {
+ return file.substr(0, extensionUrl.length) == extensionUrl ||
+ file.toLowerCase() == ExtensionErrorOverlay.MANIFEST_FILENAME_;
+ };
+
+ /**
+ * Determine whether or not we can show an overlay with more details for
+ * the given extension error.
+ * @param {Object} error The extension error.
+ * @param {string} extensionUrl The url for the extension, in the form
+ * "chrome-extension://<extension-id>/".
+ * @return {boolean} True if we can show an overlay for the error,
+ * false otherwise.
+ */
+ ExtensionErrorOverlay.canShowOverlayForError = function(error, extensionUrl) {
+ if (ExtensionErrorOverlay.canLoadFileSource(error.source, extensionUrl))
+ return true;
+
+ if (error.stackTrace) {
+ for (var i = 0; i < error.stackTrace.length; ++i) {
+ if (RuntimeErrorContent.shouldDisplayForUrl(error.stackTrace[i].url))
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ /**
+ * Send a call to chrome to request the source of a given file.
+ * This will call either the bound function in ExtensionErrorHandler or the
+ * API function from developerPrivate, depending on whether this is being
+ * used in the native chrome:extensions page or the Apps Developer Tool.
+ * @see chrome/browser/ui/webui/extensions/extension_error_ui_util.h
+ * @param {Object} args The arguments to pass to requestFileSource.
+ */
+ ExtensionErrorOverlay.requestFileSource = function(args) {
+ if (chrome.send) {
+ chrome.send('extensionErrorRequestFileSource', [args]);
+ } else if (chrome.developerPrivate) {
+ chrome.developerPrivate.requestFileSource(args, function(result) {
+ extensions.ExtensionErrorOverlay.requestFileSourceResponse(result);
+ });
+ } else {
+ assert(false, 'Cannot call either requestFileSource function.');
+ }
+ };
+
cr.addSingletonGetter(ExtensionErrorOverlay);
ExtensionErrorOverlay.prototype = {
/**
+ * The underlying error whose details are being displayed.
+ * @type {Object}
+ * @private
+ */
+ error_: undefined,
+
+ /**
* Initialize the page.
+ * @param {function(HTMLDivElement)} showOverlay The function to show or
+ * hide the ExtensionErrorOverlay; this should take a single parameter
+ * which is either the overlay Div if the overlay should be displayed,
+ * or null if the overlay should be hidden.
*/
- initializePage: function() {
+ initializePage: function(showOverlay) {
var overlay = $('overlay');
cr.ui.overlay.setupOverlay(overlay);
cr.ui.overlay.globalInitialization();
@@ -29,53 +364,147 @@ cr.define('extensions', function() {
$('extension-error-overlay-dismiss').addEventListener(
'click', this.handleDismiss_.bind(this));
+
+ /**
+ * The element of the full overlay.
+ * @type {HTMLDivElement}
+ * @private
+ */
+ this.overlayDiv_ = $('extension-error-overlay');
+
+ /**
+ * The portion of the overlay which shows the code relating to the error
+ * and the corresponding line numbers.
+ * @type {ExtensionCode}
+ * @private
+ */
+ this.codeDiv_ =
+ new extensions.ExtensionCode($('extension-error-overlay-code'));
+
+ /**
+ * The function to show or hide the ExtensionErrorOverlay.
+ * @type {function}
+ * @param {boolean} isVisible Whether the overlay should be visible.
+ */
+ this.setVisible = function(isVisible) {
+ showOverlay(isVisible ? this.overlayDiv_ : null);
+ if (isVisible)
+ this.codeDiv_.scrollToError();
+ };
+
+ /**
+ * The button to open the developer tools (only available for runtime
+ * errors).
+ * @type {HTMLButtonElement}
+ * @private
+ */
+ this.openDevtoolsButton_ = $('extension-error-overlay-devtools-button');
+ this.openDevtoolsButton_.addEventListener('click', function() {
+ this.runtimeErrorContent_.openDevtools();
+ }.bind(this));
},
/**
- * Handles a click on the dismiss button.
+ * Handles a click on the dismiss ("OK" or close) buttons.
* @param {Event} e The click event.
* @private
*/
handleDismiss_: function(e) {
- $('extension-error-overlay-content').innerHTML = '';
- extensions.ExtensionSettings.showOverlay(null);
+ this.setVisible(false);
+
+ // There's a chance that the overlay receives multiple dismiss events; in
+ // this case, handle it gracefully and return (since all necessary work
+ // will already have been done).
+ if (!this.error_)
+ return;
+
+ // Remove all previous content.
+ this.codeDiv_.clear();
+
+ this.openDevtoolsButton_.hidden = true;
+
+ if (this.error_.type == ExtensionErrorOverlay.RUNTIME_ERROR_TYPE_) {
+ this.overlayDiv_.querySelector('.content-area').removeChild(
+ this.runtimeErrorContent_);
+ this.runtimeErrorContent_.clearError();
+ }
+
+ this.error_ = undefined;
+ },
+
+ /**
+ * Associate an error with the overlay. This will set the error for the
+ * overlay, and, if possible, will populate the code section of the overlay
+ * with the relevant file, load the stack trace, and generate links for
+ * opening devtools (the latter two only happen for runtime errors).
+ * @param {Object} error The error to show in the overlay.
+ * @param {string} extensionUrl The URL of the extension, in the form
+ * "chrome-extension://<extension_id>".
+ */
+ setErrorAndShowOverlay: function(error, extensionUrl) {
+ this.error_ = error;
+
+ if (this.error_.type == ExtensionErrorOverlay.RUNTIME_ERROR_TYPE_) {
+ this.runtimeErrorContent_.setError(this.error_, extensionUrl);
+ this.overlayDiv_.querySelector('.content-area').insertBefore(
+ this.runtimeErrorContent_,
+ this.codeDiv_.nextSibling);
+ this.openDevtoolsButton_.hidden = false;
+ this.openDevtoolsButton_.disabled = !error.canInspect;
+ }
+
+ if (ExtensionErrorOverlay.canLoadFileSource(error.source, extensionUrl)) {
+ var relativeUrl = getRelativeUrl(error.source, extensionUrl);
+
+ var requestFileSourceArgs = {extensionId: error.extensionId,
+ message: error.message,
+ pathSuffix: relativeUrl};
+
+ if (relativeUrl.toLowerCase() ==
+ ExtensionErrorOverlay.MANIFEST_FILENAME_) {
+ requestFileSourceArgs.manifestKey = error.manifestKey;
+ requestFileSourceArgs.manifestSpecific = error.manifestSpecific;
+ } else {
+ requestFileSourceArgs.lineNumber =
+ error.stackTrace && error.stackTrace[0] ?
+ error.stackTrace[0].lineNumber : 0;
+ }
+ ExtensionErrorOverlay.requestFileSource(requestFileSourceArgs);
+ } else {
+ ExtensionErrorOverlay.requestFileSourceResponse(null);
+ }
+ },
+
+ /**
+ * Set the code to be displayed in the code portion of the overlay.
+ * @see ExtensionErrorOverlay.requestFileSourceResponse().
+ * @param {?Object} code The code to be displayed. If |code| is null, then
+ * a "Could not display code" message will be displayed instead.
+ */
+ setCode: function(code) {
+ document.querySelector(
+ '#extension-error-overlay .extension-error-overlay-title').
+ textContent = code.title;
+
+ this.codeDiv_.populate(
+ code,
+ loadTimeData.getString('extensionErrorOverlayNoCodeToDisplay'));
},
};
/**
* Called by the ExtensionErrorHandler responding to the request for a file's
* source. Populate the content area of the overlay and display the overlay.
- * @param {Object} result An object with four strings - the title,
+ * @param {Object?} result An object with four strings - the title,
* beforeHighlight, afterHighlight, and highlight. The three 'highlight'
* strings represent three portions of the file's content to display - the
* portion which is most relevant and should be emphasized (highlight),
* and the parts both before and after this portion. These may be empty.
*/
ExtensionErrorOverlay.requestFileSourceResponse = function(result) {
- var content = $('extension-error-overlay-content');
- document.querySelector(
- '#extension-error-overlay .extension-error-overlay-title').
- innerText = result.title;
-
- var createSpan = function(source, isHighlighted) {
- var span = document.createElement('span');
- span.className = isHighlighted ? 'highlighted-source' : 'normal-source';
- source = source.replace(/ /g, '&nbsp;').replace(/\n|\r/g, '<br>');
- span.innerHTML = source;
- return span;
- };
-
- if (result.beforeHighlight)
- content.appendChild(createSpan(result.beforeHighlight, false));
- if (result.highlight) {
- var highlightSpan = createSpan(result.highlight, true);
- highlightSpan.title = result.message;
- content.appendChild(highlightSpan);
- }
- if (result.afterHighlight)
- content.appendChild(createSpan(result.afterHighlight, false));
-
- extensions.ExtensionSettings.showOverlay($('extension-error-overlay'));
+ var overlay = extensions.ExtensionErrorOverlay.getInstance();
+ overlay.setCode(result);
+ overlay.setVisible(true);
};
// Export