Add ENABLE_MEDIA_ROUTER define to builds other than Android and iOS.
[chromium-blink-merge.git] / chrome / browser / resources / options / content_settings_exceptions_area.js
blob97b6f43d071dc6f7a6de7a5bc90091ebf9998380
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 cr.define('options.contentSettings', function() {
6 /** @const */ var ControlledSettingIndicator =
7 options.ControlledSettingIndicator;
8 /** @const */ var InlineEditableItemList = options.InlineEditableItemList;
9 /** @const */ var InlineEditableItem = options.InlineEditableItem;
10 /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
12 /**
13 * Returns whether exceptions list for the type is editable.
15 * @param {string} contentType The type of the list.
17 function IsEditableType(contentType) {
18 // Exceptions of the following lists are not editable for now.
19 return !(contentType == 'notifications' ||
20 contentType == 'location' ||
21 contentType == 'fullscreen' ||
22 contentType == 'media-stream' ||
23 contentType == 'midi-sysex' ||
24 contentType == 'zoomlevels');
27 /**
28 * Creates a new exceptions list item.
30 * @param {string} contentType The type of the list.
31 * @param {string} mode The browser mode, 'otr' or 'normal'.
32 * @param {Object} exception A dictionary that contains the data of the
33 * exception.
34 * @constructor
35 * @extends {options.InlineEditableItem}
37 function ExceptionsListItem(contentType, mode, exception) {
38 var el = cr.doc.createElement('div');
39 el.mode = mode;
40 el.contentType = contentType;
41 el.dataItem = exception;
42 el.__proto__ = ExceptionsListItem.prototype;
43 el.decorate();
45 return el;
48 ExceptionsListItem.prototype = {
49 __proto__: InlineEditableItem.prototype,
51 /**
52 * Called when an element is decorated as a list item.
54 decorate: function() {
55 InlineEditableItem.prototype.decorate.call(this);
57 this.isPlaceholder = !this.pattern;
58 var patternCell = this.createEditableTextCell(this.pattern);
59 patternCell.className = 'exception-pattern';
60 patternCell.classList.add('weakrtl');
61 this.contentElement.appendChild(patternCell);
62 if (this.pattern)
63 this.patternLabel = patternCell.querySelector('.static-text');
64 var input = patternCell.querySelector('input');
66 // TODO(stuartmorgan): Create an createEditableSelectCell abstracting
67 // this code.
68 // Setting label for display mode. |pattern| will be null for the 'add new
69 // exception' row.
70 if (this.pattern) {
71 var settingLabel = cr.doc.createElement('span');
72 settingLabel.textContent = this.settingForDisplay();
73 settingLabel.className = 'exception-setting';
74 settingLabel.setAttribute('displaymode', 'static');
75 this.contentElement.appendChild(settingLabel);
76 this.settingLabel = settingLabel;
79 // Setting select element for edit mode.
80 var select = cr.doc.createElement('select');
81 var optionAllow = cr.doc.createElement('option');
82 optionAllow.textContent = loadTimeData.getString('allowException');
83 optionAllow.value = 'allow';
84 select.appendChild(optionAllow);
86 if (this.contentType == 'plugins') {
87 var optionDetect = cr.doc.createElement('option');
88 optionDetect.textContent = loadTimeData.getString('detectException');
89 optionDetect.value = 'detect';
90 select.appendChild(optionDetect);
93 if (this.contentType == 'cookies') {
94 var optionSession = cr.doc.createElement('option');
95 optionSession.textContent = loadTimeData.getString('sessionException');
96 optionSession.value = 'session';
97 select.appendChild(optionSession);
100 if (this.contentType != 'fullscreen') {
101 var optionBlock = cr.doc.createElement('option');
102 optionBlock.textContent = loadTimeData.getString('blockException');
103 optionBlock.value = 'block';
104 select.appendChild(optionBlock);
107 if (this.isEmbeddingRule()) {
108 this.patternLabel.classList.add('sublabel');
109 this.editable = false;
112 if (this.setting == 'default') {
113 // Items that don't have their own settings (parents of 'embedded on'
114 // items) aren't deletable.
115 this.deletable = false;
116 this.editable = false;
119 if (this.contentType != 'zoomlevels') {
120 this.addEditField(select, this.settingLabel);
121 this.contentElement.appendChild(select);
123 select.className = 'exception-setting';
124 select.setAttribute('aria-labelledby', 'exception-behavior-column');
126 if (this.pattern)
127 select.setAttribute('displaymode', 'edit');
129 if (this.contentType == 'media-stream') {
130 this.settingLabel.classList.add('media-audio-setting');
132 var videoSettingLabel = cr.doc.createElement('span');
133 videoSettingLabel.textContent = this.videoSettingForDisplay();
134 videoSettingLabel.className = 'exception-setting';
135 videoSettingLabel.classList.add('media-video-setting');
136 videoSettingLabel.setAttribute('displaymode', 'static');
137 this.contentElement.appendChild(videoSettingLabel);
140 if (this.contentType == 'zoomlevels') {
141 this.deletable = true;
143 var zoomLabel = cr.doc.createElement('span');
144 zoomLabel.textContent = this.dataItem.zoom;
145 zoomLabel.className = 'exception-setting';
146 zoomLabel.setAttribute('displaymode', 'static');
147 zoomLabel.setAttribute('aria-labelledby', 'exception-zoom-column');
148 this.contentElement.appendChild(zoomLabel);
149 this.zoomLabel = zoomLabel;
152 // Used to track whether the URL pattern in the input is valid.
153 // This will be true if the browser process has informed us that the
154 // current text in the input is valid. Changing the text resets this to
155 // false, and getting a response from the browser sets it back to true.
156 // It starts off as false for empty string (new exceptions) or true for
157 // already-existing exceptions (which we assume are valid).
158 this.inputValidityKnown = this.pattern;
159 // This one tracks the actual validity of the pattern in the input. This
160 // starts off as true so as not to annoy the user when he adds a new and
161 // empty input.
162 this.inputIsValid = true;
164 this.input = input;
165 this.select = select;
167 this.updateEditables();
168 this.editable = this.editable && IsEditableType(this.contentType);
170 // If the source of the content setting exception is not a user
171 // preference, that source controls the exception and the user cannot edit
172 // or delete it.
173 var controlledBy =
174 this.dataItem.source && this.dataItem.source != 'preference' ?
175 this.dataItem.source : null;
177 if (controlledBy) {
178 this.setAttribute('controlled-by', controlledBy);
179 this.deletable = false;
180 this.editable = false;
183 if (controlledBy == 'policy' || controlledBy == 'extension') {
184 this.querySelector('.row-delete-button').hidden = true;
185 var indicator = new ControlledSettingIndicator();
186 indicator.setAttribute('content-exception', this.contentType);
187 // Create a synthetic pref change event decorated as
188 // CoreOptionsHandler::CreateValueForPref() does.
189 var event = new Event(this.contentType);
190 event.value = { controlledBy: controlledBy };
191 indicator.handlePrefChange(event);
192 this.appendChild(indicator);
195 // If the exception comes from a hosted app, display the name and the
196 // icon of the app.
197 if (controlledBy == 'HostedApp') {
198 this.title =
199 loadTimeData.getString('setBy') + ' ' + this.dataItem.appName;
200 var button = this.querySelector('.row-delete-button');
201 // Use the host app's favicon (16px, match bigger size).
202 // See c/b/ui/webui/extensions/extension_icon_source.h
203 // for a description of the chrome://extension-icon URL.
204 button.style.backgroundImage =
205 'url(\'chrome://extension-icon/' + this.dataItem.appId + '/16/1\')';
208 var listItem = this;
209 // Handle events on the editable nodes.
210 input.oninput = function(event) {
211 listItem.inputValidityKnown = false;
212 chrome.send('checkExceptionPatternValidity',
213 [listItem.contentType, listItem.mode, input.value]);
216 // Listen for edit events.
217 this.addEventListener('canceledit', this.onEditCancelled_);
218 this.addEventListener('commitedit', this.onEditCommitted_);
221 isEmbeddingRule: function() {
222 return this.dataItem.embeddingOrigin &&
223 this.dataItem.embeddingOrigin !== this.dataItem.origin;
227 * The pattern (e.g., a URL) for the exception.
229 * @type {string}
231 get pattern() {
232 if (!this.isEmbeddingRule())
233 return this.dataItem.origin;
235 return loadTimeData.getStringF('embeddedOnHost',
236 this.dataItem.embeddingOrigin);
238 set pattern(pattern) {
239 if (!this.editable)
240 console.error('Tried to change uneditable pattern');
242 this.dataItem.displayPattern = pattern;
246 * The setting (allow/block) for the exception.
248 * @type {string}
250 get setting() {
251 return this.dataItem.setting;
253 set setting(setting) {
254 this.dataItem.setting = setting;
258 * Gets a human-readable setting string.
260 * @return {string} The display string.
262 settingForDisplay: function() {
263 return this.getDisplayStringForSetting(this.setting);
267 * media video specific function.
268 * Gets a human-readable video setting string.
270 * @return {string} The display string.
272 videoSettingForDisplay: function() {
273 return this.getDisplayStringForSetting(this.dataItem.video);
277 * Gets a human-readable display string for setting.
279 * @param {string} setting The setting to be displayed.
280 * @return {string} The display string.
282 getDisplayStringForSetting: function(setting) {
283 if (setting == 'allow')
284 return loadTimeData.getString('allowException');
285 else if (setting == 'block')
286 return loadTimeData.getString('blockException');
287 else if (setting == 'ask')
288 return loadTimeData.getString('askException');
289 else if (setting == 'session')
290 return loadTimeData.getString('sessionException');
291 else if (setting == 'detect')
292 return loadTimeData.getString('detectException');
293 else if (setting == 'default')
294 return '';
296 console.error('Unknown setting: [' + setting + ']');
297 return '';
301 * Update this list item to reflect whether the input is a valid pattern.
303 * @param {boolean} valid Whether said pattern is valid in the context of a
304 * content exception setting.
306 setPatternValid: function(valid) {
307 if (valid || !this.input.value)
308 this.input.setCustomValidity('');
309 else
310 this.input.setCustomValidity(' ');
311 this.inputIsValid = valid;
312 this.inputValidityKnown = true;
316 * Set the <input> to its original contents. Used when the user quits
317 * editing.
319 resetInput: function() {
320 this.input.value = this.pattern;
324 * Copy the data model values to the editable nodes.
326 updateEditables: function() {
327 this.resetInput();
329 var settingOption =
330 this.select.querySelector('[value=\'' + this.setting + '\']');
331 if (settingOption)
332 settingOption.selected = true;
335 /** @override */
336 get currentInputIsValid() {
337 return this.inputValidityKnown && this.inputIsValid;
340 /** @override */
341 get hasBeenEdited() {
342 var livePattern = this.input.value;
343 var liveSetting = this.select.value;
344 return livePattern != this.pattern || liveSetting != this.setting;
348 * Called when committing an edit.
350 * @param {Event} e The end event.
351 * @private
353 onEditCommitted_: function(e) {
354 var newPattern = this.input.value;
355 var newSetting = this.select.value;
357 this.finishEdit(newPattern, newSetting);
361 * Called when cancelling an edit; resets the control states.
363 * @param {Event} e The cancel event.
364 * @private
366 onEditCancelled_: function(e) {
367 this.updateEditables();
368 this.setPatternValid(true);
372 * Editing is complete; update the model.
374 * @param {string} newPattern The pattern that the user entered.
375 * @param {string} newSetting The setting the user chose.
377 finishEdit: function(newPattern, newSetting) {
378 this.patternLabel.textContent = newPattern;
379 this.settingLabel.textContent = this.settingForDisplay();
380 var oldPattern = this.pattern;
381 this.pattern = newPattern;
382 this.setting = newSetting;
384 // TODO(estade): this will need to be updated if geolocation/notifications
385 // become editable.
386 if (oldPattern != newPattern) {
387 chrome.send('removeException',
388 [this.contentType, this.mode, oldPattern]);
391 chrome.send('setException',
392 [this.contentType, this.mode, newPattern, newSetting]);
397 * Creates a new list item for the Add New Item row, which doesn't represent
398 * an actual entry in the exceptions list but allows the user to add new
399 * exceptions.
401 * @param {string} contentType The type of the list.
402 * @param {string} mode The browser mode, 'otr' or 'normal'.
403 * @constructor
404 * @extends {options.contentSettings.ExceptionsListItem}
406 function ExceptionsAddRowListItem(contentType, mode) {
407 var el = cr.doc.createElement('div');
408 el.mode = mode;
409 el.contentType = contentType;
410 el.dataItem = [];
411 el.__proto__ = ExceptionsAddRowListItem.prototype;
412 el.decorate();
414 return el;
417 ExceptionsAddRowListItem.prototype = {
418 __proto__: ExceptionsListItem.prototype,
420 decorate: function() {
421 ExceptionsListItem.prototype.decorate.call(this);
423 this.input.placeholder =
424 loadTimeData.getString('addNewExceptionInstructions');
426 // Do we always want a default of allow?
427 this.setting = 'allow';
431 * Clear the <input> and let the placeholder text show again.
433 resetInput: function() {
434 this.input.value = '';
437 /** @override */
438 get hasBeenEdited() {
439 return this.input.value != '';
443 * Editing is complete; update the model. As long as the pattern isn't
444 * empty, we'll just add it.
446 * @param {string} newPattern The pattern that the user entered.
447 * @param {string} newSetting The setting the user chose.
449 finishEdit: function(newPattern, newSetting) {
450 this.resetInput();
451 chrome.send('setException',
452 [this.contentType, this.mode, newPattern, newSetting]);
457 * Creates a new exceptions list.
459 * @constructor
460 * @extends {options.InlineEditableItemList}
462 var ExceptionsList = cr.ui.define('list');
464 ExceptionsList.prototype = {
465 __proto__: InlineEditableItemList.prototype,
468 * Called when an element is decorated as a list.
470 decorate: function() {
471 InlineEditableItemList.prototype.decorate.call(this);
473 this.classList.add('settings-list');
475 for (var parentNode = this.parentNode; parentNode;
476 parentNode = parentNode.parentNode) {
477 if (parentNode.hasAttribute('contentType')) {
478 this.contentType = parentNode.getAttribute('contentType');
479 break;
483 this.mode = this.getAttribute('mode');
484 this.autoExpands = true;
485 this.reset();
489 * Creates an item to go in the list.
491 * @param {Object} entry The element from the data model for this row.
493 createItem: function(entry) {
494 if (entry) {
495 return new ExceptionsListItem(this.contentType,
496 this.mode,
497 entry);
498 } else {
499 var addRowItem = new ExceptionsAddRowListItem(this.contentType,
500 this.mode);
501 addRowItem.deletable = false;
502 return addRowItem;
507 * Sets the exceptions in the js model.
509 * @param {Array<options.Exception>} entries A list of dictionaries of
510 * values, each dictionary represents an exception.
512 setExceptions: function(entries) {
513 var deleteCount = this.dataModel.length;
515 if (this.isEditable()) {
516 // We don't want to remove the Add New Exception row.
517 deleteCount = deleteCount - 1;
520 var args = [0, deleteCount];
521 args.push.apply(args, entries);
522 this.dataModel.splice.apply(this.dataModel, args);
526 * The browser has finished checking a pattern for validity. Update the list
527 * item to reflect this.
529 * @param {string} pattern The pattern.
530 * @param {boolean} valid Whether said pattern is valid in the context of a
531 * content exception setting.
533 patternValidityCheckComplete: function(pattern, valid) {
534 var listItems = this.items;
535 for (var i = 0; i < listItems.length; i++) {
536 var listItem = listItems[i];
537 // Don't do anything for messages for the item if it is not the intended
538 // recipient, or if the response is stale (i.e. the input value has
539 // changed since we sent the request to analyze it).
540 if (pattern == listItem.input.value)
541 listItem.setPatternValid(valid);
546 * Returns whether the rows are editable in this list.
548 isEditable: function() {
549 // Exceptions of the following lists are not editable for now.
550 return IsEditableType(this.contentType);
554 * Removes all exceptions from the js model.
556 reset: function() {
557 if (this.isEditable()) {
558 // The null creates the Add New Exception row.
559 this.dataModel = new ArrayDataModel([null]);
560 } else {
561 this.dataModel = new ArrayDataModel([]);
565 /** @override */
566 deleteItemAtIndex: function(index) {
567 var listItem = this.getListItemByIndex(index);
568 if (!listItem.deletable)
569 return;
571 var dataItem = listItem.dataItem;
572 chrome.send('removeException', [listItem.contentType,
573 listItem.mode,
574 dataItem.origin,
575 dataItem.embeddingOrigin]);
579 var Page = cr.ui.pageManager.Page;
580 var PageManager = cr.ui.pageManager.PageManager;
583 * Encapsulated handling of content settings list subpage.
585 * @constructor
586 * @extends {cr.ui.pageManager.Page}
588 function ContentSettingsExceptionsArea() {
589 Page.call(this, 'contentExceptions',
590 loadTimeData.getString('contentSettingsPageTabTitle'),
591 'content-settings-exceptions-area');
594 cr.addSingletonGetter(ContentSettingsExceptionsArea);
596 ContentSettingsExceptionsArea.prototype = {
597 __proto__: Page.prototype,
599 /** @override */
600 initializePage: function() {
601 Page.prototype.initializePage.call(this);
603 var exceptionsLists = this.pageDiv.querySelectorAll('list');
604 for (var i = 0; i < exceptionsLists.length; i++) {
605 options.contentSettings.ExceptionsList.decorate(exceptionsLists[i]);
608 ContentSettingsExceptionsArea.hideOTRLists(false);
610 // If the user types in the URL without a hash, show just cookies.
611 this.showList('cookies');
613 $('content-settings-exceptions-overlay-confirm').onclick =
614 PageManager.closeOverlay.bind(PageManager);
618 * Shows one list and hides all others.
620 * @param {string} type The content type.
622 showList: function(type) {
623 // Update the title for the type that was shown.
624 this.title = loadTimeData.getString(type + 'TabTitle');
626 var header = this.pageDiv.querySelector('h1');
627 var camelCasedType = type.replace(/-([a-z])/g, function(g) {
628 return g[1].toUpperCase();
630 header.textContent = loadTimeData.getString(camelCasedType + 'Header');
632 var divs = this.pageDiv.querySelectorAll('div[contentType]');
633 for (var i = 0; i < divs.length; i++) {
634 if (divs[i].getAttribute('contentType') == type)
635 divs[i].hidden = false;
636 else
637 divs[i].hidden = true;
640 var mediaHeader = this.pageDiv.querySelector('.media-header');
641 mediaHeader.hidden = type != 'media-stream';
643 $('exception-behavior-column').hidden = type == 'zoomlevels';
644 $('exception-zoom-column').hidden = type != 'zoomlevels';
648 * Called after the page has been shown. Show the content type for the
649 * location's hash.
651 didShowPage: function() {
652 if (this.hash)
653 this.showList(this.hash.slice(1));
658 * Called when the last incognito window is closed.
660 ContentSettingsExceptionsArea.OTRProfileDestroyed = function() {
661 this.hideOTRLists(true);
665 * Hides the incognito exceptions lists and optionally clears them as well.
666 * @param {boolean} clear Whether to clear the lists.
668 ContentSettingsExceptionsArea.hideOTRLists = function(clear) {
669 var otrLists = document.querySelectorAll('list[mode=otr]');
671 for (var i = 0; i < otrLists.length; i++) {
672 otrLists[i].parentNode.hidden = true;
673 if (clear)
674 otrLists[i].reset();
678 return {
679 ExceptionsListItem: ExceptionsListItem,
680 ExceptionsAddRowListItem: ExceptionsAddRowListItem,
681 ExceptionsList: ExceptionsList,
682 ContentSettingsExceptionsArea: ContentSettingsExceptionsArea,