ozone: evdev: Sync caps lock LED state to evdev
[chromium-blink-merge.git] / chrome / browser / resources / extensions / extension_error.js
blob3763de4a3c9c0126e465e1a02adccbf5ca795244
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    * Clone a template within the extension error template collection.
10    * @param {string} templateName The class name of the template to clone.
11    * @return {HTMLElement} The clone of the template.
12    */
13   function cloneTemplate(templateName) {
14     return /** @type {HTMLElement} */($('template-collection-extension-error').
15         querySelector('.' + templateName).cloneNode(true));
16   }
18   /**
19    * Checks that an Extension ID follows the proper format (i.e., is 32
20    * characters long, is lowercase, and contains letters in the range [a, p]).
21    * @param {string} id The Extension ID to test.
22    * @return {boolean} Whether or not the ID is valid.
23    */
24   function idIsValid(id) {
25     return /^[a-p]{32}$/.test(id);
26   }
28   /**
29    * Creates a new ExtensionError HTMLElement; this is used to show a
30    * notification to the user when an error is caused by an extension.
31    * @param {RuntimeError} error The error the element should represent.
32    * @param {Element} boundary The boundary for the focus grid.
33    * @constructor
34    * @extends {cr.ui.FocusRow}
35    */
36   function ExtensionError(error, boundary) {
37     var div = cloneTemplate('extension-error-metadata');
38     div.__proto__ = ExtensionError.prototype;
39     div.decorate(error, boundary);
40     return div;
41   }
43   ExtensionError.prototype = {
44     __proto__: cr.ui.FocusRow.prototype,
46     /** @override */
47     getEquivalentElement: function(element) {
48       return assert(this.querySelector('.extension-error-view-details'));
49     },
51     /**
52      * @param {RuntimeError} error The error the element should represent
53      * @param {Element} boundary The boundary for the FocusGrid.
54      * @override
55      */
56     decorate: function(error, boundary) {
57       cr.ui.FocusRow.prototype.decorate.call(this, boundary);
59       // Add an additional class for the severity level.
60       if (error.level == 0)
61         this.classList.add('extension-error-severity-info');
62       else if (error.level == 1)
63         this.classList.add('extension-error-severity-warning');
64       else
65         this.classList.add('extension-error-severity-fatal');
67       var iconNode = document.createElement('img');
68       iconNode.className = 'extension-error-icon';
69       // TODO(hcarmona): Populate alt text with a proper description since this
70       // icon conveys the severity of the error. (info, warning, fatal).
71       iconNode.alt = '';
72       this.insertBefore(iconNode, this.firstChild);
74       var messageSpan = this.querySelector('.extension-error-message');
75       messageSpan.textContent = error.message;
76       messageSpan.title = error.message;
78       var extensionUrl = 'chrome-extension://' + error.extensionId + '/';
79       var viewDetailsLink = this.querySelector('.extension-error-view-details');
81       // If we cannot open the file source and there are no external frames in
82       // the stack, then there are no details to display.
83       if (!extensions.ExtensionErrorOverlay.canShowOverlayForError(
84               error, extensionUrl)) {
85         viewDetailsLink.hidden = true;
86       } else {
87         var stringId = extensionUrl.toLowerCase() == 'manifest.json' ?
88             'extensionErrorViewManifest' : 'extensionErrorViewDetails';
89         viewDetailsLink.textContent = loadTimeData.getString(stringId);
91         viewDetailsLink.addEventListener('click', function(e) {
92           extensions.ExtensionErrorOverlay.getInstance().setErrorAndShowOverlay(
93               error, extensionUrl);
94         });
96         this.addFocusableElement(viewDetailsLink);
97       }
98     },
99   };
101   /**
102    * A variable length list of runtime or manifest errors for a given extension.
103    * @param {Array<Object>} errors The list of extension errors with which
104    *     to populate the list.
105    * @constructor
106    * @extends {HTMLDivElement}
107    */
108   function ExtensionErrorList(errors) {
109     var div = cloneTemplate('extension-error-list');
110     div.__proto__ = ExtensionErrorList.prototype;
111     div.errors_ = errors;
112     div.decorate();
113     return div;
114   }
116   /**
117    * @private
118    * @const
119    * @type {number}
120    */
121   ExtensionErrorList.MAX_ERRORS_TO_SHOW_ = 3;
123   ExtensionErrorList.prototype = {
124     __proto__: HTMLDivElement.prototype,
126     decorate: function() {
127       this.focusGrid_ = new cr.ui.FocusGrid();
128       this.gridBoundary_ = this.querySelector('.extension-error-list-contents');
129       this.gridBoundary_.addEventListener('focus', this.onFocus_.bind(this));
130       this.gridBoundary_.addEventListener('focusin',
131                                           this.onFocusin_.bind(this));
132       this.errors_.forEach(function(error) {
133         if (idIsValid(error.extensionId)) {
134           var focusRow = new ExtensionError(error, this.gridBoundary_);
135           this.gridBoundary_.appendChild(
136               document.createElement('li')).appendChild(focusRow);
137           this.focusGrid_.addRow(focusRow);
138         }
139       }, this);
141       var numShowing = this.focusGrid_.rows.length;
142       if (numShowing > ExtensionErrorList.MAX_ERRORS_TO_SHOW_)
143         this.initShowMoreLink_();
144     },
146     /**
147      * @return {?Element} The element that toggles between show more and show
148      *     less, or null if it's hidden. Button will be hidden if there are less
149      *     errors than |MAX_ERRORS_TO_SHOW_|.
150      */
151     getToggleElement: function() {
152       return this.querySelector(
153           '.extension-error-list-show-more [is="action-link"]:not([hidden])');
154     },
156     /** @return {!Element} The element containing the list of errors. */
157     getErrorListElement: function() {
158       return this.gridBoundary_;
159     },
161     /**
162      * The grid should not be focusable once it or an element inside it is
163      * focused. This is necessary to allow tabbing out of the grid in reverse.
164      * @private
165      */
166     onFocusin_: function() {
167       this.gridBoundary_.tabIndex = -1;
168     },
170     /**
171      * Focus the first focusable row when tabbing into the grid for the
172      * first time.
173      * @private
174      */
175     onFocus_: function() {
176       var activeRow = this.gridBoundary_.querySelector('.focus-row-active');
177       var toggleButton = this.getToggleElement();
179       if (toggleButton && !toggleButton.isShowingAll) {
180         var rows = this.focusGrid_.rows;
181         assert(rows.length > ExtensionErrorList.MAX_ERRORS_TO_SHOW_);
183         var firstVisible = rows.length - ExtensionErrorList.MAX_ERRORS_TO_SHOW_;
184         if (rows.indexOf(activeRow) < firstVisible)
185           activeRow = rows[firstVisible];
186       } else if (!activeRow) {
187         activeRow = this.focusGrid_.rows[0];
188       }
190       activeRow.getEquivalentElement(null).focus();
191     },
193     /**
194      * Initialize the "Show More" link for the error list. If there are more
195      * than |MAX_ERRORS_TO_SHOW_| errors in the list.
196      * @private
197      */
198     initShowMoreLink_: function() {
199       var link = this.querySelector(
200           '.extension-error-list-show-more [is="action-link"]');
201       link.hidden = false;
202       link.isShowingAll = false;
204       var listContents = this.querySelector('.extension-error-list-contents');
206       // TODO(dbeam/kalman): trade all this transition voodoo for .animate()?
207       listContents.addEventListener('webkitTransitionEnd', function(e) {
208         if (listContents.classList.contains('deactivating'))
209           listContents.classList.remove('deactivating', 'active');
210         else
211           listContents.classList.add('scrollable');
212       });
214       link.addEventListener('click', function(e) {
215         // Needs to be enabled in case the focused row is now hidden.
216         this.gridBoundary_.tabIndex = 0;
218         link.isShowingAll = !link.isShowingAll;
220         var message = link.isShowingAll ? 'extensionErrorsShowFewer' :
221                                           'extensionErrorsShowMore';
222         link.textContent = loadTimeData.getString(message);
224         // Disable scrolling while transitioning. If the element is active,
225         // scrolling is enabled when the transition ends.
226         listContents.classList.remove('scrollable');
228         if (link.isShowingAll) {
229           listContents.classList.add('active');
230           listContents.classList.remove('deactivating');
231         } else {
232           listContents.classList.add('deactivating');
233         }
234       }.bind(this));
235     }
236   };
238   return {
239     ExtensionErrorList: ExtensionErrorList
240   };