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