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() {
9 * Returns whether or not a given |url| is associated with an extension.
10 * @param {string} url The url to examine.
11 * @param {string} extensionUrl The url of the extension.
12 * @return {boolean} Whether or not the url is associated with the extension.
14 function isExtensionUrl(url, extensionUrl) {
15 return url.substring(0, extensionUrl.length) == extensionUrl;
19 * Get the url relative to the main extension url. If the url is
20 * unassociated with the extension, this will be the full url.
21 * @param {string} url The url to make relative.
22 * @param {string} extensionUrl The host for which the url is relative.
23 * @return {string} The url relative to the host.
25 function getRelativeUrl(url, extensionUrl) {
26 return isExtensionUrl(url, extensionUrl) ?
27 url.substring(extensionUrl.length) : url;
31 * Clone a template within the extension error template collection.
32 * @param {string} templateName The class name of the template to clone.
33 * @return {HTMLElement} The clone of the template.
35 function cloneTemplate(templateName) {
36 return $('template-collection-extension-error').
37 querySelector('.' + templateName).cloneNode(true);
41 * Creates a new ExtensionError HTMLElement; this is used to show a
42 * notification to the user when an error is caused by an extension.
43 * @param {Object} error The error the element should represent.
44 * @param {string} templateName The name of the template to clone for the
45 * error ('extension-error-[detailed|simple]-wrapper').
47 * @extends {HTMLDivElement}
49 function ExtensionError(error, templateName) {
50 var div = cloneTemplate(templateName);
51 div.__proto__ = ExtensionError.prototype;
57 ExtensionError.prototype = {
58 __proto__: HTMLDivElement.prototype,
61 decorate: function() {
62 var metadata = cloneTemplate('extension-error-metadata');
64 // Add an additional class for the severity level.
65 if (this.error_.level == 0)
66 metadata.classList.add('extension-error-severity-info');
67 else if (this.error_.level == 1)
68 metadata.classList.add('extension-error-severity-warning');
70 metadata.classList.add('extension-error-severity-fatal');
72 var iconNode = document.createElement('img');
73 iconNode.className = 'extension-error-icon';
74 metadata.insertBefore(iconNode, metadata.firstChild);
76 // Add a property for the extension's base url in order to determine if
77 // a url belongs to the extension.
79 'chrome-extension://' + this.error_.extensionId + '/';
81 metadata.querySelector('.extension-error-message').textContent =
84 metadata.appendChild(this.createViewSourceAndInspect_(
85 getRelativeUrl(this.error_.source, this.extensionUrl_),
88 // The error template may specify a <summary> to put template metadata in.
89 // If not, just append it to the top-level element.
90 var metadataContainer = this.querySelector('summary') || this;
91 metadataContainer.appendChild(metadata);
93 var detailsNode = this.querySelector('.extension-error-details');
94 if (detailsNode && this.error_.contextUrl)
95 detailsNode.appendChild(this.createContextNode_());
96 if (detailsNode && this.error_.stackTrace) {
97 var stackNode = this.createStackNode_();
99 detailsNode.appendChild(this.createStackNode_());
104 * Return a div with text |description|. If it's possible to view the source
105 * for |url|, linkify the div to do so. Attach an inspect button if it's
106 * possible to open the inspector for |url|.
107 * @param {string} description a human-friendly description the location
108 * (e.g., filename, line).
109 * @param {string} url The url of the resource to view.
110 * @param {?number} line An optional line number of the resource.
111 * @param {?number} column An optional column number of the resource.
112 * @return {HTMLElement} The created node, either a link or plaintext.
115 createViewSourceAndInspect_: function(description, url, line, column) {
116 var errorLinks = document.createElement('div');
117 errorLinks.className = 'extension-error-links';
119 if (this.error_.canInspect)
120 errorLinks.appendChild(this.createInspectLink_(url, line, column));
122 if (this.canViewSource_(url))
123 var viewSource = this.createViewSourceLink_(url, line);
125 var viewSource = document.createElement('div');
126 viewSource.className = 'extension-error-view-source';
127 viewSource.textContent = description;
128 errorLinks.appendChild(viewSource);
133 * Determine whether we can view the source of a given url.
134 * @param {string} url The url of the resource to view.
135 * @return {boolean} Whether or not we can view the source for the url.
138 canViewSource_: function(url) {
139 return isExtensionUrl(url, this.extensionUrl_) || url == 'manifest.json';
143 * Determine whether or not we should display the url to the user. We don't
144 * want to include any of our own code in stack traces.
145 * @param {string} url The url in question.
146 * @return {boolean} True if the url should be displayed, and false
147 * otherwise (i.e., if it is an internal script).
149 shouldDisplayForUrl_: function(url) {
150 var extensionsNamespace = 'extensions::';
151 // All our internal scripts are in the 'extensions::' namespace.
152 return url.substr(0, extensionsNamespace.length) != extensionsNamespace;
156 * Create a clickable node to view the source for the given url.
157 * @param {string} url The url to the resource to view.
158 * @param {?number} line An optional line number of the resource (for
160 * @return {HTMLElement} The clickable node to view the source.
163 createViewSourceLink_: function(url, line) {
164 var viewSource = document.createElement('a');
165 viewSource.href = 'javascript:void(0)';
166 var relativeUrl = getRelativeUrl(url, this.extensionUrl_);
167 var requestFileSourceArgs = { 'extensionId': this.error_.extensionId,
168 'message': this.error_.message,
169 'pathSuffix': relativeUrl };
170 if (relativeUrl == 'manifest.json') {
171 requestFileSourceArgs.manifestKey = this.error_.manifestKey;
172 requestFileSourceArgs.manifestSpecific = this.error_.manifestSpecific;
174 // Prefer |line| if available, or default to the line of the last stack
176 requestFileSourceArgs.lineNumber =
177 line ? line : this.getLastPosition_('lineNumber');
180 viewSource.addEventListener('click', function(e) {
181 chrome.send('extensionErrorRequestFileSource', [requestFileSourceArgs]);
183 viewSource.title = loadTimeData.getString('extensionErrorViewSource');
188 * Check the most recent stack frame to get the last position in the code.
189 * @param {string} type The position type, i.e. '[line|column]Number'.
190 * @return {?number} The last position of the given |type|, or undefined if
191 * there is no stack trace to check.
194 getLastPosition_: function(type) {
195 var stackTrace = this.error_.stackTrace;
196 return stackTrace && stackTrace[0] ? stackTrace[0][type] : undefined;
200 * Create an "Inspect" link, in the form of an icon.
201 * @param {?string} url The url of the resource to inspect; if absent, the
202 * render view (and no particular resource) is inspected.
203 * @param {?number} line An optional line number of the resource.
204 * @param {?number} column An optional column number of the resource.
205 * @return {HTMLImageElement} The created "Inspect" link for the resource.
208 createInspectLink_: function(url, line, column) {
209 var linkWrapper = document.createElement('a');
210 linkWrapper.href = 'javascript:void(0)';
211 var inspectIcon = document.createElement('img');
212 inspectIcon.className = 'extension-error-inspect';
213 inspectIcon.title = loadTimeData.getString('extensionErrorInspect');
215 inspectIcon.addEventListener('click', function(e) {
216 chrome.send('extensionErrorOpenDevTools',
217 [{'renderProcessId': this.error_.renderProcessId,
218 'renderViewId': this.error_.renderViewId,
220 'lineNumber': line ? line :
221 this.getLastPosition_('lineNumber'),
222 'columnNumber': column ? column :
223 this.getLastPosition_('columnNumber')}]);
225 linkWrapper.appendChild(inspectIcon);
230 * Get the context node for this error. This will attempt to link to the
231 * context in which the error occurred, and can be either an extension page
232 * or an external page.
233 * @return {HTMLDivElement} The context node for the error, including the
234 * label and a link to the context.
237 createContextNode_: function() {
238 var node = cloneTemplate('extension-error-context-wrapper');
239 var linkNode = node.querySelector('a');
240 if (isExtensionUrl(this.error_.contextUrl, this.extensionUrl_)) {
241 linkNode.textContent = getRelativeUrl(this.error_.contextUrl,
244 linkNode.textContent = this.error_.contextUrl;
247 // Prepend a link to inspect the context page, if possible.
248 if (this.error_.canInspect)
249 node.insertBefore(this.createInspectLink_(), linkNode);
251 linkNode.href = this.error_.contextUrl;
252 linkNode.target = '_blank';
257 * Get a node for the stack trace for this error. Each stack frame will
258 * include a resource url, line number, and function name (possibly
259 * anonymous). If possible, these frames will also be linked for viewing the
260 * source and inspection.
261 * @return {HTMLDetailsElement} The stack trace node for this error, with
262 * all stack frames nested in a details-summary object.
265 createStackNode_: function() {
266 var node = cloneTemplate('extension-error-stack-trace');
267 var listNode = node.querySelector('.extension-error-stack-trace-list');
268 this.error_.stackTrace.forEach(function(frame) {
269 if (!this.shouldDisplayForUrl_(frame.url))
271 var frameNode = document.createElement('div');
272 var description = getRelativeUrl(frame.url, this.extensionUrl_) +
273 ':' + frame.lineNumber;
274 if (frame.functionName) {
275 var functionName = frame.functionName == '(anonymous function)' ?
276 loadTimeData.getString('extensionErrorAnonymousFunction') :
278 description += ' (' + functionName + ')';
280 frameNode.appendChild(this.createViewSourceAndInspect_(
281 description, frame.url, frame.lineNumber, frame.columnNumber));
282 listNode.appendChild(
283 document.createElement('li')).appendChild(frameNode);
286 if (listNode.childElementCount == 0)
294 * A variable length list of runtime or manifest errors for a given extension.
295 * @param {Array.<Object>} errors The list of extension errors with which
296 * to populate the list.
297 * @param {string} title The i18n key for the title of the error list, i.e.
298 * 'extensionErrors[Manifest,Runtime]Errors'.
300 * @extends {HTMLDivElement}
302 function ExtensionErrorList(errors, title) {
303 var div = cloneTemplate('extension-error-list');
304 div.__proto__ = ExtensionErrorList.prototype;
305 div.errors_ = errors;
311 ExtensionErrorList.prototype = {
312 __proto__: HTMLDivElement.prototype,
319 MAX_ERRORS_TO_SHOW_: 3,
322 decorate: function() {
323 this.querySelector('.extension-error-list-title').textContent =
324 loadTimeData.getString(this.title_);
326 this.contents_ = this.querySelector('.extension-error-list-contents');
327 this.errors_.forEach(function(error) {
328 this.contents_.appendChild(document.createElement('li')).appendChild(
329 new ExtensionError(error,
330 error.contextUrl || error.stackTrace ?
331 'extension-error-detailed-wrapper' :
332 'extension-error-simple-wrapper'));
335 if (this.contents_.children.length > this.MAX_ERRORS_TO_SHOW_) {
336 for (var i = this.MAX_ERRORS_TO_SHOW_;
337 i < this.contents_.children.length; ++i) {
338 this.contents_.children[i].hidden = true;
340 this.initShowMoreButton_();
345 * Initialize the "Show More" button for the error list. If there are more
346 * than |MAX_ERRORS_TO_SHOW_| errors in the list.
349 initShowMoreButton_: function() {
350 var button = this.querySelector('.extension-error-list-show-more a');
351 button.hidden = false;
352 button.isShowingAll = false;
353 button.addEventListener('click', function(e) {
354 for (var i = this.MAX_ERRORS_TO_SHOW_;
355 i < this.contents_.children.length; ++i) {
356 this.contents_.children[i].hidden = button.isShowingAll;
358 var message = button.isShowingAll ? 'extensionErrorsShowMore' :
359 'extensionErrorsShowFewer';
360 button.textContent = loadTimeData.getString(message);
361 button.isShowingAll = !button.isShowingAll;
367 ExtensionErrorList: ExtensionErrorList