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