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 cr.define('extensions', function() { | 5 cr.define('extensions', function() { |
6 'use strict'; | 6 'use strict'; |
7 | 7 |
8 /** | 8 /** |
9 * Returns whether or not a given |url| is associated with an extension. | 9 * Returns whether or not a given |url| is associated with an extension. |
10 * @param {string} url The url to examine. | 10 * @param {string} url The url to examine. |
(...skipping 23 matching lines...) Expand all Loading... |
34 */ | 34 */ |
35 function cloneTemplate(templateName) { | 35 function cloneTemplate(templateName) { |
36 return $('template-collection-extension-error'). | 36 return $('template-collection-extension-error'). |
37 querySelector('.' + templateName).cloneNode(true); | 37 querySelector('.' + templateName).cloneNode(true); |
38 } | 38 } |
39 | 39 |
40 /** | 40 /** |
41 * Creates a new ExtensionError HTMLElement; this is used to show a | 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. | 42 * notification to the user when an error is caused by an extension. |
43 * @param {Object} error The error the element should represent. | 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'). |
44 * @constructor | 46 * @constructor |
45 * @extends {HTMLDivElement} | 47 * @extends {HTMLDivElement} |
46 */ | 48 */ |
47 function ExtensionError(error) { | 49 function ExtensionError(error, templateName) { |
48 var div = document.createElement('div'); | 50 var div = cloneTemplate(templateName); |
49 div.__proto__ = ExtensionError.prototype; | 51 div.__proto__ = ExtensionError.prototype; |
50 div.className = 'extension-error-simple-wrapper'; | |
51 div.error_ = error; | 52 div.error_ = error; |
52 div.decorate(); | 53 div.decorate(); |
53 return div; | 54 return div; |
54 } | 55 } |
55 | 56 |
56 ExtensionError.prototype = { | 57 ExtensionError.prototype = { |
57 __proto__: HTMLDivElement.prototype, | 58 __proto__: HTMLDivElement.prototype, |
58 | 59 |
59 /** @override */ | 60 /** @override */ |
60 decorate: function() { | 61 decorate: function() { |
61 var metadata = cloneTemplate('extension-error-metadata'); | 62 var metadata = cloneTemplate('extension-error-metadata'); |
62 | 63 |
63 // Add an additional class for the severity level. | 64 // Add an additional class for the severity level. |
64 if (this.error_.level == 0) | 65 if (this.error_.level == 0) |
65 metadata.className += ' extension-error-severity-info'; | 66 metadata.className += ' extension-error-severity-info'; |
66 else if (this.error_.level == 1) | 67 else if (this.error_.level == 1) |
67 metadata.className += ' extension-error-severity-warning'; | 68 metadata.className += ' extension-error-severity-warning'; |
68 else | 69 else |
69 metadata.className += ' extension-error-severity-fatal'; | 70 metadata.className += ' extension-error-severity-fatal'; |
70 | 71 |
71 var iconNode = document.createElement('img'); | 72 var iconNode = document.createElement('img'); |
72 iconNode.className = 'extension-error-icon'; | 73 iconNode.className = 'extension-error-icon'; |
73 metadata.insertBefore(iconNode, metadata.firstChild); | 74 metadata.insertBefore(iconNode, metadata.firstChild); |
74 | 75 |
75 // Add a property for the extension's base url in order to determine if | 76 // Add a property for the extension's base url in order to determine if |
76 // a url belongs to the extension. | 77 // a url belongs to the extension. |
77 this.extensionUrl_ = | 78 this.extensionUrl_ = |
78 'chrome-extension://' + this.error_.extensionId + '/'; | 79 'chrome-extension://' + this.error_.extensionId + '/'; |
79 | 80 |
80 metadata.querySelector('.extension-error-message').innerText = | 81 metadata.querySelector('.extension-error-message').textContent = |
81 this.error_.message; | 82 this.error_.message; |
82 | 83 |
83 metadata.appendChild(this.getViewSourceOrPlain_( | 84 metadata.appendChild(this.getViewSourceOrPlain_( |
84 getRelativeUrl(this.error_.source, this.extensionUrl_), | 85 getRelativeUrl(this.error_.source, this.extensionUrl_), |
85 this.error_.source)); | 86 this.error_.source)); |
86 | 87 |
87 this.appendChild(metadata); | 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); |
| 92 |
| 93 var detailsNode = this.querySelector('.extension-error-details'); |
| 94 if (detailsNode && this.error_.contextUrl) |
| 95 detailsNode.appendChild(this.getContextNode_()); |
| 96 if (detailsNode && this.error_.stackTrace) |
| 97 detailsNode.appendChild(this.getStackNode_()); |
88 }, | 98 }, |
89 | 99 |
90 /** | 100 /** |
91 * Return a div with text |description|. If it's possible to view the source | 101 * Return a div with text |description|. If it's possible to view the source |
92 * for |url|, linkify the div to do so. | 102 * for |url|, linkify the div to do so. |
93 * @param {string} description a human-friendly description the location | 103 * @param {string} description a human-friendly description the location |
94 * (e.g., filename, line). | 104 * (e.g., filename, line). |
95 * @param {string} url The url of the resource to view. | 105 * @param {string} url The url of the resource to view. |
| 106 * @param {?number} line An optional line number of the resource. |
96 * @return {HTMLElement} The created node, either a link or plaintext. | 107 * @return {HTMLElement} The created node, either a link or plaintext. |
97 * @private | 108 * @private |
98 */ | 109 */ |
99 getViewSourceOrPlain_: function(description, url) { | 110 getViewSourceOrPlain_: function(description, url, line) { |
100 if (this.canViewSource_(url)) | 111 if (this.canViewSource_(url)) |
101 var node = this.getViewSourceLink_(url); | 112 var node = this.getViewSourceLink_(url, line); |
102 else | 113 else |
103 var node = document.createElement('div'); | 114 var node = document.createElement('div'); |
104 node.className = 'extension-error-view-source'; | 115 node.className = 'extension-error-view-source'; |
105 node.innerText = description; | 116 node.textContent = description; |
106 return node; | 117 return node; |
107 }, | 118 }, |
108 | 119 |
109 /** | 120 /** |
110 * Determine whether we can view the source of a given url. | 121 * Determine whether we can view the source of a given url. |
111 * @param {string} url The url of the resource to view. | 122 * @param {string} url The url of the resource to view. |
112 * @return {boolean} Whether or not we can view the source for the url. | 123 * @return {boolean} Whether or not we can view the source for the url. |
113 * @private | 124 * @private |
114 */ | 125 */ |
115 canViewSource_: function(url) { | 126 canViewSource_: function(url) { |
116 return isExtensionUrl(url, this.extensionUrl_) || url == 'manifest.json'; | 127 return isExtensionUrl(url, this.extensionUrl_) || url == 'manifest.json'; |
117 }, | 128 }, |
118 | 129 |
119 /** | 130 /** |
120 * Create a clickable node to view the source for the given url. | 131 * Create a clickable node to view the source for the given url. |
121 * @param {string} url The url to the resource to view. | 132 * @param {string} url The url to the resource to view. |
| 133 * @param {?number} line An optional line number of the resource (for |
| 134 * source files). |
122 * @return {HTMLElement} The clickable node to view the source. | 135 * @return {HTMLElement} The clickable node to view the source. |
123 * @private | 136 * @private |
124 */ | 137 */ |
125 getViewSourceLink_: function(url) { | 138 getViewSourceLink_: function(url, line) { |
126 var node = document.createElement('a'); | 139 var node = document.createElement('a'); |
127 var relativeUrl = getRelativeUrl(url, this.extensionUrl_); | 140 var relativeUrl = getRelativeUrl(url, this.extensionUrl_); |
| 141 var requestFileSourceArgs = { 'extensionId': this.error_.extensionId, |
| 142 'message': this.error_.message, |
| 143 'pathSuffix': relativeUrl }; |
| 144 if (relativeUrl == 'manifest.json') { |
| 145 requestFileSourceArgs.manifestKey = this.error_.manifestKey; |
| 146 requestFileSourceArgs.manifestSpecific = this.error_.manifestSpecific; |
| 147 } else { |
| 148 // Prefer |line| if available, or default to the line of the last stack |
| 149 // frame. |
| 150 if (line) { |
| 151 requestFileSourceArgs.lineNumber = line; |
| 152 } else if (this.error_.stackTrace) { |
| 153 requestFileSourceArgs.lineNumber = |
| 154 this.error_.stackTrace[0].lineNumber; |
| 155 } |
| 156 } |
128 | 157 |
129 node.addEventListener('click', function(e) { | 158 node.addEventListener('click', function(e) { |
130 chrome.send('extensionErrorRequestFileSource', | 159 chrome.send('extensionErrorRequestFileSource', [requestFileSourceArgs]); |
131 [{'extensionId': this.error_.extensionId, | 160 }); |
132 'message': this.error_.message, | 161 node.title = loadTimeData.getString('extensionErrorViewSource'); |
133 'fileType': 'manifest', | 162 return node; |
134 'pathSuffix': relativeUrl, | 163 }, |
135 'manifestKey': this.error_.manifestKey, | 164 |
136 'manifestSpecific': this.error_.manifestSpecific}]); | 165 /** |
137 }.bind(this)); | 166 * Get the context node for this error. This will attempt to link to the |
| 167 * context in which the error occurred, and can be either an extension page |
| 168 * or an external page. |
| 169 * @return {HTMLDivElement} The context node for the error, including the |
| 170 * label and a link to the context. |
| 171 * @private |
| 172 */ |
| 173 getContextNode_: function() { |
| 174 var node = cloneTemplate('extension-error-context-wrapper'); |
| 175 var linkNode = node.querySelector('a'); |
| 176 if (isExtensionUrl(this.error_.contextUrl, this.extensionUrl_)) { |
| 177 linkNode.textContent = getRelativeUrl(this.error_.contextUrl, |
| 178 this.extensionUrl_); |
| 179 } else { |
| 180 linkNode.textContent = this.error_.contextUrl; |
| 181 } |
| 182 linkNode.href = this.error_.contextUrl; |
| 183 linkNode.target = '_blank'; |
| 184 return node; |
| 185 }, |
| 186 |
| 187 /** |
| 188 * Get a node for the stack trace for this error. Each stack frame will |
| 189 * include a resource url, line number, and function name (possibly |
| 190 * anonymous). If possible, these frames will also be linked for viewing the |
| 191 * source. |
| 192 * @return {HTMLDetailsElement} The stack trace node for this error, with |
| 193 * all stack frames nested in a details-summary object. |
| 194 * @private |
| 195 */ |
| 196 getStackNode_: function() { |
| 197 var node = cloneTemplate('extension-error-stack-trace'); |
| 198 var listNode = node.querySelector('.extension-error-stack-trace-list'); |
| 199 this.error_.stackTrace.forEach(function(frame) { |
| 200 var frameNode = document.createElement('div'); |
| 201 var description = getRelativeUrl(frame.url, this.extensionUrl_) + |
| 202 ':' + frame.lineNumber; |
| 203 if (frame.functionName) { |
| 204 var functionName = frame.functionName == '(anonymous function)' ? |
| 205 loadTimeData.getString('extensionErrorAnonymousFunction') : |
| 206 frame.functionName; |
| 207 description += ' (' + functionName + ')'; |
| 208 } |
| 209 frameNode.appendChild(this.getViewSourceOrPlain_( |
| 210 description, frame.url, frame.lineNumber)); |
| 211 listNode.appendChild( |
| 212 document.createElement('li')).appendChild(frameNode); |
| 213 }, this); |
| 214 |
138 return node; | 215 return node; |
139 }, | 216 }, |
140 }; | 217 }; |
141 | 218 |
142 /** | 219 /** |
143 * A variable length list of runtime or manifest errors for a given extension. | 220 * A variable length list of runtime or manifest errors for a given extension. |
| 221 * @param {Array.<Object>} errors The list of extension errors with which |
| 222 * to populate the list. |
| 223 * @param {string} title The i18n key for the title of the error list, i.e. |
| 224 * 'extensionErrors[Manifest,Runtime]Errors'. |
144 * @constructor | 225 * @constructor |
145 * @extends {HTMLDivElement} | 226 * @extends {HTMLDivElement} |
146 */ | 227 */ |
147 function ExtensionErrorList(errors) { | 228 function ExtensionErrorList(errors, title) { |
148 var div = cloneTemplate('extension-error-list'); | 229 var div = cloneTemplate('extension-error-list'); |
149 div.__proto__ = ExtensionErrorList.prototype; | 230 div.__proto__ = ExtensionErrorList.prototype; |
150 div.errors_ = errors; | 231 div.errors_ = errors; |
| 232 div.title_ = title; |
151 div.decorate(); | 233 div.decorate(); |
152 return div; | 234 return div; |
153 } | 235 } |
154 | 236 |
155 ExtensionErrorList.prototype = { | 237 ExtensionErrorList.prototype = { |
156 __proto__: HTMLDivElement.prototype, | 238 __proto__: HTMLDivElement.prototype, |
157 | 239 |
158 /** | 240 /** |
159 * @private | 241 * @private |
160 * @const | 242 * @const |
161 * @type {number} | 243 * @type {number} |
162 */ | 244 */ |
163 MAX_ERRORS_TO_SHOW_: 3, | 245 MAX_ERRORS_TO_SHOW_: 3, |
164 | 246 |
165 /** @override */ | 247 /** @override */ |
166 decorate: function() { | 248 decorate: function() { |
| 249 this.querySelector('.extension-error-list-title').textContent = |
| 250 loadTimeData.getString(this.title_); |
| 251 |
167 this.contents_ = this.querySelector('.extension-error-list-contents'); | 252 this.contents_ = this.querySelector('.extension-error-list-contents'); |
168 this.errors_.forEach(function(error) { | 253 this.errors_.forEach(function(error) { |
169 this.contents_.appendChild(document.createElement('li')).appendChild( | 254 this.contents_.appendChild(document.createElement('li')).appendChild( |
170 new ExtensionError(error)); | 255 new ExtensionError(error, |
| 256 error.contextUrl || error.stackTrace ? |
| 257 'extension-error-detailed-wrapper' : |
| 258 'extension-error-simple-wrapper')); |
171 }, this); | 259 }, this); |
172 | 260 |
173 if (this.contents_.children.length > this.MAX_ERRORS_TO_SHOW_) { | 261 if (this.contents_.children.length > this.MAX_ERRORS_TO_SHOW_) { |
174 for (var i = this.MAX_ERRORS_TO_SHOW_; | 262 for (var i = this.MAX_ERRORS_TO_SHOW_; |
175 i < this.contents_.children.length; ++i) { | 263 i < this.contents_.children.length; ++i) { |
176 this.contents_.children[i].hidden = true; | 264 this.contents_.children[i].hidden = true; |
177 } | 265 } |
178 this.initShowMoreButton_(); | 266 this.initShowMoreButton_(); |
179 } | 267 } |
180 }, | 268 }, |
181 | 269 |
182 /** | 270 /** |
183 * Initialize the "Show More" button for the error list. If there are more | 271 * Initialize the "Show More" button for the error list. If there are more |
184 * than |MAX_ERRORS_TO_SHOW_| errors in the list. | 272 * than |MAX_ERRORS_TO_SHOW_| errors in the list. |
185 * @private | 273 * @private |
186 */ | 274 */ |
187 initShowMoreButton_: function() { | 275 initShowMoreButton_: function() { |
188 var button = this.querySelector('.extension-error-list-show-more a'); | 276 var button = this.querySelector('.extension-error-list-show-more a'); |
189 button.hidden = false; | 277 button.hidden = false; |
190 button.isShowingAll = false; | 278 button.isShowingAll = false; |
191 button.addEventListener('click', function(e) { | 279 button.addEventListener('click', function(e) { |
192 for (var i = this.MAX_ERRORS_TO_SHOW_; | 280 for (var i = this.MAX_ERRORS_TO_SHOW_; |
193 i < this.contents_.children.length; ++i) { | 281 i < this.contents_.children.length; ++i) { |
194 this.contents_.children[i].hidden = button.isShowingAll; | 282 this.contents_.children[i].hidden = button.isShowingAll; |
195 } | 283 } |
196 var message = button.isShowingAll ? 'extensionErrorsShowMore' : | 284 var message = button.isShowingAll ? 'extensionErrorsShowMore' : |
197 'extensionErrorsShowFewer'; | 285 'extensionErrorsShowFewer'; |
198 button.innerText = loadTimeData.getString(message); | 286 button.textContent = loadTimeData.getString(message); |
199 button.isShowingAll = !button.isShowingAll; | 287 button.isShowingAll = !button.isShowingAll; |
200 }.bind(this)); | 288 }.bind(this)); |
201 } | 289 } |
202 }; | 290 }; |
203 | 291 |
204 return { | 292 return { |
205 ExtensionErrorList: ExtensionErrorList | 293 ExtensionErrorList: ExtensionErrorList |
206 }; | 294 }; |
207 }); | 295 }); |
OLD | NEW |