OLD | NEW |
| (Empty) |
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. | |
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 | |
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 this.contentType == 'fullscreen'); | |
434 }, | |
435 | |
436 /** | |
437 * Removes all exceptions from the js model. | |
438 */ | |
439 reset: function() { | |
440 if (this.isEditable()) { | |
441 // The null creates the Add New Exception row. | |
442 this.dataModel = new ArrayDataModel([null]); | |
443 } else { | |
444 this.dataModel = new ArrayDataModel([]); | |
445 } | |
446 }, | |
447 | |
448 /** @inheritDoc */ | |
449 deleteItemAtIndex: function(index) { | |
450 var listItem = this.getListItemByIndex(index); | |
451 if (listItem.undeletable) | |
452 return; | |
453 | |
454 var dataItem = listItem.dataItem; | |
455 var args = [listItem.contentType]; | |
456 if (listItem.contentType == 'location') | |
457 args.push(dataItem['origin'], dataItem['embeddingOrigin']); | |
458 else if (listItem.contentType == 'notifications') | |
459 args.push(dataItem['origin'], dataItem['setting']); | |
460 else | |
461 args.push(listItem.mode, listItem.pattern); | |
462 | |
463 chrome.send('removeException', args); | |
464 }, | |
465 }; | |
466 | |
467 var OptionsPage = options.OptionsPage; | |
468 | |
469 /** | |
470 * Encapsulated handling of content settings list subpage. | |
471 * @constructor | |
472 */ | |
473 function ContentSettingsExceptionsArea() { | |
474 OptionsPage.call(this, 'contentExceptions', | |
475 templateData.contentSettingsPageTabTitle, | |
476 'content-settings-exceptions-area'); | |
477 } | |
478 | |
479 cr.addSingletonGetter(ContentSettingsExceptionsArea); | |
480 | |
481 ContentSettingsExceptionsArea.prototype = { | |
482 __proto__: OptionsPage.prototype, | |
483 | |
484 initializePage: function() { | |
485 OptionsPage.prototype.initializePage.call(this); | |
486 | |
487 var exceptionsLists = this.pageDiv.querySelectorAll('list'); | |
488 for (var i = 0; i < exceptionsLists.length; i++) { | |
489 options.contentSettings.ExceptionsList.decorate(exceptionsLists[i]); | |
490 } | |
491 | |
492 ContentSettingsExceptionsArea.hideOTRLists(); | |
493 | |
494 // If the user types in the URL without a hash, show just cookies. | |
495 this.showList('cookies'); | |
496 }, | |
497 | |
498 /** | |
499 * Shows one list and hides all others. | |
500 * @param {string} type The content type. | |
501 */ | |
502 showList: function(type) { | |
503 var header = this.pageDiv.querySelector('h1'); | |
504 header.textContent = templateData[type + '_header']; | |
505 | |
506 var divs = this.pageDiv.querySelectorAll('div[contentType]'); | |
507 for (var i = 0; i < divs.length; i++) { | |
508 if (divs[i].getAttribute('contentType') == type) | |
509 divs[i].hidden = false; | |
510 else | |
511 divs[i].hidden = true; | |
512 } | |
513 }, | |
514 | |
515 /** | |
516 * Called after the page has been shown. Show the content type for the | |
517 * location's hash. | |
518 */ | |
519 didShowPage: function() { | |
520 var hash = location.hash; | |
521 if (hash) | |
522 this.showList(hash.slice(1)); | |
523 }, | |
524 }; | |
525 | |
526 /** | |
527 * Called when the last incognito window is closed. | |
528 */ | |
529 ContentSettingsExceptionsArea.OTRProfileDestroyed = function() { | |
530 this.hideOTRLists(); | |
531 }; | |
532 | |
533 /** | |
534 * Clears and hides the incognito exceptions lists. | |
535 */ | |
536 ContentSettingsExceptionsArea.hideOTRLists = function() { | |
537 var otrLists = document.querySelectorAll('list[mode=otr]'); | |
538 | |
539 for (var i = 0; i < otrLists.length; i++) { | |
540 otrLists[i].reset(); | |
541 otrLists[i].parentNode.hidden = true; | |
542 } | |
543 }; | |
544 | |
545 return { | |
546 ExceptionsListItem: ExceptionsListItem, | |
547 ExceptionsAddRowListItem: ExceptionsAddRowListItem, | |
548 ExceptionsList: ExceptionsList, | |
549 ContentSettingsExceptionsArea: ContentSettingsExceptionsArea, | |
550 }; | |
551 }); | |
OLD | NEW |