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-mic' ||
23 contentType
== 'media-stream-camera' ||
24 contentType
== 'midi-sysex' ||
25 contentType
== 'zoomlevels');
29 * Creates a new exceptions list item.
31 * @param {string} contentType The type of the list.
32 * @param {string} mode The browser mode, 'otr' or 'normal'.
33 * @param {Object} exception A dictionary that contains the data of the
36 * @extends {options.InlineEditableItem}
38 function ExceptionsListItem(contentType
, mode
, exception
) {
39 var el
= cr
.doc
.createElement('div');
41 el
.contentType
= contentType
;
42 el
.dataItem
= exception
;
43 el
.__proto__
= ExceptionsListItem
.prototype;
49 ExceptionsListItem
.prototype = {
50 __proto__
: InlineEditableItem
.prototype,
53 * Called when an element is decorated as a list item.
55 decorate: function() {
56 InlineEditableItem
.prototype.decorate
.call(this);
58 this.isPlaceholder
= !this.pattern
;
59 var patternCell
= this.createEditableTextCell(this.pattern
);
60 patternCell
.className
= 'exception-pattern';
61 patternCell
.classList
.add('weakrtl');
62 this.contentElement
.appendChild(patternCell
);
64 this.patternLabel
= patternCell
.querySelector('.static-text');
65 var input
= patternCell
.querySelector('input');
67 // TODO(stuartmorgan): Create an createEditableSelectCell abstracting
69 // Setting label for display mode. |pattern| will be null for the 'add new
72 var settingLabel
= cr
.doc
.createElement('span');
73 settingLabel
.textContent
= this.settingForDisplay();
74 settingLabel
.className
= 'exception-setting';
75 settingLabel
.setAttribute('displaymode', 'static');
76 this.contentElement
.appendChild(settingLabel
);
77 this.settingLabel
= settingLabel
;
80 // Setting select element for edit mode.
81 var select
= cr
.doc
.createElement('select');
82 var optionAllow
= cr
.doc
.createElement('option');
83 optionAllow
.textContent
= loadTimeData
.getString('allowException');
84 optionAllow
.value
= 'allow';
85 select
.appendChild(optionAllow
);
87 if (this.contentType
== 'plugins') {
88 var optionDetect
= cr
.doc
.createElement('option');
89 optionDetect
.textContent
= loadTimeData
.getString('detectException');
90 optionDetect
.value
= 'detect';
91 select
.appendChild(optionDetect
);
94 if (this.contentType
== 'cookies') {
95 var optionSession
= cr
.doc
.createElement('option');
96 optionSession
.textContent
= loadTimeData
.getString('sessionException');
97 optionSession
.value
= 'session';
98 select
.appendChild(optionSession
);
101 if (this.contentType
!= 'fullscreen') {
102 var optionBlock
= cr
.doc
.createElement('option');
103 optionBlock
.textContent
= loadTimeData
.getString('blockException');
104 optionBlock
.value
= 'block';
105 select
.appendChild(optionBlock
);
108 if (this.isEmbeddingRule()) {
109 this.patternLabel
.classList
.add('sublabel');
110 this.editable
= false;
113 if (this.setting
== 'default') {
114 // Items that don't have their own settings (parents of 'embedded on'
115 // items) aren't deletable.
116 this.deletable
= false;
117 this.editable
= false;
120 if (this.contentType
!= 'zoomlevels') {
121 this.addEditField(select
, this.settingLabel
);
122 this.contentElement
.appendChild(select
);
124 select
.className
= 'exception-setting';
125 select
.setAttribute('aria-labelledby', 'exception-behavior-column');
128 select
.setAttribute('displaymode', 'edit');
130 if (this.contentType
== 'zoomlevels') {
131 this.deletable
= true;
133 var zoomLabel
= cr
.doc
.createElement('span');
134 zoomLabel
.textContent
= this.dataItem
.zoom
;
135 zoomLabel
.className
= 'exception-setting';
136 zoomLabel
.setAttribute('displaymode', 'static');
137 zoomLabel
.setAttribute('aria-labelledby', 'exception-zoom-column');
138 this.contentElement
.appendChild(zoomLabel
);
139 this.zoomLabel
= zoomLabel
;
142 // Used to track whether the URL pattern in the input is valid.
143 // This will be true if the browser process has informed us that the
144 // current text in the input is valid. Changing the text resets this to
145 // false, and getting a response from the browser sets it back to true.
146 // It starts off as false for empty string (new exceptions) or true for
147 // already-existing exceptions (which we assume are valid).
148 this.inputValidityKnown
= this.pattern
;
149 // This one tracks the actual validity of the pattern in the input. This
150 // starts off as true so as not to annoy the user when he adds a new and
152 this.inputIsValid
= true;
155 this.select
= select
;
157 this.updateEditables();
158 this.editable
= this.editable
&& IsEditableType(this.contentType
);
160 // If the source of the content setting exception is not a user
161 // preference, that source controls the exception and the user cannot edit
164 this.dataItem
.source
&& this.dataItem
.source
!= 'preference' ?
165 this.dataItem
.source
: null;
168 this.setAttribute('controlled-by', controlledBy
);
169 this.deletable
= false;
170 this.editable
= false;
173 if (controlledBy
== 'policy' || controlledBy
== 'extension') {
174 this.querySelector('.row-delete-button').hidden
= true;
175 var indicator
= new ControlledSettingIndicator();
176 indicator
.setAttribute('content-exception', this.contentType
);
177 // Create a synthetic pref change event decorated as
178 // CoreOptionsHandler::CreateValueForPref() does.
179 var event
= new Event(this.contentType
);
180 event
.value
= { controlledBy
: controlledBy
};
181 indicator
.handlePrefChange(event
);
182 this.appendChild(indicator
);
185 // If the exception comes from a hosted app, display the name and the
187 if (controlledBy
== 'HostedApp') {
189 loadTimeData
.getString('setBy') + ' ' + this.dataItem
.appName
;
190 var button
= this.querySelector('.row-delete-button');
191 // Use the host app's favicon (16px, match bigger size).
192 // See c/b/ui/webui/extensions/extension_icon_source.h
193 // for a description of the chrome://extension-icon URL.
194 button
.style
.backgroundImage
=
195 'url(\'chrome://extension-icon/' + this.dataItem
.appId
+ '/16/1\')';
199 // Handle events on the editable nodes.
200 input
.oninput = function(event
) {
201 listItem
.inputValidityKnown
= false;
202 chrome
.send('checkExceptionPatternValidity',
203 [listItem
.contentType
, listItem
.mode
, input
.value
]);
206 // Listen for edit events.
207 this.addEventListener('canceledit', this.onEditCancelled_
);
208 this.addEventListener('commitedit', this.onEditCommitted_
);
211 isEmbeddingRule: function() {
212 return this.dataItem
.embeddingOrigin
&&
213 this.dataItem
.embeddingOrigin
!== this.dataItem
.origin
;
217 * The pattern (e.g., a URL) for the exception.
222 if (!this.isEmbeddingRule())
223 return this.dataItem
.origin
;
225 return loadTimeData
.getStringF('embeddedOnHost',
226 this.dataItem
.embeddingOrigin
);
228 set pattern(pattern
) {
230 console
.error('Tried to change uneditable pattern');
232 this.dataItem
.displayPattern
= pattern
;
236 * The setting (allow/block) for the exception.
241 return this.dataItem
.setting
;
243 set setting(setting
) {
244 this.dataItem
.setting
= setting
;
248 * Gets a human-readable setting string.
250 * @return {string} The display string.
252 settingForDisplay: function() {
253 return this.getDisplayStringForSetting(this.setting
);
257 * Gets a human-readable display string for setting.
259 * @param {string} setting The setting to be displayed.
260 * @return {string} The display string.
262 getDisplayStringForSetting: function(setting
) {
263 if (setting
== 'allow')
264 return loadTimeData
.getString('allowException');
265 else if (setting
== 'block')
266 return loadTimeData
.getString('blockException');
267 else if (setting
== 'ask')
268 return loadTimeData
.getString('askException');
269 else if (setting
== 'session')
270 return loadTimeData
.getString('sessionException');
271 else if (setting
== 'detect')
272 return loadTimeData
.getString('detectException');
273 else if (setting
== 'default')
276 console
.error('Unknown setting: [' + setting
+ ']');
281 * Update this list item to reflect whether the input is a valid pattern.
283 * @param {boolean} valid Whether said pattern is valid in the context of a
284 * content exception setting.
286 setPatternValid: function(valid
) {
287 if (valid
|| !this.input
.value
)
288 this.input
.setCustomValidity('');
290 this.input
.setCustomValidity(' ');
291 this.inputIsValid
= valid
;
292 this.inputValidityKnown
= true;
296 * Set the <input> to its original contents. Used when the user quits
299 resetInput: function() {
300 this.input
.value
= this.pattern
;
304 * Copy the data model values to the editable nodes.
306 updateEditables: function() {
310 this.select
.querySelector('[value=\'' + this.setting
+ '\']');
312 settingOption
.selected
= true;
316 get currentInputIsValid() {
317 return this.inputValidityKnown
&& this.inputIsValid
;
321 get hasBeenEdited() {
322 var livePattern
= this.input
.value
;
323 var liveSetting
= this.select
.value
;
324 return livePattern
!= this.pattern
|| liveSetting
!= this.setting
;
328 * Called when committing an edit.
330 * @param {Event} e The end event.
333 onEditCommitted_: function(e
) {
334 var newPattern
= this.input
.value
;
335 var newSetting
= this.select
.value
;
337 this.finishEdit(newPattern
, newSetting
);
341 * Called when cancelling an edit; resets the control states.
343 * @param {Event} e The cancel event.
346 onEditCancelled_: function(e
) {
347 this.updateEditables();
348 this.setPatternValid(true);
352 * Editing is complete; update the model.
354 * @param {string} newPattern The pattern that the user entered.
355 * @param {string} newSetting The setting the user chose.
357 finishEdit: function(newPattern
, newSetting
) {
358 this.patternLabel
.textContent
= newPattern
;
359 this.settingLabel
.textContent
= this.settingForDisplay();
360 var oldPattern
= this.pattern
;
361 this.pattern
= newPattern
;
362 this.setting
= newSetting
;
364 // TODO(estade): this will need to be updated if geolocation/notifications
366 if (oldPattern
!= newPattern
) {
367 chrome
.send('removeException',
368 [this.contentType
, this.mode
, oldPattern
]);
371 chrome
.send('setException',
372 [this.contentType
, this.mode
, newPattern
, newSetting
]);
377 * Creates a new list item for the Add New Item row, which doesn't represent
378 * an actual entry in the exceptions list but allows the user to add new
381 * @param {string} contentType The type of the list.
382 * @param {string} mode The browser mode, 'otr' or 'normal'.
384 * @extends {options.contentSettings.ExceptionsListItem}
386 function ExceptionsAddRowListItem(contentType
, mode
) {
387 var el
= cr
.doc
.createElement('div');
389 el
.contentType
= contentType
;
391 el
.__proto__
= ExceptionsAddRowListItem
.prototype;
397 ExceptionsAddRowListItem
.prototype = {
398 __proto__
: ExceptionsListItem
.prototype,
400 decorate: function() {
401 ExceptionsListItem
.prototype.decorate
.call(this);
403 this.input
.placeholder
=
404 loadTimeData
.getString('addNewExceptionInstructions');
406 // Do we always want a default of allow?
407 this.setting
= 'allow';
411 * Clear the <input> and let the placeholder text show again.
413 resetInput: function() {
414 this.input
.value
= '';
418 get hasBeenEdited() {
419 return this.input
.value
!= '';
423 * Editing is complete; update the model. As long as the pattern isn't
424 * empty, we'll just add it.
426 * @param {string} newPattern The pattern that the user entered.
427 * @param {string} newSetting The setting the user chose.
429 finishEdit: function(newPattern
, newSetting
) {
431 chrome
.send('setException',
432 [this.contentType
, this.mode
, newPattern
, newSetting
]);
437 * Creates a new exceptions list.
440 * @extends {options.InlineEditableItemList}
442 var ExceptionsList
= cr
.ui
.define('list');
444 ExceptionsList
.prototype = {
445 __proto__
: InlineEditableItemList
.prototype,
448 * Called when an element is decorated as a list.
450 decorate: function() {
451 InlineEditableItemList
.prototype.decorate
.call(this);
453 this.classList
.add('settings-list');
455 for (var parentNode
= this.parentNode
; parentNode
;
456 parentNode
= parentNode
.parentNode
) {
457 if (parentNode
.hasAttribute('contentType')) {
458 this.contentType
= parentNode
.getAttribute('contentType');
463 if (!this.isEditable())
466 this.mode
= this.getAttribute('mode');
467 this.autoExpands
= true;
472 * Creates an item to go in the list.
474 * @param {Object} entry The element from the data model for this row.
476 createItem: function(entry
) {
478 return new ExceptionsListItem(this.contentType
,
482 var addRowItem
= new ExceptionsAddRowListItem(this.contentType
,
484 addRowItem
.deletable
= false;
490 * Sets the exceptions in the js model.
492 * @param {Array<options.Exception>} entries A list of dictionaries of
493 * values, each dictionary represents an exception.
495 setExceptions: function(entries
) {
496 var deleteCount
= this.dataModel
.length
;
498 if (this.isEditable()) {
499 // We don't want to remove the Add New Exception row.
500 deleteCount
= deleteCount
- 1;
503 var args
= [0, deleteCount
];
504 args
.push
.apply(args
, entries
);
505 this.dataModel
.splice
.apply(this.dataModel
, args
);
509 * The browser has finished checking a pattern for validity. Update the list
510 * item to reflect this.
512 * @param {string} pattern The pattern.
513 * @param {boolean} valid Whether said pattern is valid in the context of a
514 * content exception setting.
516 patternValidityCheckComplete: function(pattern
, valid
) {
517 var listItems
= this.items
;
518 for (var i
= 0; i
< listItems
.length
; i
++) {
519 var listItem
= listItems
[i
];
520 // Don't do anything for messages for the item if it is not the intended
521 // recipient, or if the response is stale (i.e. the input value has
522 // changed since we sent the request to analyze it).
523 if (pattern
== listItem
.input
.value
)
524 listItem
.setPatternValid(valid
);
529 * Returns whether the rows are editable in this list.
531 isEditable: function() {
532 // Exceptions of the following lists are not editable for now.
533 return IsEditableType(this.contentType
);
537 * Removes all exceptions from the js model.
540 if (this.isEditable()) {
541 // The null creates the Add New Exception row.
542 this.dataModel
= new ArrayDataModel([null]);
544 this.dataModel
= new ArrayDataModel([]);
549 deleteItemAtIndex: function(index
) {
550 var listItem
= this.getListItemByIndex(index
);
551 if (!listItem
.deletable
)
554 var dataItem
= listItem
.dataItem
;
555 chrome
.send('removeException', [listItem
.contentType
,
558 dataItem
.embeddingOrigin
]);
562 var Page
= cr
.ui
.pageManager
.Page
;
563 var PageManager
= cr
.ui
.pageManager
.PageManager
;
566 * Encapsulated handling of content settings list subpage.
569 * @extends {cr.ui.pageManager.Page}
571 function ContentSettingsExceptionsArea() {
572 Page
.call(this, 'contentExceptions',
573 loadTimeData
.getString('contentSettingsPageTabTitle'),
574 'content-settings-exceptions-area');
577 cr
.addSingletonGetter(ContentSettingsExceptionsArea
);
579 ContentSettingsExceptionsArea
.prototype = {
580 __proto__
: Page
.prototype,
583 initializePage: function() {
584 Page
.prototype.initializePage
.call(this);
586 var exceptionsLists
= this.pageDiv
.querySelectorAll('list');
587 for (var i
= 0; i
< exceptionsLists
.length
; i
++) {
588 options
.contentSettings
.ExceptionsList
.decorate(exceptionsLists
[i
]);
591 ContentSettingsExceptionsArea
.hideOTRLists(false);
593 // If the user types in the URL without a hash, show just cookies.
594 this.showList('cookies');
596 $('content-settings-exceptions-overlay-confirm').onclick
=
597 PageManager
.closeOverlay
.bind(PageManager
);
601 * Shows one list and hides all others.
603 * @param {string} type The content type.
605 showList: function(type
) {
606 // Update the title for the type that was shown.
607 this.title
= loadTimeData
.getString(type
+ 'TabTitle');
609 var header
= this.pageDiv
.querySelector('h1');
610 var camelCasedType
= type
.replace(/-([a-z])/g, function(g
) {
611 return g
[1].toUpperCase();
613 header
.textContent
= loadTimeData
.getString(camelCasedType
+ 'Header');
615 var divs
= this.pageDiv
.querySelectorAll('div[contentType]');
616 for (var i
= 0; i
< divs
.length
; i
++) {
617 if (divs
[i
].getAttribute('contentType') == type
)
618 divs
[i
].hidden
= false;
620 divs
[i
].hidden
= true;
623 $('exception-behavior-column').hidden
= type
== 'zoomlevels';
624 $('exception-zoom-column').hidden
= type
!= 'zoomlevels';
628 * Called after the page has been shown. Show the content type for the
631 didShowPage: function() {
633 this.showList(this.hash
.slice(1));
638 * Called when the last incognito window is closed.
640 ContentSettingsExceptionsArea
.OTRProfileDestroyed = function() {
641 this.hideOTRLists(true);
645 * Hides the incognito exceptions lists and optionally clears them as well.
646 * @param {boolean} clear Whether to clear the lists.
648 ContentSettingsExceptionsArea
.hideOTRLists = function(clear
) {
649 var otrLists
= document
.querySelectorAll('list[mode=otr]');
651 for (var i
= 0; i
< otrLists
.length
; i
++) {
652 otrLists
[i
].parentNode
.hidden
= true;
659 ExceptionsListItem
: ExceptionsListItem
,
660 ExceptionsAddRowListItem
: ExceptionsAddRowListItem
,
661 ExceptionsList
: ExceptionsList
,
662 ContentSettingsExceptionsArea
: ContentSettingsExceptionsArea
,