Index: chrome/browser/resources/options2/content_settings_exceptions_area.js |
diff --git a/chrome/browser/resources/options2/content_settings_exceptions_area.js b/chrome/browser/resources/options2/content_settings_exceptions_area.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..7adde6862095c8c49f505dc3c223b8d19a0ea14e |
--- /dev/null |
+++ b/chrome/browser/resources/options2/content_settings_exceptions_area.js |
@@ -0,0 +1,552 @@ |
+// Copyright (c) 2011 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+cr.define('options.contentSettings', function() { |
+ const InlineEditableItemList = options.InlineEditableItemList; |
+ const InlineEditableItem = options.InlineEditableItem; |
+ const ArrayDataModel = cr.ui.ArrayDataModel; |
+ |
+ /** |
+ * Creates a new exceptions list item. |
+ * @param {string} contentType The type of the list. |
+ * @param {string} mode The browser mode, 'otr' or 'normal'. |
+ * @param {boolean} enableAskOption Whether to show an 'ask every time' |
+ * option in the select. |
+ * @param {Object} exception A dictionary that contains the data of the |
+ * exception. |
+ * @constructor |
+ * @extends {options.InlineEditableItem} |
+ */ |
+ function ExceptionsListItem(contentType, mode, enableAskOption, exception) { |
+ var el = cr.doc.createElement('div'); |
+ el.mode = mode; |
+ el.contentType = contentType; |
+ el.enableAskOption = enableAskOption; |
+ el.dataItem = exception; |
+ el.__proto__ = ExceptionsListItem.prototype; |
+ el.decorate(); |
+ |
+ return el; |
+ } |
+ |
+ ExceptionsListItem.prototype = { |
+ __proto__: InlineEditableItem.prototype, |
+ |
+ /** |
+ * Called when an element is decorated as a list item. |
+ */ |
+ decorate: function() { |
+ InlineEditableItem.prototype.decorate.call(this); |
+ |
+ this.isPlaceholder = !this.pattern; |
+ var patternCell = this.createEditableTextCell(this.pattern); |
+ patternCell.className = 'exception-pattern'; |
+ patternCell.classList.add('weakrtl'); |
+ this.contentElement.appendChild(patternCell); |
+ if (this.pattern) |
+ this.patternLabel = patternCell.querySelector('.static-text'); |
+ var input = patternCell.querySelector('input'); |
+ |
+ // TODO(stuartmorgan): Create an createEditableSelectCell abstracting |
+ // this code. |
+ // Setting label for display mode. |pattern| will be null for the 'add new |
+ // exception' row. |
+ if (this.pattern) { |
+ var settingLabel = cr.doc.createElement('span'); |
+ settingLabel.textContent = this.settingForDisplay(); |
+ settingLabel.className = 'exception-setting'; |
+ settingLabel.setAttribute('displaymode', 'static'); |
+ this.contentElement.appendChild(settingLabel); |
+ this.settingLabel = settingLabel; |
+ } |
+ |
+ // Setting select element for edit mode. |
+ var select = cr.doc.createElement('select'); |
+ var optionAllow = cr.doc.createElement('option'); |
+ optionAllow.textContent = templateData.allowException; |
+ optionAllow.value = 'allow'; |
+ select.appendChild(optionAllow); |
+ |
+ if (this.enableAskOption) { |
+ var optionAsk = cr.doc.createElement('option'); |
+ optionAsk.textContent = templateData.askException; |
+ optionAsk.value = 'ask'; |
+ select.appendChild(optionAsk); |
+ } |
+ |
+ if (this.contentType == 'cookies') { |
+ var optionSession = cr.doc.createElement('option'); |
+ optionSession.textContent = templateData.sessionException; |
+ optionSession.value = 'session'; |
+ select.appendChild(optionSession); |
+ } |
+ |
+ if (this.contentType != 'fullscreen') { |
+ var optionBlock = cr.doc.createElement('option'); |
+ optionBlock.textContent = templateData.blockException; |
+ optionBlock.value = 'block'; |
+ select.appendChild(optionBlock); |
+ } |
+ |
+ this.contentElement.appendChild(select); |
+ select.className = 'exception-setting'; |
+ if (this.pattern) |
+ select.setAttribute('displaymode', 'edit'); |
+ |
+ // Used to track whether the URL pattern in the input is valid. |
+ // This will be true if the browser process has informed us that the |
+ // current text in the input is valid. Changing the text resets this to |
+ // false, and getting a response from the browser sets it back to true. |
+ // It starts off as false for empty string (new exceptions) or true for |
+ // already-existing exceptions (which we assume are valid). |
+ this.inputValidityKnown = this.pattern; |
+ // This one tracks the actual validity of the pattern in the input. This |
+ // starts off as true so as not to annoy the user when he adds a new and |
+ // empty input. |
+ this.inputIsValid = true; |
+ |
+ this.input = input; |
+ this.select = select; |
+ |
+ this.updateEditables(); |
+ |
+ // Editing notifications and geolocation is disabled for now. |
+ if (this.contentType == 'notifications' || |
+ this.contentType == 'location') { |
+ this.editable = false; |
+ } |
+ |
+ // If the source of the content setting exception is not the user |
+ // preference, then the content settings exception is managed and the user |
+ // can't edit it. |
+ if (this.dataItem.source && |
+ this.dataItem.source != 'preference') { |
+ this.setAttribute('managedby', this.dataItem.source); |
+ this.deletable = false; |
+ this.editable = false; |
+ } |
+ |
+ var listItem = this; |
+ // Handle events on the editable nodes. |
+ input.oninput = function(event) { |
+ listItem.inputValidityKnown = false; |
+ chrome.send('checkExceptionPatternValidity', |
+ [listItem.contentType, listItem.mode, input.value]); |
+ }; |
+ |
+ // Listen for edit events. |
+ this.addEventListener('canceledit', this.onEditCancelled_); |
+ this.addEventListener('commitedit', this.onEditCommitted_); |
+ }, |
+ |
+ /** |
+ * The pattern (e.g., a URL) for the exception. |
+ * @type {string} |
+ */ |
+ get pattern() { |
+ return this.dataItem['displayPattern']; |
+ }, |
+ set pattern(pattern) { |
+ this.dataItem['displayPattern'] = pattern; |
+ }, |
+ |
+ /** |
+ * The setting (allow/block) for the exception. |
+ * @type {string} |
+ */ |
+ get setting() { |
+ return this.dataItem['setting']; |
+ }, |
+ set setting(setting) { |
+ this.dataItem['setting'] = setting; |
+ }, |
+ |
+ /** |
+ * Gets a human-readable setting string. |
+ * @type {string} |
+ */ |
+ settingForDisplay: function() { |
+ var setting = this.setting; |
+ if (setting == 'allow') |
+ return templateData.allowException; |
+ else if (setting == 'block') |
+ return templateData.blockException; |
+ else if (setting == 'ask') |
+ return templateData.askException; |
+ else if (setting == 'session') |
+ return templateData.sessionException; |
+ }, |
+ |
+ /** |
+ * Update this list item to reflect whether the input is a valid pattern. |
+ * @param {boolean} valid Whether said pattern is valid in the context of |
+ * a content exception setting. |
+ */ |
+ setPatternValid: function(valid) { |
+ if (valid || !this.input.value) |
+ this.input.setCustomValidity(''); |
+ else |
+ this.input.setCustomValidity(' '); |
+ this.inputIsValid = valid; |
+ this.inputValidityKnown = true; |
+ }, |
+ |
+ /** |
+ * Set the <input> to its original contents. Used when the user quits |
+ * editing. |
+ */ |
+ resetInput: function() { |
+ this.input.value = this.pattern; |
+ }, |
+ |
+ /** |
+ * Copy the data model values to the editable nodes. |
+ */ |
+ updateEditables: function() { |
+ this.resetInput(); |
+ |
+ var settingOption = |
+ this.select.querySelector('[value=\'' + this.setting + '\']'); |
+ if (settingOption) |
+ settingOption.selected = true; |
+ }, |
+ |
+ /** @inheritDoc */ |
+ get currentInputIsValid() { |
+ return this.inputValidityKnown && this.inputIsValid; |
+ }, |
+ |
+ /** @inheritDoc */ |
+ get hasBeenEdited() { |
+ var livePattern = this.input.value; |
+ var liveSetting = this.select.value; |
+ return livePattern != this.pattern || liveSetting != this.setting; |
+ }, |
+ |
+ /** |
+ * Called when committing an edit. |
+ * @param {Event} e The end event. |
+ * @private |
+ */ |
+ onEditCommitted_: function(e) { |
+ var newPattern = this.input.value; |
+ var newSetting = this.select.value; |
+ |
+ this.finishEdit(newPattern, newSetting); |
+ }, |
+ |
+ /** |
+ * Called when cancelling an edit; resets the control states. |
+ * @param {Event} e The cancel event. |
+ * @private |
+ */ |
+ onEditCancelled_: function() { |
+ this.updateEditables(); |
+ this.setPatternValid(true); |
+ }, |
+ |
+ /** |
+ * Editing is complete; update the model. |
+ * @param {string} newPattern The pattern that the user entered. |
+ * @param {string} newSetting The setting the user chose. |
+ */ |
+ finishEdit: function(newPattern, newSetting) { |
+ this.patternLabel.textContent = newPattern; |
+ this.settingLabel.textContent = this.settingForDisplay(); |
+ var oldPattern = this.pattern; |
+ this.pattern = newPattern; |
+ this.setting = newSetting; |
+ |
+ // TODO(estade): this will need to be updated if geolocation/notifications |
+ // become editable. |
+ if (oldPattern != newPattern) { |
+ chrome.send('removeException', |
+ [this.contentType, this.mode, oldPattern]); |
+ } |
+ |
+ chrome.send('setException', |
+ [this.contentType, this.mode, newPattern, newSetting]); |
+ } |
+ }; |
+ |
+ /** |
+ * Creates a new list item for the Add New Item row, which doesn't represent |
+ * an actual entry in the exceptions list but allows the user to add new |
+ * exceptions. |
+ * @param {string} contentType The type of the list. |
+ * @param {string} mode The browser mode, 'otr' or 'normal'. |
+ * @param {boolean} enableAskOption Whether to show an 'ask every time' |
+ * option in the select. |
+ * @constructor |
+ * @extends {cr.ui.ExceptionsListItem} |
+ */ |
+ function ExceptionsAddRowListItem(contentType, mode, enableAskOption) { |
+ var el = cr.doc.createElement('div'); |
+ el.mode = mode; |
+ el.contentType = contentType; |
+ el.enableAskOption = enableAskOption; |
+ el.dataItem = []; |
+ el.__proto__ = ExceptionsAddRowListItem.prototype; |
+ el.decorate(); |
+ |
+ return el; |
+ } |
+ |
+ ExceptionsAddRowListItem.prototype = { |
+ __proto__: ExceptionsListItem.prototype, |
+ |
+ decorate: function() { |
+ ExceptionsListItem.prototype.decorate.call(this); |
+ |
+ this.input.placeholder = templateData.addNewExceptionInstructions; |
+ |
+ // Do we always want a default of allow? |
+ this.setting = 'allow'; |
+ }, |
+ |
+ /** |
+ * Clear the <input> and let the placeholder text show again. |
+ */ |
+ resetInput: function() { |
+ this.input.value = ''; |
+ }, |
+ |
+ /** @inheritDoc */ |
+ get hasBeenEdited() { |
+ return this.input.value != ''; |
+ }, |
+ |
+ /** |
+ * Editing is complete; update the model. As long as the pattern isn't |
+ * empty, we'll just add it. |
+ * @param {string} newPattern The pattern that the user entered. |
+ * @param {string} newSetting The setting the user chose. |
+ */ |
+ finishEdit: function(newPattern, newSetting) { |
+ this.resetInput(); |
+ chrome.send('setException', |
+ [this.contentType, this.mode, newPattern, newSetting]); |
+ }, |
+ }; |
+ |
+ /** |
+ * Creates a new exceptions list. |
+ * @constructor |
+ * @extends {cr.ui.List} |
+ */ |
+ var ExceptionsList = cr.ui.define('list'); |
+ |
+ ExceptionsList.prototype = { |
+ __proto__: InlineEditableItemList.prototype, |
+ |
+ /** |
+ * Called when an element is decorated as a list. |
+ */ |
+ decorate: function() { |
+ InlineEditableItemList.prototype.decorate.call(this); |
+ |
+ this.classList.add('settings-list'); |
+ |
+ for (var parentNode = this.parentNode; parentNode; |
+ parentNode = parentNode.parentNode) { |
+ if (parentNode.hasAttribute('contentType')) { |
+ this.contentType = parentNode.getAttribute('contentType'); |
+ break; |
+ } |
+ } |
+ |
+ this.mode = this.getAttribute('mode'); |
+ |
+ var exceptionList = this; |
+ |
+ // Whether the exceptions in this list allow an 'Ask every time' option. |
+ this.enableAskOption = (this.contentType == 'plugins' && |
+ templateData.enable_click_to_play); |
+ |
+ this.autoExpands = true; |
+ this.reset(); |
+ }, |
+ |
+ /** |
+ * Creates an item to go in the list. |
+ * @param {Object} entry The element from the data model for this row. |
+ */ |
+ createItem: function(entry) { |
+ if (entry) { |
+ return new ExceptionsListItem(this.contentType, |
+ this.mode, |
+ this.enableAskOption, |
+ entry); |
+ } else { |
+ var addRowItem = new ExceptionsAddRowListItem(this.contentType, |
+ this.mode, |
+ this.enableAskOption); |
+ addRowItem.deletable = false; |
+ return addRowItem; |
+ } |
+ }, |
+ |
+ /** |
+ * Sets the exceptions in the js model. |
+ * @param {Object} entries A list of dictionaries of values, each dictionary |
+ * represents an exception. |
+ */ |
+ setExceptions: function(entries) { |
+ var deleteCount = this.dataModel.length; |
+ |
+ if (this.isEditable()) { |
+ // We don't want to remove the Add New Exception row. |
+ deleteCount = deleteCount - 1; |
+ } |
+ |
+ var args = [0, deleteCount]; |
+ args.push.apply(args, entries); |
+ this.dataModel.splice.apply(this.dataModel, args); |
+ }, |
+ |
+ /** |
+ * The browser has finished checking a pattern for validity. Update the |
+ * list item to reflect this. |
+ * @param {string} pattern The pattern. |
+ * @param {bool} valid Whether said pattern is valid in the context of |
+ * a content exception setting. |
+ */ |
+ patternValidityCheckComplete: function(pattern, valid) { |
+ var listItems = this.items; |
+ for (var i = 0; i < listItems.length; i++) { |
+ var listItem = listItems[i]; |
+ // Don't do anything for messages for the item if it is not the intended |
+ // recipient, or if the response is stale (i.e. the input value has |
+ // changed since we sent the request to analyze it). |
+ if (pattern == listItem.input.value) |
+ listItem.setPatternValid(valid); |
+ } |
+ }, |
+ |
+ /** |
+ * Returns whether the rows are editable in this list. |
+ */ |
+ isEditable: function() { |
+ // Editing notifications and geolocation is disabled for now. |
+ return !(this.contentType == 'notifications' || |
+ this.contentType == 'location' || |
+ this.contentType == 'fullscreen'); |
+ }, |
+ |
+ /** |
+ * Removes all exceptions from the js model. |
+ */ |
+ reset: function() { |
+ if (this.isEditable()) { |
+ // The null creates the Add New Exception row. |
+ this.dataModel = new ArrayDataModel([null]); |
+ } else { |
+ this.dataModel = new ArrayDataModel([]); |
+ } |
+ }, |
+ |
+ /** @inheritDoc */ |
+ deleteItemAtIndex: function(index) { |
+ var listItem = this.getListItemByIndex(index); |
+ if (listItem.undeletable) |
+ return; |
+ |
+ var dataItem = listItem.dataItem; |
+ var args = [listItem.contentType]; |
+ if (listItem.contentType == 'location') |
+ args.push(dataItem['origin'], dataItem['embeddingOrigin']); |
+ else if (listItem.contentType == 'notifications') |
+ args.push(dataItem['origin'], dataItem['setting']); |
+ else |
+ args.push(listItem.mode, listItem.pattern); |
+ |
+ chrome.send('removeException', args); |
+ }, |
+ }; |
+ |
+ var OptionsPage = options.OptionsPage; |
+ |
+ /** |
+ * Encapsulated handling of content settings list subpage. |
+ * @constructor |
+ */ |
+ function ContentSettingsExceptionsArea() { |
+ OptionsPage.call(this, 'contentExceptions', |
+ templateData.contentSettingsPageTabTitle, |
+ 'content-settings-exceptions-area'); |
+ } |
+ |
+ cr.addSingletonGetter(ContentSettingsExceptionsArea); |
+ |
+ ContentSettingsExceptionsArea.prototype = { |
+ __proto__: OptionsPage.prototype, |
+ |
+ initializePage: function() { |
+ OptionsPage.prototype.initializePage.call(this); |
+ |
+ var exceptionsLists = this.pageDiv.querySelectorAll('list'); |
+ for (var i = 0; i < exceptionsLists.length; i++) { |
+ options.contentSettings.ExceptionsList.decorate(exceptionsLists[i]); |
+ } |
+ |
+ ContentSettingsExceptionsArea.hideOTRLists(); |
+ |
+ // If the user types in the URL without a hash, show just cookies. |
+ this.showList('cookies'); |
+ }, |
+ |
+ /** |
+ * Shows one list and hides all others. |
+ * @param {string} type The content type. |
+ */ |
+ showList: function(type) { |
+ var header = this.pageDiv.querySelector('h1'); |
+ header.textContent = templateData[type + '_header']; |
+ |
+ var divs = this.pageDiv.querySelectorAll('div[contentType]'); |
+ for (var i = 0; i < divs.length; i++) { |
+ if (divs[i].getAttribute('contentType') == type) |
+ divs[i].hidden = false; |
+ else |
+ divs[i].hidden = true; |
+ } |
+ }, |
+ |
+ /** |
+ * Called after the page has been shown. Show the content type for the |
+ * location's hash. |
+ */ |
+ didShowPage: function() { |
+ var hash = location.hash; |
+ if (hash) |
+ this.showList(hash.slice(1)); |
+ }, |
+ }; |
+ |
+ /** |
+ * Called when the last incognito window is closed. |
+ */ |
+ ContentSettingsExceptionsArea.OTRProfileDestroyed = function() { |
+ this.hideOTRLists(); |
+ }; |
+ |
+ /** |
+ * Clears and hides the incognito exceptions lists. |
+ */ |
+ ContentSettingsExceptionsArea.hideOTRLists = function() { |
+ var otrLists = document.querySelectorAll('list[mode=otr]'); |
+ |
+ for (var i = 0; i < otrLists.length; i++) { |
+ otrLists[i].reset(); |
+ otrLists[i].parentNode.hidden = true; |
+ } |
+ }; |
+ |
+ return { |
+ ExceptionsListItem: ExceptionsListItem, |
+ ExceptionsAddRowListItem: ExceptionsAddRowListItem, |
+ ExceptionsList: ExceptionsList, |
+ ContentSettingsExceptionsArea: ContentSettingsExceptionsArea, |
+ }; |
+}); |