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
;
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');
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
35 * @extends {options.InlineEditableItem}
37 function ExceptionsListItem(contentType
, mode
, exception
) {
38 var el
= cr
.doc
.createElement('div');
40 el
.contentType
= contentType
;
41 el
.dataItem
= exception
;
42 el
.__proto__
= ExceptionsListItem
.prototype;
48 ExceptionsListItem
.prototype = {
49 __proto__
: InlineEditableItem
.prototype,
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
);
63 this.patternLabel
= patternCell
.querySelector('.static-text');
64 var input
= patternCell
.querySelector('input');
66 // TODO(stuartmorgan): Create an createEditableSelectCell abstracting
68 // Setting label for display mode. |pattern| will be null for the 'add new
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');
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
162 this.inputIsValid
= true;
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
174 this.dataItem
.source
&& this.dataItem
.source
!= 'preference' ?
175 this.dataItem
.source
: null;
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
197 if (controlledBy
== 'HostedApp') {
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\')';
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.
232 if (!this.isEmbeddingRule())
233 return this.dataItem
.origin
;
235 return loadTimeData
.getStringF('embeddedOnHost',
236 this.dataItem
.embeddingOrigin
);
238 set pattern(pattern
) {
240 console
.error('Tried to change uneditable pattern');
242 this.dataItem
.displayPattern
= pattern
;
246 * The setting (allow/block) for the exception.
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')
296 console
.error('Unknown setting: [' + setting
+ ']');
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('');
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
319 resetInput: function() {
320 this.input
.value
= this.pattern
;
324 * Copy the data model values to the editable nodes.
326 updateEditables: function() {
330 this.select
.querySelector('[value=\'' + this.setting
+ '\']');
332 settingOption
.selected
= true;
336 get currentInputIsValid() {
337 return this.inputValidityKnown
&& this.inputIsValid
;
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.
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.
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
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
401 * @param {string} contentType The type of the list.
402 * @param {string} mode The browser mode, 'otr' or 'normal'.
404 * @extends {options.contentSettings.ExceptionsListItem}
406 function ExceptionsAddRowListItem(contentType
, mode
) {
407 var el
= cr
.doc
.createElement('div');
409 el
.contentType
= contentType
;
411 el
.__proto__
= ExceptionsAddRowListItem
.prototype;
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
= '';
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
) {
451 chrome
.send('setException',
452 [this.contentType
, this.mode
, newPattern
, newSetting
]);
457 * Creates a new exceptions list.
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');
483 this.mode
= this.getAttribute('mode');
484 this.autoExpands
= true;
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
) {
495 return new ExceptionsListItem(this.contentType
,
499 var addRowItem
= new ExceptionsAddRowListItem(this.contentType
,
501 addRowItem
.deletable
= false;
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.
557 if (this.isEditable()) {
558 // The null creates the Add New Exception row.
559 this.dataModel
= new ArrayDataModel([null]);
561 this.dataModel
= new ArrayDataModel([]);
566 deleteItemAtIndex: function(index
) {
567 var listItem
= this.getListItemByIndex(index
);
568 if (!listItem
.deletable
)
571 var dataItem
= listItem
.dataItem
;
572 chrome
.send('removeException', [listItem
.contentType
,
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.
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,
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;
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
651 didShowPage: function() {
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;
679 ExceptionsListItem
: ExceptionsListItem
,
680 ExceptionsAddRowListItem
: ExceptionsAddRowListItem
,
681 ExceptionsList
: ExceptionsList
,
682 ContentSettingsExceptionsArea
: ContentSettingsExceptionsArea
,