Only grant permissions to new extensions from sync if they have the expected version
[chromium-blink-merge.git] / chrome / browser / resources / options / search_engine_manager_engine_list.js
blobb2c6c1169d03f27451df9f55a7ef4624b3596905
1 // Copyright (c) 2012 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 /**
6  * @typedef {{canBeDefault: boolean,
7  *            canBeEdited: boolean,
8  *            canBeRemoved: boolean,
9  *            default: boolean,
10  *            displayName: string,
11  *            extension: (Object|undefined),
12  *            iconURL: (string|undefined),
13  *            isOmniboxExtension: boolean,
14  *            keyword: string,
15  *            modelIndex: string,
16  *            name: string,
17  *            url: string,
18  *            urlLocked: boolean}}
19  * @see chrome/browser/ui/webui/options/search_engine_manager_handler.cc
20  */
21 var SearchEngine;
23 cr.define('options.search_engines', function() {
24   /** @const */ var ControlledSettingIndicator =
25                     options.ControlledSettingIndicator;
26   /** @const */ var InlineEditableItemList = options.InlineEditableItemList;
27   /** @const */ var InlineEditableItem = options.InlineEditableItem;
28   /** @const */ var ListSelectionController = cr.ui.ListSelectionController;
30   /**
31    * Creates a new search engine list item.
32    * @param {SearchEngine} searchEngine The search engine this represents.
33    * @constructor
34    * @extends {options.InlineEditableItem}
35    */
36   function SearchEngineListItem(searchEngine) {
37     var el = cr.doc.createElement('div');
38     el.searchEngine_ = searchEngine;
39     SearchEngineListItem.decorate(el);
40     return el;
41   }
43   /**
44    * Decorates an element as a search engine list item.
45    * @param {!HTMLElement} el The element to decorate.
46    */
47   SearchEngineListItem.decorate = function(el) {
48     el.__proto__ = SearchEngineListItem.prototype;
49     el.decorate();
50   };
52   SearchEngineListItem.prototype = {
53     __proto__: InlineEditableItem.prototype,
55     /**
56      * Input field for editing the engine name.
57      * @type {HTMLElement}
58      * @private
59      */
60     nameField_: null,
62     /**
63      * Input field for editing the engine keyword.
64      * @type {HTMLElement}
65      * @private
66      */
67     keywordField_: null,
69     /**
70      * Input field for editing the engine url.
71      * @type {HTMLElement}
72      * @private
73      */
74     urlField_: null,
76     /**
77      * Whether or not an input validation request is currently outstanding.
78      * @type {boolean}
79      * @private
80      */
81     waitingForValidation_: false,
83     /**
84      * Whether or not the current set of input is known to be valid.
85      * @type {boolean}
86      * @private
87      */
88     currentlyValid_: false,
90     /**
91      * @type {?SearchEngine}
92      */
93     searchEngine_: null,
95     /** @override */
96     decorate: function() {
97       InlineEditableItem.prototype.decorate.call(this);
99       var engine = this.searchEngine_;
101       if (engine.modelIndex == '-1') {
102         this.isPlaceholder = true;
103         engine.name = '';
104         engine.keyword = '';
105         engine.url = '';
106       }
108       this.currentlyValid_ = !this.isPlaceholder;
110       if (engine.default)
111         this.classList.add('default');
113       this.deletable = engine.canBeRemoved;
114       this.closeButtonFocusAllowed = true;
116       // Construct the name column.
117       var nameColEl = this.ownerDocument.createElement('div');
118       nameColEl.className = 'name-column';
119       nameColEl.classList.add('weakrtl');
120       this.contentElement.appendChild(nameColEl);
122       // Add the favicon.
123       var faviconDivEl = this.ownerDocument.createElement('div');
124       faviconDivEl.className = 'favicon';
125       if (!this.isPlaceholder) {
126         faviconDivEl.style.backgroundImage = imageset(
127             'chrome://favicon/size/16@scalefactorx/iconurl/' + engine.iconURL);
128       }
129       nameColEl.appendChild(faviconDivEl);
131       var nameEl = this.createEditableTextCell(engine.displayName);
132       nameEl.classList.add('weakrtl');
133       nameColEl.appendChild(nameEl);
135       // Then the keyword column.
136       var keywordEl = this.createEditableTextCell(engine.keyword);
137       keywordEl.className = 'keyword-column';
138       keywordEl.classList.add('weakrtl');
139       this.contentElement.appendChild(keywordEl);
141       // And the URL column.
142       var urlEl = this.createEditableTextCell(engine.url);
143       var makeDefaultButtonEl = null;
144       // Extensions should not display a URL column.
145       if (!engine.isOmniboxExtension) {
146         var urlWithButtonEl = this.ownerDocument.createElement('div');
147         urlWithButtonEl.appendChild(urlEl);
148         urlWithButtonEl.className = 'url-column';
149         urlWithButtonEl.classList.add('weakrtl');
150         this.contentElement.appendChild(urlWithButtonEl);
151         // Add the Make Default button. Temporary until drag-and-drop
152         // re-ordering is implemented. When this is removed, remove the extra
153         // div above.
154         if (engine.canBeDefault) {
155           makeDefaultButtonEl = this.ownerDocument.createElement('button');
156           makeDefaultButtonEl.className =
157               'custom-appearance list-inline-button';
158           makeDefaultButtonEl.textContent =
159               loadTimeData.getString('makeDefaultSearchEngineButton');
160           makeDefaultButtonEl.onclick = function(e) {
161             chrome.send('managerSetDefaultSearchEngine', [engine.modelIndex]);
162           };
163           makeDefaultButtonEl.onmousedown = function(e) {
164             // Don't select the row when clicking the button.
165             e.stopPropagation();
166             // Don't focus on the button.
167             e.preventDefault();
168           };
169           urlWithButtonEl.appendChild(makeDefaultButtonEl);
170         }
171       }
173       // Do final adjustment to the input fields.
174       this.nameField_ = /** @type {HTMLElement} */(
175           nameEl.querySelector('input'));
176       // The editable field uses the raw name, not the display name.
177       this.nameField_.value = engine.name;
178       this.keywordField_ = /** @type {HTMLElement} */(
179           keywordEl.querySelector('input'));
180       this.urlField_ = /** @type {HTMLElement} */(urlEl.querySelector('input'));
182       if (engine.urlLocked)
183         this.urlField_.disabled = true;
185       if (this.isPlaceholder) {
186         this.nameField_.placeholder =
187             loadTimeData.getString('searchEngineTableNamePlaceholder');
188         this.keywordField_.placeholder =
189             loadTimeData.getString('searchEngineTableKeywordPlaceholder');
190         this.urlField_.placeholder =
191             loadTimeData.getString('searchEngineTableURLPlaceholder');
192       }
194       this.setFocusableColumnIndex(this.nameField_, 0);
195       this.setFocusableColumnIndex(this.keywordField_, 1);
196       this.setFocusableColumnIndex(this.urlField_, 2);
197       this.setFocusableColumnIndex(makeDefaultButtonEl, 3);
198       this.setFocusableColumnIndex(this.closeButtonElement, 4);
200       var fields = [this.nameField_, this.keywordField_, this.urlField_];
201         for (var i = 0; i < fields.length; i++) {
202         fields[i].oninput = this.startFieldValidation_.bind(this);
203       }
205       // Listen for edit events.
206       if (engine.canBeEdited) {
207         this.addEventListener('edit', this.onEditStarted_.bind(this));
208         this.addEventListener('canceledit', this.onEditCancelled_.bind(this));
209         this.addEventListener('commitedit', this.onEditCommitted_.bind(this));
210       } else {
211         this.editable = false;
212         this.querySelector('.row-delete-button').hidden = true;
213         var indicator = new ControlledSettingIndicator();
214         indicator.setAttribute('setting', 'search-engine');
215         // Create a synthetic pref change event decorated as
216         // CoreOptionsHandler::CreateValueForPref() does.
217         var event = new Event(this.contentType);
218         if (engine.extension) {
219           event.value = { controlledBy: 'extension',
220                           extension: engine.extension };
221         } else {
222           event.value = { controlledBy: 'policy' };
223         }
224         indicator.handlePrefChange(event);
225         this.appendChild(indicator);
226       }
227     },
229     /** @override */
230     get currentInputIsValid() {
231       return !this.waitingForValidation_ && this.currentlyValid_;
232     },
234     /** @override */
235     get hasBeenEdited() {
236       var engine = this.searchEngine_;
237       return this.nameField_.value != engine.name ||
238              this.keywordField_.value != engine.keyword ||
239              this.urlField_.value != engine.url;
240     },
242     /**
243      * Called when entering edit mode; starts an edit session in the model.
244      * @param {Event} e The edit event.
245      * @private
246      */
247     onEditStarted_: function(e) {
248       var editIndex = this.searchEngine_.modelIndex;
249       chrome.send('editSearchEngine', [String(editIndex)]);
250       this.startFieldValidation_();
251     },
253     /**
254      * Called when committing an edit; updates the model.
255      * @param {Event} e The end event.
256      * @private
257      */
258     onEditCommitted_: function(e) {
259       chrome.send('searchEngineEditCompleted', this.getInputFieldValues_());
260     },
262     /**
263      * Called when cancelling an edit; informs the model and resets the control
264      * states.
265      * @param {Event} e The cancel event.
266      * @private
267      */
268     onEditCancelled_: function(e) {
269       chrome.send('searchEngineEditCancelled');
271       // The name field has been automatically set to match the display name,
272       // but it should use the raw name instead.
273       this.nameField_.value = this.searchEngine_.name;
274       this.currentlyValid_ = !this.isPlaceholder;
275     },
277     /**
278      * Returns the input field values as an array suitable for passing to
279      * chrome.send. The order of the array is important.
280      * @private
281      * @return {Array} The current input field values.
282      */
283     getInputFieldValues_: function() {
284       return [this.nameField_.value,
285               this.keywordField_.value,
286               this.urlField_.value];
287     },
289     /**
290      * Begins the process of asynchronously validing the input fields.
291      * @private
292      */
293     startFieldValidation_: function() {
294       this.waitingForValidation_ = true;
295       var args = this.getInputFieldValues_();
296       args.push(this.searchEngine_.modelIndex);
297       chrome.send('checkSearchEngineInfoValidity', args);
298     },
300     /**
301      * Callback for the completion of an input validition check.
302      * @param {Object} validity A dictionary of validitation results.
303      */
304     validationComplete: function(validity) {
305       this.waitingForValidation_ = false;
306       // TODO(stuartmorgan): Implement the full validation UI with
307       // checkmark/exclamation mark icons and tooltips showing the errors.
308       if (validity.name) {
309         this.nameField_.setCustomValidity('');
310       } else {
311         this.nameField_.setCustomValidity(
312             loadTimeData.getString('editSearchEngineInvalidTitleToolTip'));
313       }
315       if (validity.keyword) {
316         this.keywordField_.setCustomValidity('');
317       } else {
318         this.keywordField_.setCustomValidity(
319             loadTimeData.getString('editSearchEngineInvalidKeywordToolTip'));
320       }
322       if (validity.url) {
323         this.urlField_.setCustomValidity('');
324       } else {
325         this.urlField_.setCustomValidity(
326             loadTimeData.getString('editSearchEngineInvalidURLToolTip'));
327       }
329       this.currentlyValid_ = validity.name && validity.keyword && validity.url;
330     },
331   };
333   /**
334    * @constructor
335    * @extends {options.InlineEditableItemList}
336    */
337   var SearchEngineList = cr.ui.define('list');
339   SearchEngineList.prototype = {
340     __proto__: InlineEditableItemList.prototype,
342     /**
343      * @override
344      * @param {SearchEngine} searchEngine
345      */
346     createItem: function(searchEngine) {
347       return new SearchEngineListItem(searchEngine);
348     },
350     /** @override */
351     deleteItemAtIndex: function(index) {
352       var modelIndex = this.dataModel.item(index).modelIndex;
353       chrome.send('removeSearchEngine', [String(modelIndex)]);
354     },
356     /**
357      * Passes the results of an input validation check to the requesting row
358      * if it's still being edited.
359      * @param {number} modelIndex The model index of the item that was checked.
360      * @param {Object} validity A dictionary of validitation results.
361      */
362     validationComplete: function(validity, modelIndex) {
363       // If it's not still being edited, it no longer matters.
364       var currentSelection = this.selectedItem;
365       if (!currentSelection)
366         return;
367       var listItem = this.getListItem(currentSelection);
368       if (listItem.editing && currentSelection.modelIndex == modelIndex)
369         listItem.validationComplete(validity);
370     },
371   };
373   // Export
374   return {
375     SearchEngineList: SearchEngineList
376   };