| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 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. | |
| 4 | |
| 5 cr.define('options.contentSettings', function() { | |
| 6 const InlineEditableItemList = options.InlineEditableItemList; | |
| 7 const InlineEditableItem = options.InlineEditableItem; | |
| 8 const ArrayDataModel = cr.ui.ArrayDataModel; | |
| 9 | |
| 10 /** | |
| 11 * Creates a new exceptions list item. | |
| 12 * @param {string} contentType The type of the list. | |
| 13 * @param {string} mode The browser mode, 'otr' or 'normal'. | |
| 14 * @param {boolean} enableAskOption Whether to show an 'ask every time' | |
| 15 * option in the select. | |
| 16 * @param {Object} exception A dictionary that contains the data of the | |
| 17 * exception. | |
| 18 * @constructor | |
| 19 * @extends {options.InlineEditableItem} | |
| 20 */ | |
| 21 function ExceptionsListItem(contentType, mode, enableAskOption, exception) { | |
| 22 var el = cr.doc.createElement('div'); | |
| 23 el.mode = mode; | |
| 24 el.contentType = contentType; | |
| 25 el.enableAskOption = enableAskOption; | |
| 26 el.dataItem = exception; | |
| 27 el.__proto__ = ExceptionsListItem.prototype; | |
| 28 el.decorate(); | |
| 29 | |
| 30 return el; | |
| 31 } | |
| 32 | |
| 33 ExceptionsListItem.prototype = { | |
| 34 __proto__: InlineEditableItem.prototype, | |
| 35 | |
| 36 /** | |
| 37 * Called when an element is decorated as a list item. | |
| 38 */ | |
| 39 decorate: function() { | |
| 40 InlineEditableItem.prototype.decorate.call(this); | |
| 41 | |
| 42 this.isPlaceholder = !this.pattern; | |
| 43 var patternCell = this.createEditableTextCell(this.pattern); | |
| 44 patternCell.className = 'exception-pattern'; | |
| 45 this.contentElement.appendChild(patternCell); | |
| 46 if (this.pattern) | |
| 47 this.patternLabel = patternCell.querySelector('.static-text'); | |
| 48 var input = patternCell.querySelector('input'); | |
| 49 | |
| 50 // TODO(stuartmorgan): Create an createEditableSelectCell abstracting | |
| 51 // this code. | |
| 52 // Setting label for display mode. |pattern| will be null for the 'add new | |
| 53 // exception' row. | |
| 54 if (this.pattern) { | |
| 55 var settingLabel = cr.doc.createElement('span'); | |
| 56 settingLabel.textContent = this.settingForDisplay(); | |
| 57 settingLabel.className = 'exception-setting'; | |
| 58 settingLabel.setAttribute('displaymode', 'static'); | |
| 59 this.contentElement.appendChild(settingLabel); | |
| 60 this.settingLabel = settingLabel; | |
| 61 } | |
| 62 | |
| 63 // Setting select element for edit mode. | |
| 64 var select = cr.doc.createElement('select'); | |
| 65 var optionAllow = cr.doc.createElement('option'); | |
| 66 optionAllow.textContent = templateData.allowException; | |
| 67 optionAllow.value = 'allow'; | |
| 68 select.appendChild(optionAllow); | |
| 69 | |
| 70 if (this.enableAskOption) { | |
| 71 var optionAsk = cr.doc.createElement('option'); | |
| 72 optionAsk.textContent = templateData.askException; | |
| 73 optionAsk.value = 'ask'; | |
| 74 select.appendChild(optionAsk); | |
| 75 } | |
| 76 | |
| 77 if (this.contentType == 'cookies') { | |
| 78 var optionSession = cr.doc.createElement('option'); | |
| 79 optionSession.textContent = templateData.sessionException; | |
| 80 optionSession.value = 'session'; | |
| 81 select.appendChild(optionSession); | |
| 82 } | |
| 83 | |
| 84 var optionBlock = cr.doc.createElement('option'); | |
| 85 optionBlock.textContent = templateData.blockException; | |
| 86 optionBlock.value = 'block'; | |
| 87 select.appendChild(optionBlock); | |
| 88 | |
| 89 this.contentElement.appendChild(select); | |
| 90 select.className = 'exception-setting'; | |
| 91 if (this.pattern) | |
| 92 select.setAttribute('displaymode', 'edit'); | |
| 93 | |
| 94 // Used to track whether the URL pattern in the input is valid. | |
| 95 // This will be true if the browser process has informed us that the | |
| 96 // current text in the input is valid. Changing the text resets this to | |
| 97 // false, and getting a response from the browser sets it back to true. | |
| 98 // It starts off as false for empty string (new exceptions) or true for | |
| 99 // already-existing exceptions (which we assume are valid). | |
| 100 this.inputValidityKnown = this.pattern; | |
| 101 // This one tracks the actual validity of the pattern in the input. This | |
| 102 // starts off as true so as not to annoy the user when he adds a new and | |
| 103 // empty input. | |
| 104 this.inputIsValid = true; | |
| 105 | |
| 106 this.input = input; | |
| 107 this.select = select; | |
| 108 | |
| 109 this.updateEditables(); | |
| 110 | |
| 111 // Editing notifications and geolocation is disabled for now. | |
| 112 if (this.contentType == 'notifications' || | |
| 113 this.contentType == 'location') { | |
| 114 this.editable = false; | |
| 115 } | |
| 116 | |
| 117 var listItem = this; | |
| 118 // Handle events on the editable nodes. | |
| 119 input.oninput = function(event) { | |
| 120 listItem.inputValidityKnown = false; | |
| 121 chrome.send('checkExceptionPatternValidity', | |
| 122 [listItem.contentType, listItem.mode, input.value]); | |
| 123 }; | |
| 124 | |
| 125 // Listen for edit events. | |
| 126 this.addEventListener('canceledit', this.onEditCancelled_); | |
| 127 this.addEventListener('commitedit', this.onEditCommitted_); | |
| 128 }, | |
| 129 | |
| 130 /** | |
| 131 * The pattern (e.g., a URL) for the exception. | |
| 132 * @type {string} | |
| 133 */ | |
| 134 get pattern() { | |
| 135 return this.dataItem['displayPattern']; | |
| 136 }, | |
| 137 set pattern(pattern) { | |
| 138 this.dataItem['displayPattern'] = pattern; | |
| 139 }, | |
| 140 | |
| 141 /** | |
| 142 * The setting (allow/block) for the exception. | |
| 143 * @type {string} | |
| 144 */ | |
| 145 get setting() { | |
| 146 return this.dataItem['setting']; | |
| 147 }, | |
| 148 set setting(setting) { | |
| 149 this.dataItem['setting'] = setting; | |
| 150 }, | |
| 151 | |
| 152 /** | |
| 153 * Gets a human-readable setting string. | |
| 154 * @type {string} | |
| 155 */ | |
| 156 settingForDisplay: function() { | |
| 157 var setting = this.setting; | |
| 158 if (setting == 'allow') | |
| 159 return templateData.allowException; | |
| 160 else if (setting == 'block') | |
| 161 return templateData.blockException; | |
| 162 else if (setting == 'ask') | |
| 163 return templateData.askException; | |
| 164 else if (setting == 'session') | |
| 165 return templateData.sessionException; | |
| 166 }, | |
| 167 | |
| 168 /** | |
| 169 * Update this list item to reflect whether the input is a valid pattern. | |
| 170 * @param {boolean} valid Whether said pattern is valid in the context of | |
| 171 * a content exception setting. | |
| 172 */ | |
| 173 setPatternValid: function(valid) { | |
| 174 if (valid || !this.input.value) | |
| 175 this.input.setCustomValidity(''); | |
| 176 else | |
| 177 this.input.setCustomValidity(' '); | |
| 178 this.inputIsValid = valid; | |
| 179 this.inputValidityKnown = true; | |
| 180 }, | |
| 181 | |
| 182 /** | |
| 183 * Set the <input> to its original contents. Used when the user quits | |
| 184 * editing. | |
| 185 */ | |
| 186 resetInput: function() { | |
| 187 this.input.value = this.pattern; | |
| 188 }, | |
| 189 | |
| 190 /** | |
| 191 * Copy the data model values to the editable nodes. | |
| 192 */ | |
| 193 updateEditables: function() { | |
| 194 this.resetInput(); | |
| 195 | |
| 196 var settingOption = | |
| 197 this.select.querySelector('[value=\'' + this.setting + '\']'); | |
| 198 if (settingOption) | |
| 199 settingOption.selected = true; | |
| 200 }, | |
| 201 | |
| 202 /** @inheritDoc */ | |
| 203 get currentInputIsValid() { | |
| 204 return this.inputValidityKnown && this.inputIsValid; | |
| 205 }, | |
| 206 | |
| 207 /** @inheritDoc */ | |
| 208 get hasBeenEdited() { | |
| 209 var livePattern = this.input.value; | |
| 210 var liveSetting = this.select.value; | |
| 211 return livePattern != this.pattern || liveSetting != this.setting; | |
| 212 }, | |
| 213 | |
| 214 /** | |
| 215 * Called when committing an edit. | |
| 216 * @param {Event} e The end event. | |
| 217 * @private | |
| 218 */ | |
| 219 onEditCommitted_: function(e) { | |
| 220 var newPattern = this.input.value; | |
| 221 var newSetting = this.select.value; | |
| 222 | |
| 223 this.finishEdit(newPattern, newSetting); | |
| 224 }, | |
| 225 | |
| 226 /** | |
| 227 * Called when cancelling an edit; resets the control states. | |
| 228 * @param {Event} e The cancel event. | |
| 229 * @private | |
| 230 */ | |
| 231 onEditCancelled_: function() { | |
| 232 this.updateEditables(); | |
| 233 this.setPatternValid(true); | |
| 234 }, | |
| 235 | |
| 236 /** | |
| 237 * Editing is complete; update the model. | |
| 238 * @param {string} newPattern The pattern that the user entered. | |
| 239 * @param {string} newSetting The setting the user chose. | |
| 240 */ | |
| 241 finishEdit: function(newPattern, newSetting) { | |
| 242 this.patternLabel.textContent = newPattern; | |
| 243 this.settingLabel.textContent = this.settingForDisplay(); | |
| 244 var oldPattern = this.pattern; | |
| 245 this.pattern = newPattern; | |
| 246 this.setting = newSetting; | |
| 247 | |
| 248 // TODO(estade): this will need to be updated if geolocation/notifications | |
| 249 // become editable. | |
| 250 if (oldPattern != newPattern) { | |
| 251 chrome.send('removeException', | |
| 252 [this.contentType, this.mode, oldPattern]); | |
| 253 } | |
| 254 | |
| 255 chrome.send('setException', | |
| 256 [this.contentType, this.mode, newPattern, newSetting]); | |
| 257 } | |
| 258 }; | |
| 259 | |
| 260 /** | |
| 261 * Creates a new list item for the Add New Item row, which doesn't represent | |
| 262 * an actual entry in the exceptions list but allows the user to add new | |
| 263 * exceptions. | |
| 264 * @param {string} contentType The type of the list. | |
| 265 * @param {string} mode The browser mode, 'otr' or 'normal'. | |
| 266 * @param {boolean} enableAskOption Whether to show an 'ask every time' | |
| 267 * option in the select. | |
| 268 * @constructor | |
| 269 * @extends {cr.ui.ExceptionsListItem} | |
| 270 */ | |
| 271 function ExceptionsAddRowListItem(contentType, mode, enableAskOption) { | |
| 272 var el = cr.doc.createElement('div'); | |
| 273 el.mode = mode; | |
| 274 el.contentType = contentType; | |
| 275 el.enableAskOption = enableAskOption; | |
| 276 el.dataItem = []; | |
| 277 el.__proto__ = ExceptionsAddRowListItem.prototype; | |
| 278 el.decorate(); | |
| 279 | |
| 280 return el; | |
| 281 } | |
| 282 | |
| 283 ExceptionsAddRowListItem.prototype = { | |
| 284 __proto__: ExceptionsListItem.prototype, | |
| 285 | |
| 286 decorate: function() { | |
| 287 ExceptionsListItem.prototype.decorate.call(this); | |
| 288 | |
| 289 this.input.placeholder = templateData.addNewExceptionInstructions; | |
| 290 | |
| 291 // Do we always want a default of allow? | |
| 292 this.setting = 'allow'; | |
| 293 }, | |
| 294 | |
| 295 /** | |
| 296 * Clear the <input> and let the placeholder text show again. | |
| 297 */ | |
| 298 resetInput: function() { | |
| 299 this.input.value = ''; | |
| 300 }, | |
| 301 | |
| 302 /** @inheritDoc */ | |
| 303 get hasBeenEdited() { | |
| 304 return this.input.value != ''; | |
| 305 }, | |
| 306 | |
| 307 /** | |
| 308 * Editing is complete; update the model. As long as the pattern isn't | |
| 309 * empty, we'll just add it. | |
| 310 * @param {string} newPattern The pattern that the user entered. | |
| 311 * @param {string} newSetting The setting the user chose. | |
| 312 */ | |
| 313 finishEdit: function(newPattern, newSetting) { | |
| 314 chrome.send('setException', | |
| 315 [this.contentType, this.mode, newPattern, newSetting]); | |
| 316 }, | |
| 317 }; | |
| 318 | |
| 319 /** | |
| 320 * Creates a new exceptions list. | |
| 321 * @constructor | |
| 322 * @extends {cr.ui.List} | |
| 323 */ | |
| 324 var ExceptionsList = cr.ui.define('list'); | |
| 325 | |
| 326 ExceptionsList.prototype = { | |
| 327 __proto__: InlineEditableItemList.prototype, | |
| 328 | |
| 329 /** | |
| 330 * Called when an element is decorated as a list. | |
| 331 */ | |
| 332 decorate: function() { | |
| 333 InlineEditableItemList.prototype.decorate.call(this); | |
| 334 | |
| 335 this.classList.add('settings-list'); | |
| 336 | |
| 337 for (var parentNode = this.parentNode; parentNode; | |
| 338 parentNode = parentNode.parentNode) { | |
| 339 if (parentNode.hasAttribute('contentType')) { | |
| 340 this.contentType = parentNode.getAttribute('contentType'); | |
| 341 break; | |
| 342 } | |
| 343 } | |
| 344 | |
| 345 this.mode = this.getAttribute('mode'); | |
| 346 | |
| 347 var exceptionList = this; | |
| 348 function handleBlur(e) { | |
| 349 // When the blur event happens we do not know who is getting focus so we | |
| 350 // delay this a bit until we know if the new focus node is outside the | |
| 351 // list. | |
| 352 var doc = e.target.ownerDocument; | |
| 353 window.setTimeout(function() { | |
| 354 var activeElement = doc.activeElement; | |
| 355 if (!exceptionList.contains(activeElement)) | |
| 356 exceptionList.selectionModel.unselectAll(); | |
| 357 }, 50); | |
| 358 } | |
| 359 | |
| 360 this.addEventListener('blur', handleBlur, true); | |
| 361 | |
| 362 // Whether the exceptions in this list allow an 'Ask every time' option. | |
| 363 this.enableAskOption = (this.contentType == 'plugins' && | |
| 364 templateData.enable_click_to_play); | |
| 365 | |
| 366 this.autoExpands = true; | |
| 367 this.reset(); | |
| 368 }, | |
| 369 | |
| 370 /** | |
| 371 * Creates an item to go in the list. | |
| 372 * @param {Object} entry The element from the data model for this row. | |
| 373 */ | |
| 374 createItem: function(entry) { | |
| 375 if (entry) { | |
| 376 return new ExceptionsListItem(this.contentType, | |
| 377 this.mode, | |
| 378 this.enableAskOption, | |
| 379 entry); | |
| 380 } else { | |
| 381 var addRowItem = new ExceptionsAddRowListItem(this.contentType, | |
| 382 this.mode, | |
| 383 this.enableAskOption); | |
| 384 addRowItem.deletable = false; | |
| 385 return addRowItem; | |
| 386 } | |
| 387 }, | |
| 388 | |
| 389 /** | |
| 390 * Sets the exceptions in the js model. | |
| 391 * @param {Object} entries A list of dictionaries of values, each dictionary | |
| 392 * represents an exception. | |
| 393 */ | |
| 394 setExceptions: function(entries) { | |
| 395 var deleteCount = this.dataModel.length; | |
| 396 | |
| 397 if (this.isEditable()) { | |
| 398 // We don't want to remove the Add New Exception row. | |
| 399 deleteCount = deleteCount - 1; | |
| 400 } | |
| 401 | |
| 402 var args = [0, deleteCount]; | |
| 403 args.push.apply(args, entries); | |
| 404 this.dataModel.splice.apply(this.dataModel, args); | |
| 405 }, | |
| 406 | |
| 407 /** | |
| 408 * The browser has finished checking a pattern for validity. Update the | |
| 409 * list item to reflect this. | |
| 410 * @param {string} pattern The pattern. | |
| 411 * @param {bool} valid Whether said pattern is valid in the context of | |
| 412 * a content exception setting. | |
| 413 */ | |
| 414 patternValidityCheckComplete: function(pattern, valid) { | |
| 415 var listItems = this.items; | |
| 416 for (var i = 0; i < listItems.length; i++) { | |
| 417 var listItem = listItems[i]; | |
| 418 // Don't do anything for messages for the item if it is not the intended | |
| 419 // recipient, or if the response is stale (i.e. the input value has | |
| 420 // changed since we sent the request to analyze it). | |
| 421 if (pattern == listItem.input.value) | |
| 422 listItem.setPatternValid(valid); | |
| 423 } | |
| 424 }, | |
| 425 | |
| 426 /** | |
| 427 * Returns whether the rows are editable in this list. | |
| 428 */ | |
| 429 isEditable: function() { | |
| 430 // Editing notifications and geolocation is disabled for now. | |
| 431 return !(this.contentType == 'notifications' || | |
| 432 this.contentType == 'location'); | |
| 433 }, | |
| 434 | |
| 435 /** | |
| 436 * Removes all exceptions from the js model. | |
| 437 */ | |
| 438 reset: function() { | |
| 439 if (this.isEditable()) { | |
| 440 // The null creates the Add New Exception row. | |
| 441 this.dataModel = new ArrayDataModel([null]); | |
| 442 } else { | |
| 443 this.dataModel = new ArrayDataModel([]); | |
| 444 } | |
| 445 }, | |
| 446 | |
| 447 /** @inheritDoc */ | |
| 448 deleteItemAtIndex: function(index) { | |
| 449 var listItem = this.getListItemByIndex(index); | |
| 450 if (listItem.undeletable) | |
| 451 return; | |
| 452 | |
| 453 var dataItem = listItem.dataItem; | |
| 454 var args = [listItem.contentType]; | |
| 455 if (listItem.contentType == 'location') | |
| 456 args.push(dataItem['origin'], dataItem['embeddingOrigin']); | |
| 457 else if (listItem.contentType == 'notifications') | |
| 458 args.push(dataItem['origin'], dataItem['setting']); | |
| 459 else | |
| 460 args.push(listItem.mode, listItem.pattern); | |
| 461 | |
| 462 chrome.send('removeException', args); | |
| 463 }, | |
| 464 }; | |
| 465 | |
| 466 var OptionsPage = options.OptionsPage; | |
| 467 | |
| 468 /** | |
| 469 * Encapsulated handling of content settings list subpage. | |
| 470 * @constructor | |
| 471 */ | |
| 472 function ContentSettingsExceptionsArea() { | |
| 473 OptionsPage.call(this, 'contentExceptions', | |
| 474 templateData.contentSettingsPageTabTitle, | |
| 475 'content-settings-exceptions-area'); | |
| 476 } | |
| 477 | |
| 478 cr.addSingletonGetter(ContentSettingsExceptionsArea); | |
| 479 | |
| 480 ContentSettingsExceptionsArea.prototype = { | |
| 481 __proto__: OptionsPage.prototype, | |
| 482 | |
| 483 initializePage: function() { | |
| 484 OptionsPage.prototype.initializePage.call(this); | |
| 485 | |
| 486 var exceptionsLists = this.pageDiv.querySelectorAll('list'); | |
| 487 for (var i = 0; i < exceptionsLists.length; i++) { | |
| 488 options.contentSettings.ExceptionsList.decorate(exceptionsLists[i]); | |
| 489 } | |
| 490 | |
| 491 ContentSettingsExceptionsArea.hideOTRLists(); | |
| 492 | |
| 493 // If the user types in the URL without a hash, show just cookies. | |
| 494 this.showList('cookies'); | |
| 495 }, | |
| 496 | |
| 497 /** | |
| 498 * Shows one list and hides all others. | |
| 499 * @param {string} type The content type. | |
| 500 */ | |
| 501 showList: function(type) { | |
| 502 var header = this.pageDiv.querySelector('h1'); | |
| 503 header.textContent = templateData[type + '_header']; | |
| 504 | |
| 505 var divs = this.pageDiv.querySelectorAll('div[contentType]'); | |
| 506 for (var i = 0; i < divs.length; i++) { | |
| 507 if (divs[i].getAttribute('contentType') == type) | |
| 508 divs[i].classList.remove('hidden'); | |
| 509 else | |
| 510 divs[i].classList.add('hidden'); | |
| 511 } | |
| 512 }, | |
| 513 | |
| 514 /** | |
| 515 * Called after the page has been shown. Show the content type for the | |
| 516 * location's hash. | |
| 517 */ | |
| 518 didShowPage: function() { | |
| 519 var hash = location.hash; | |
| 520 if (hash) | |
| 521 this.showList(hash.slice(1)); | |
| 522 }, | |
| 523 }; | |
| 524 | |
| 525 /** | |
| 526 * Called when the last incognito window is closed. | |
| 527 */ | |
| 528 ContentSettingsExceptionsArea.OTRProfileDestroyed = function() { | |
| 529 this.hideOTRLists(); | |
| 530 }; | |
| 531 | |
| 532 /** | |
| 533 * Clears and hides the incognito exceptions lists. | |
| 534 */ | |
| 535 ContentSettingsExceptionsArea.hideOTRLists = function() { | |
| 536 var otrLists = document.querySelectorAll('list[mode=otr]'); | |
| 537 | |
| 538 for (var i = 0; i < otrLists.length; i++) { | |
| 539 otrLists[i].reset(); | |
| 540 otrLists[i].parentNode.classList.add('hidden'); | |
| 541 } | |
| 542 }; | |
| 543 | |
| 544 return { | |
| 545 ExceptionsListItem: ExceptionsListItem, | |
| 546 ExceptionsAddRowListItem: ExceptionsAddRowListItem, | |
| 547 ExceptionsList: ExceptionsList, | |
| 548 ContentSettingsExceptionsArea: ContentSettingsExceptionsArea, | |
| 549 }; | |
| 550 }); | |
| OLD | NEW |