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', function() { | |
6 const DeletableItemList = options.DeletableItemList; | |
7 const DeletableItem = options.DeletableItem; | |
8 const ArrayDataModel = cr.ui.ArrayDataModel; | |
9 const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel; | |
10 | |
11 // This structure maps the various cookie type names from C++ (hence the | |
12 // underscores) to arrays of the different types of data each has, along with | |
13 // the i18n name for the description of that data type. | |
14 const cookieInfo = { | |
15 'cookie': [ ['name', 'label_cookie_name'], | |
16 ['content', 'label_cookie_content'], | |
17 ['domain', 'label_cookie_domain'], | |
18 ['path', 'label_cookie_path'], | |
19 ['sendfor', 'label_cookie_send_for'], | |
20 ['accessibleToScript', 'label_cookie_accessible_to_script'], | |
21 ['created', 'label_cookie_created'], | |
22 ['expires', 'label_cookie_expires'] ], | |
23 'app_cache': [ ['manifest', 'label_app_cache_manifest'], | |
24 ['size', 'label_local_storage_size'], | |
25 ['created', 'label_cookie_created'], | |
26 ['accessed', 'label_cookie_last_accessed'] ], | |
27 'database': [ ['name', 'label_cookie_name'], | |
28 ['desc', 'label_webdb_desc'], | |
29 ['webdbSize', 'label_local_storage_size'], | |
30 ['modified', 'label_local_storage_last_modified'] ], | |
31 'local_storage': [ ['origin', 'label_local_storage_origin'], | |
32 ['size', 'label_local_storage_size'], | |
33 ['modified', 'label_local_storage_last_modified'] ], | |
34 'indexed_db': [ ['origin', 'label_indexed_db_origin'], | |
35 ['size', 'label_indexed_db_size'], | |
36 ['modified', 'label_indexed_db_last_modified'] ], | |
37 }; | |
38 | |
39 const localStrings = new LocalStrings(); | |
40 | |
41 /** | |
42 * Returns the item's height, like offsetHeight but such that it works better | |
43 * when the page is zoomed. See the similar calculation in @{code cr.ui.List}. | |
44 * This version also accounts for the animation done in this file. | |
45 * @param {Element} item The item to get the height of. | |
46 * @return {number} The height of the item, calculated with zooming in mind. | |
47 */ | |
48 function getItemHeight(item) { | |
49 var height = item.style.height; | |
50 // Use the fixed animation target height if set, in case the element is | |
51 // currently being animated and we'd get an intermediate height below. | |
52 if (height && height.substr(-2) == 'px') | |
53 return parseInt(height.substr(0, height.length - 2)); | |
54 return item.getBoundingClientRect().height; | |
55 } | |
56 | |
57 var parentLookup = {}; | |
58 var lookupRequests = {}; | |
59 | |
60 /** | |
61 * Creates a new list item for sites data. Note that these are created and | |
62 * destroyed lazily as they scroll into and out of view, so they must be | |
63 * stateless. We cache the expanded item in @{code CookiesList} though, so it | |
64 * can keep state. (Mostly just which item is selected.) | |
65 * @param {Object} origin Data used to create a cookie list item. | |
66 * @param {CookiesList} list The list that will contain this item. | |
67 * @constructor | |
68 * @extends {DeletableItem} | |
69 */ | |
70 function CookieListItem(origin, list) { | |
71 var listItem = new DeletableItem(null); | |
72 listItem.__proto__ = CookieListItem.prototype; | |
73 | |
74 listItem.origin = origin; | |
75 listItem.list = list; | |
76 listItem.decorate(); | |
77 | |
78 // This hooks up updateOrigin() to the list item, makes the top-level | |
79 // tree nodes (i.e., origins) register their IDs in parentLookup, and | |
80 // causes them to request their children if they have none. Note that we | |
81 // have special logic in the setter for the parent property to make sure | |
82 // that we can still garbage collect list items when they scroll out of | |
83 // view, even though it appears that we keep a direct reference. | |
84 if (origin) { | |
85 origin.parent = listItem; | |
86 origin.updateOrigin(); | |
87 } | |
88 | |
89 return listItem; | |
90 } | |
91 | |
92 CookieListItem.prototype = { | |
93 __proto__: DeletableItem.prototype, | |
94 | |
95 /** @inheritDoc */ | |
96 decorate: function() { | |
97 this.siteChild = this.ownerDocument.createElement('div'); | |
98 this.siteChild.className = 'cookie-site'; | |
99 this.dataChild = this.ownerDocument.createElement('div'); | |
100 this.dataChild.className = 'cookie-data'; | |
101 this.itemsChild = this.ownerDocument.createElement('div'); | |
102 this.itemsChild.className = 'cookie-items'; | |
103 this.infoChild = this.ownerDocument.createElement('div'); | |
104 this.infoChild.className = 'cookie-details hidden'; | |
105 var remove = this.ownerDocument.createElement('button'); | |
106 remove.textContent = localStrings.getString('remove_cookie'); | |
107 remove.onclick = this.removeCookie_.bind(this); | |
108 this.infoChild.appendChild(remove); | |
109 var content = this.contentElement; | |
110 content.appendChild(this.siteChild); | |
111 content.appendChild(this.dataChild); | |
112 content.appendChild(this.itemsChild); | |
113 this.itemsChild.appendChild(this.infoChild); | |
114 if (this.origin && this.origin.data) { | |
115 this.siteChild.textContent = this.origin.data.title; | |
116 this.siteChild.setAttribute('title', this.origin.data.title); | |
117 } | |
118 this.itemList_ = []; | |
119 }, | |
120 | |
121 /** @type {boolean} */ | |
122 get expanded() { | |
123 return this.expanded_; | |
124 }, | |
125 set expanded(expanded) { | |
126 if (this.expanded_ == expanded) | |
127 return; | |
128 this.expanded_ = expanded; | |
129 if (expanded) { | |
130 var oldExpanded = this.list.expandedItem; | |
131 this.list.expandedItem = this; | |
132 this.updateItems_(); | |
133 if (oldExpanded) | |
134 oldExpanded.expanded = false; | |
135 this.classList.add('show-items'); | |
136 } else { | |
137 if (this.list.expandedItem == this) { | |
138 this.list.leadItemHeight = 0; | |
139 this.list.expandedItem = null; | |
140 } | |
141 this.style.height = ''; | |
142 this.itemsChild.style.height = ''; | |
143 this.classList.remove('show-items'); | |
144 } | |
145 }, | |
146 | |
147 /** | |
148 * The callback for the "remove" button shown when an item is selected. | |
149 * Requests that the currently selected cookie be removed. | |
150 * @private | |
151 */ | |
152 removeCookie_: function() { | |
153 if (this.selectedIndex_ >= 0) { | |
154 var item = this.itemList_[this.selectedIndex_]; | |
155 if (item && item.node) | |
156 chrome.send('removeCookie', [item.node.pathId]); | |
157 } | |
158 }, | |
159 | |
160 /** | |
161 * Disable animation within this cookie list item, in preparation for making | |
162 * changes that will need to be animated. Makes it possible to measure the | |
163 * contents without displaying them, to set animation targets. | |
164 * @private | |
165 */ | |
166 disableAnimation_: function() { | |
167 this.itemsHeight_ = getItemHeight(this.itemsChild); | |
168 this.classList.add('measure-items'); | |
169 }, | |
170 | |
171 /** | |
172 * Enable animation after changing the contents of this cookie list item. | |
173 * See @{code disableAnimation_}. | |
174 * @private | |
175 */ | |
176 enableAnimation_: function() { | |
177 if (!this.classList.contains('measure-items')) | |
178 this.disableAnimation_(); | |
179 this.itemsChild.style.height = ''; | |
180 // This will force relayout in order to calculate the new heights. | |
181 var itemsHeight = getItemHeight(this.itemsChild); | |
182 var fixedHeight = getItemHeight(this) + itemsHeight - this.itemsHeight_; | |
183 this.itemsChild.style.height = this.itemsHeight_ + 'px'; | |
184 // Force relayout before enabling animation, so that if we have | |
185 // changed things since the last layout, they will not be animated | |
186 // during subsequent layouts. | |
187 this.itemsChild.offsetHeight; | |
188 this.classList.remove('measure-items'); | |
189 this.itemsChild.style.height = itemsHeight + 'px'; | |
190 this.style.height = fixedHeight + 'px'; | |
191 if (this.expanded) | |
192 this.list.leadItemHeight = fixedHeight; | |
193 }, | |
194 | |
195 /** | |
196 * Updates the origin summary to reflect changes in its items. | |
197 * Both CookieListItem and CookieTreeNode implement this API. | |
198 * This implementation scans the descendants to update the text. | |
199 */ | |
200 updateOrigin: function() { | |
201 var info = { | |
202 cookies: 0, | |
203 database: false, | |
204 localStorage: false, | |
205 appCache: false, | |
206 indexedDb: false | |
207 }; | |
208 if (this.origin) | |
209 this.origin.collectSummaryInfo(info); | |
210 var list = []; | |
211 if (info.cookies > 1) | |
212 list.push(localStrings.getStringF('cookie_plural', info.cookies)); | |
213 else if (info.cookies > 0) | |
214 list.push(localStrings.getString('cookie_singular')); | |
215 if (info.database || info.indexedDb) | |
216 list.push(localStrings.getString('cookie_database_storage')); | |
217 if (info.localStorage) | |
218 list.push(localStrings.getString('cookie_local_storage')); | |
219 if (info.appCache) | |
220 list.push(localStrings.getString('cookie_app_cache')); | |
221 var text = ''; | |
222 for (var i = 0; i < list.length; ++i) | |
223 if (text.length > 0) | |
224 text += ', ' + list[i]; | |
225 else | |
226 text = list[i]; | |
227 this.dataChild.textContent = text; | |
228 if (this.expanded) | |
229 this.updateItems_(); | |
230 }, | |
231 | |
232 /** | |
233 * Updates the items section to reflect changes, animating to the new state. | |
234 * Removes existing contents and calls @{code CookieTreeNode.createItems}. | |
235 * @private | |
236 */ | |
237 updateItems_: function() { | |
238 this.disableAnimation_(); | |
239 this.itemsChild.textContent = ''; | |
240 this.infoChild.classList.add('hidden'); | |
241 this.selectedIndex_ = -1; | |
242 this.itemList_ = []; | |
243 if (this.origin) | |
244 this.origin.createItems(this); | |
245 this.itemsChild.appendChild(this.infoChild); | |
246 this.enableAnimation_(); | |
247 }, | |
248 | |
249 /** | |
250 * Append a new cookie node "bubble" to this list item. | |
251 * @param {CookieTreeNode} node The cookie node to add a bubble for. | |
252 * @param {Element} div The DOM element for the bubble itself. | |
253 * @return {number} The index the bubble was added at. | |
254 */ | |
255 appendItem: function(node, div) { | |
256 this.itemList_.push({node: node, div: div}); | |
257 this.itemsChild.appendChild(div); | |
258 return this.itemList_.length - 1; | |
259 }, | |
260 | |
261 /** | |
262 * The currently selected cookie node ("cookie bubble") index. | |
263 * @type {number} | |
264 * @private | |
265 */ | |
266 selectedIndex_: -1, | |
267 | |
268 /** | |
269 * Get the currently selected cookie node ("cookie bubble") index. | |
270 * @type {number} | |
271 */ | |
272 get selectedIndex() { | |
273 return this.selectedIndex_; | |
274 }, | |
275 | |
276 /** | |
277 * Set the currently selected cookie node ("cookie bubble") index to | |
278 * @{code itemIndex}, unselecting any previously selected node first. | |
279 * @param {number} itemIndex The index to set as the selected index. | |
280 */ | |
281 set selectedIndex(itemIndex) { | |
282 // Get the list index up front before we change anything. | |
283 var index = this.list.getIndexOfListItem(this); | |
284 // Unselect any previously selected item. | |
285 if (this.selectedIndex_ >= 0) { | |
286 var item = this.itemList_[this.selectedIndex_]; | |
287 if (item && item.div) | |
288 item.div.removeAttribute('selected'); | |
289 } | |
290 // Special case: decrementing -1 wraps around to the end of the list. | |
291 if (itemIndex == -2) | |
292 itemIndex = this.itemList_.length - 1; | |
293 // Check if we're going out of bounds and hide the item details. | |
294 if (itemIndex < 0 || itemIndex >= this.itemList_.length) { | |
295 this.selectedIndex_ = -1; | |
296 this.disableAnimation_(); | |
297 this.infoChild.classList.add('hidden'); | |
298 this.enableAnimation_(); | |
299 return; | |
300 } | |
301 // Set the new selected item and show the item details for it. | |
302 this.selectedIndex_ = itemIndex; | |
303 this.itemList_[itemIndex].div.setAttribute('selected', ''); | |
304 this.disableAnimation_(); | |
305 this.itemList_[itemIndex].node.setDetailText(this.infoChild, | |
306 this.list.infoNodes); | |
307 this.infoChild.classList.remove('hidden'); | |
308 this.enableAnimation_(); | |
309 // If we're near the bottom of the list this may cause the list item to go | |
310 // beyond the end of the visible area. Fix it after the animation is done. | |
311 var list = this.list; | |
312 window.setTimeout(function() { list.scrollIndexIntoView(index); }, 150); | |
313 }, | |
314 }; | |
315 | |
316 /** | |
317 * {@code CookieTreeNode}s mirror the structure of the cookie tree lazily, and | |
318 * contain all the actual data used to generate the {@code CookieListItem}s. | |
319 * @param {Object} data The data object for this node. | |
320 * @constructor | |
321 */ | |
322 function CookieTreeNode(data) { | |
323 this.data = data; | |
324 this.children = []; | |
325 } | |
326 | |
327 CookieTreeNode.prototype = { | |
328 /** | |
329 * Insert a cookie tree node at the given index. | |
330 * Both CookiesList and CookieTreeNode implement this API. | |
331 * @param {Object} data The data object for the node to add. | |
332 * @param {number} index The index at which to insert the node. | |
333 */ | |
334 insertAt: function(data, index) { | |
335 var child = new CookieTreeNode(data); | |
336 this.children.splice(index, 0, child); | |
337 child.parent = this; | |
338 this.updateOrigin(); | |
339 }, | |
340 | |
341 /** | |
342 * Remove a cookie tree node from the given index. | |
343 * Both CookiesList and CookieTreeNode implement this API. | |
344 * @param {number} index The index of the tree node to remove. | |
345 */ | |
346 remove: function(index) { | |
347 if (index < this.children.length) { | |
348 this.children.splice(index, 1); | |
349 this.updateOrigin(); | |
350 } | |
351 }, | |
352 | |
353 /** | |
354 * Clears all children. | |
355 * Both CookiesList and CookieTreeNode implement this API. | |
356 * It is used by CookiesList.loadChildren(). | |
357 */ | |
358 clear: function() { | |
359 // We might leave some garbage in parentLookup for removed children. | |
360 // But that should be OK because parentLookup is cleared when we | |
361 // reload the tree. | |
362 this.children = []; | |
363 this.updateOrigin(); | |
364 }, | |
365 | |
366 /** | |
367 * The counter used by startBatchUpdates() and endBatchUpdates(). | |
368 * @type {number} | |
369 */ | |
370 batchCount_: 0, | |
371 | |
372 /** | |
373 * See cr.ui.List.startBatchUpdates(). | |
374 * Both CookiesList (via List) and CookieTreeNode implement this API. | |
375 */ | |
376 startBatchUpdates: function() { | |
377 this.batchCount_++; | |
378 }, | |
379 | |
380 /** | |
381 * See cr.ui.List.endBatchUpdates(). | |
382 * Both CookiesList (via List) and CookieTreeNode implement this API. | |
383 */ | |
384 endBatchUpdates: function() { | |
385 if (!--this.batchCount_) | |
386 this.updateOrigin(); | |
387 }, | |
388 | |
389 /** | |
390 * Requests updating the origin summary to reflect changes in this item. | |
391 * Both CookieListItem and CookieTreeNode implement this API. | |
392 */ | |
393 updateOrigin: function() { | |
394 if (!this.batchCount_ && this.parent) | |
395 this.parent.updateOrigin(); | |
396 }, | |
397 | |
398 /** | |
399 * Summarize the information in this node and update @{code info}. | |
400 * This will recurse into child nodes to summarize all descendants. | |
401 * @param {Object} info The info object from @{code updateOrigin}. | |
402 */ | |
403 collectSummaryInfo: function(info) { | |
404 if (this.children.length > 0) { | |
405 for (var i = 0; i < this.children.length; ++i) | |
406 this.children[i].collectSummaryInfo(info); | |
407 } else if (this.data && !this.data.hasChildren) { | |
408 if (this.data.type == 'cookie') | |
409 info.cookies++; | |
410 else if (this.data.type == 'database') | |
411 info.database = true; | |
412 else if (this.data.type == 'local_storage') | |
413 info.localStorage = true; | |
414 else if (this.data.type == 'app_cache') | |
415 info.appCache = true; | |
416 else if (this.data.type == 'indexed_db') | |
417 info.indexedDb = true; | |
418 } | |
419 }, | |
420 | |
421 /** | |
422 * Create the cookie "bubbles" for this node, recursing into children | |
423 * if there are any. Append the cookie bubbles to @{code item}. | |
424 * @param {CookieListItem} item The cookie list item to create items in. | |
425 */ | |
426 createItems: function(item) { | |
427 if (this.children.length > 0) { | |
428 for (var i = 0; i < this.children.length; ++i) | |
429 this.children[i].createItems(item); | |
430 } else if (this.data && !this.data.hasChildren) { | |
431 var text = ''; | |
432 switch (this.data.type) { | |
433 case 'cookie': | |
434 case 'database': | |
435 text = this.data.name; | |
436 break; | |
437 case 'local_storage': | |
438 text = localStrings.getString('cookie_local_storage'); | |
439 break; | |
440 case 'app_cache': | |
441 text = localStrings.getString('cookie_app_cache'); | |
442 break; | |
443 case 'indexed_db': | |
444 text = localStrings.getString('cookie_indexed_db'); | |
445 break; | |
446 } | |
447 var div = item.ownerDocument.createElement('div'); | |
448 div.className = 'cookie-item'; | |
449 // Help out screen readers and such: this is a clickable thing. | |
450 div.setAttribute('role', 'button'); | |
451 div.textContent = text; | |
452 var index = item.appendItem(this, div); | |
453 div.onclick = function() { | |
454 if (item.selectedIndex == index) | |
455 item.selectedIndex = -1; | |
456 else | |
457 item.selectedIndex = index; | |
458 }; | |
459 } | |
460 }, | |
461 | |
462 /** | |
463 * Set the detail text to be displayed to that of this cookie tree node. | |
464 * Uses preallocated DOM elements for each cookie node type from @{code | |
465 * infoNodes}, and inserts the appropriate elements to @{code element}. | |
466 * @param {Element} element The DOM element to insert elements to. | |
467 * @param {Object.<string, {table: Element, info: Object.<string, | |
468 * Element>}>} infoNodes The map from cookie node types to maps from | |
469 * cookie attribute names to DOM elements to display cookie attribute | |
470 * values, created by @{code CookiesList.decorate}. | |
471 */ | |
472 setDetailText: function(element, infoNodes) { | |
473 var table; | |
474 if (this.data && !this.data.hasChildren) { | |
475 if (cookieInfo[this.data.type]) { | |
476 var info = cookieInfo[this.data.type]; | |
477 var nodes = infoNodes[this.data.type].info; | |
478 for (var i = 0; i < info.length; ++i) { | |
479 var name = info[i][0]; | |
480 if (name != 'id' && this.data[name]) | |
481 nodes[name].textContent = this.data[name]; | |
482 } | |
483 table = infoNodes[this.data.type].table; | |
484 } | |
485 } | |
486 while (element.childNodes.length > 1) | |
487 element.removeChild(element.firstChild); | |
488 if (table) | |
489 element.insertBefore(table, element.firstChild); | |
490 }, | |
491 | |
492 /** | |
493 * The parent of this cookie tree node. | |
494 * @type {?CookieTreeNode|CookieListItem} | |
495 */ | |
496 get parent(parent) { | |
497 // See below for an explanation of this special case. | |
498 if (typeof this.parent_ == 'number') | |
499 return this.list_.getListItemByIndex(this.parent_); | |
500 return this.parent_; | |
501 }, | |
502 set parent(parent) { | |
503 if (parent == this.parent) | |
504 return; | |
505 if (parent instanceof CookieListItem) { | |
506 // If the parent is to be a CookieListItem, then we keep the reference | |
507 // to it by its containing list and list index, rather than directly. | |
508 // This allows the list items to be garbage collected when they scroll | |
509 // out of view (except the expanded item, which we cache). This is | |
510 // transparent except in the setter and getter, where we handle it. | |
511 this.parent_ = parent.listIndex; | |
512 this.list_ = parent.list; | |
513 parent.addEventListener('listIndexChange', | |
514 this.parentIndexChanged_.bind(this)); | |
515 } else { | |
516 this.parent_ = parent; | |
517 } | |
518 if (this.data && this.data.id) { | |
519 if (parent) | |
520 parentLookup[this.data.id] = this; | |
521 else | |
522 delete parentLookup[this.data.id]; | |
523 } | |
524 if (this.data && this.data.hasChildren && | |
525 !this.children.length && !lookupRequests[this.data.id]) { | |
526 lookupRequests[this.data.id] = true; | |
527 chrome.send('loadCookie', [this.pathId]); | |
528 } | |
529 }, | |
530 | |
531 /** | |
532 * Called when the parent is a CookieListItem whose index has changed. | |
533 * See the code above that avoids keeping a direct reference to | |
534 * CookieListItem parents, to allow them to be garbage collected. | |
535 * @private | |
536 */ | |
537 parentIndexChanged_: function(event) { | |
538 if (typeof this.parent_ == 'number') { | |
539 this.parent_ = event.newValue; | |
540 // We set a timeout to update the origin, rather than doing it right | |
541 // away, because this callback may occur while the list items are | |
542 // being repopulated following a scroll event. Calling updateOrigin() | |
543 // immediately could trigger relayout that would reset the scroll | |
544 // position within the list, among other things. | |
545 window.setTimeout(this.updateOrigin.bind(this), 0); | |
546 } | |
547 }, | |
548 | |
549 /** | |
550 * The cookie tree path id. | |
551 * @type {string} | |
552 */ | |
553 get pathId() { | |
554 var parent = this.parent; | |
555 if (parent && parent instanceof CookieTreeNode) | |
556 return parent.pathId + ',' + this.data.id; | |
557 return this.data.id; | |
558 }, | |
559 }; | |
560 | |
561 /** | |
562 * Creates a new cookies list. | |
563 * @param {Object=} opt_propertyBag Optional properties. | |
564 * @constructor | |
565 * @extends {DeletableItemList} | |
566 */ | |
567 var CookiesList = cr.ui.define('list'); | |
568 | |
569 CookiesList.prototype = { | |
570 __proto__: DeletableItemList.prototype, | |
571 | |
572 /** @inheritDoc */ | |
573 decorate: function() { | |
574 DeletableItemList.prototype.decorate.call(this); | |
575 this.classList.add('cookie-list'); | |
576 this.data_ = []; | |
577 this.dataModel = new ArrayDataModel(this.data_); | |
578 this.addEventListener('keydown', this.handleKeyLeftRight_.bind(this)); | |
579 var sm = new ListSingleSelectionModel(); | |
580 sm.addEventListener('change', this.cookieSelectionChange_.bind(this)); | |
581 sm.addEventListener('leadIndexChange', this.cookieLeadChange_.bind(this)); | |
582 this.selectionModel = sm; | |
583 this.infoNodes = {}; | |
584 var doc = this.ownerDocument; | |
585 // Create a table for each type of site data (e.g. cookies, databases, | |
586 // etc.) and save it so that we can reuse it for all origins. | |
587 for (var type in cookieInfo) { | |
588 var table = doc.createElement('table'); | |
589 table.className = 'cookie-details-table'; | |
590 var tbody = doc.createElement('tbody'); | |
591 table.appendChild(tbody); | |
592 var info = {}; | |
593 for (var i = 0; i < cookieInfo[type].length; i++) { | |
594 var tr = doc.createElement('tr'); | |
595 var name = doc.createElement('td'); | |
596 var data = doc.createElement('td'); | |
597 var pair = cookieInfo[type][i]; | |
598 name.className = 'cookie-details-label'; | |
599 name.textContent = localStrings.getString(pair[1]); | |
600 data.className = 'cookie-details-value'; | |
601 data.textContent = ''; | |
602 tr.appendChild(name); | |
603 tr.appendChild(data); | |
604 tbody.appendChild(tr); | |
605 info[pair[0]] = data; | |
606 } | |
607 this.infoNodes[type] = {table: table, info: info}; | |
608 } | |
609 }, | |
610 | |
611 /** | |
612 * Handles key down events and looks for left and right arrows, then | |
613 * dispatches to the currently expanded item, if any. | |
614 * @param {Event} e The keydown event. | |
615 * @private | |
616 */ | |
617 handleKeyLeftRight_: function(e) { | |
618 var id = e.keyIdentifier; | |
619 if ((id == 'Left' || id == 'Right') && this.expandedItem) { | |
620 var cs = this.ownerDocument.defaultView.getComputedStyle(this); | |
621 var rtl = cs.direction == 'rtl'; | |
622 if ((!rtl && id == 'Left') || (rtl && id == 'Right')) | |
623 this.expandedItem.selectedIndex--; | |
624 else | |
625 this.expandedItem.selectedIndex++; | |
626 this.scrollIndexIntoView(this.expandedItem.listIndex); | |
627 // Prevent the page itself from scrolling. | |
628 e.preventDefault(); | |
629 } | |
630 }, | |
631 | |
632 /** | |
633 * Called on selection model selection changes. | |
634 * @param {Event} ce The selection change event. | |
635 * @private | |
636 */ | |
637 cookieSelectionChange_: function(ce) { | |
638 ce.changes.forEach(function(change) { | |
639 var listItem = this.getListItemByIndex(change.index); | |
640 if (listItem) { | |
641 if (!change.selected) { | |
642 // We set a timeout here, rather than setting the item unexpanded | |
643 // immediately, so that if another item gets set expanded right | |
644 // away, it will be expanded before this item is unexpanded. It | |
645 // will notice that, and unexpand this item in sync with its own | |
646 // expansion. Later, this callback will end up having no effect. | |
647 window.setTimeout(function() { | |
648 if (!listItem.selected || !listItem.lead) | |
649 listItem.expanded = false; | |
650 }, 0); | |
651 } else if (listItem.lead) { | |
652 listItem.expanded = true; | |
653 } | |
654 } | |
655 }, this); | |
656 }, | |
657 | |
658 /** | |
659 * Called on selection model lead changes. | |
660 * @param {Event} pe The lead change event. | |
661 * @private | |
662 */ | |
663 cookieLeadChange_: function(pe) { | |
664 if (pe.oldValue != -1) { | |
665 var listItem = this.getListItemByIndex(pe.oldValue); | |
666 if (listItem) { | |
667 // See cookieSelectionChange_ above for why we use a timeout here. | |
668 window.setTimeout(function() { | |
669 if (!listItem.lead || !listItem.selected) | |
670 listItem.expanded = false; | |
671 }, 0); | |
672 } | |
673 } | |
674 if (pe.newValue != -1) { | |
675 var listItem = this.getListItemByIndex(pe.newValue); | |
676 if (listItem && listItem.selected) | |
677 listItem.expanded = true; | |
678 } | |
679 }, | |
680 | |
681 /** | |
682 * The currently expanded item. Used by CookieListItem above. | |
683 * @type {?CookieListItem} | |
684 */ | |
685 expandedItem: null, | |
686 | |
687 // from cr.ui.List | |
688 /** @inheritDoc */ | |
689 createItem: function(data) { | |
690 // We use the cached expanded item in order to allow it to maintain some | |
691 // state (like its fixed height, and which bubble is selected). | |
692 if (this.expandedItem && this.expandedItem.origin == data) | |
693 return this.expandedItem; | |
694 return new CookieListItem(data, this); | |
695 }, | |
696 | |
697 // from options.DeletableItemList | |
698 /** @inheritDoc */ | |
699 deleteItemAtIndex: function(index) { | |
700 var item = this.data_[index]; | |
701 if (item) { | |
702 var pathId = item.pathId; | |
703 if (pathId) | |
704 chrome.send('removeCookie', [pathId]); | |
705 } | |
706 }, | |
707 | |
708 /** | |
709 * Insert a cookie tree node at the given index. | |
710 * Both CookiesList and CookieTreeNode implement this API. | |
711 * @param {Object} data The data object for the node to add. | |
712 * @param {number} index The index at which to insert the node. | |
713 */ | |
714 insertAt: function(data, index) { | |
715 this.dataModel.splice(index, 0, new CookieTreeNode(data)); | |
716 }, | |
717 | |
718 /** | |
719 * Remove a cookie tree node from the given index. | |
720 * Both CookiesList and CookieTreeNode implement this API. | |
721 * @param {number} index The index of the tree node to remove. | |
722 */ | |
723 remove: function(index) { | |
724 if (index < this.data_.length) | |
725 this.dataModel.splice(index, 1); | |
726 }, | |
727 | |
728 /** | |
729 * Clears the list. | |
730 * Both CookiesList and CookieTreeNode implement this API. | |
731 * It is used by CookiesList.loadChildren(). | |
732 */ | |
733 clear: function() { | |
734 parentLookup = {}; | |
735 this.data_ = []; | |
736 this.dataModel = new ArrayDataModel(this.data_); | |
737 this.redraw(); | |
738 }, | |
739 | |
740 /** | |
741 * Add tree nodes by given parent. | |
742 * Note: this method will be O(n^2) in the general case. Use it only to | |
743 * populate an empty parent or to insert single nodes to avoid this. | |
744 * @param {Object} parent The parent node. | |
745 * @param {number} start Start index of where to insert nodes. | |
746 * @param {Array} nodesData Nodes data array. | |
747 * @private | |
748 */ | |
749 addByParent_: function(parent, start, nodesData) { | |
750 if (!parent) | |
751 return; | |
752 | |
753 parent.startBatchUpdates(); | |
754 for (var i = 0; i < nodesData.length; ++i) | |
755 parent.insertAt(nodesData[i], start + i); | |
756 parent.endBatchUpdates(); | |
757 | |
758 cr.dispatchSimpleEvent(this, 'change'); | |
759 }, | |
760 | |
761 /** | |
762 * Add tree nodes by parent id. | |
763 * This is used by cookies_view.js. | |
764 * Note: this method will be O(n^2) in the general case. Use it only to | |
765 * populate an empty parent or to insert single nodes to avoid this. | |
766 * @param {string} parentId Id of the parent node. | |
767 * @param {number} start Start index of where to insert nodes. | |
768 * @param {Array} nodesData Nodes data array. | |
769 */ | |
770 addByParentId: function(parentId, start, nodesData) { | |
771 var parent = parentId ? parentLookup[parentId] : this; | |
772 this.addByParent_(parent, start, nodesData); | |
773 }, | |
774 | |
775 /** | |
776 * Removes tree nodes by parent id. | |
777 * This is used by cookies_view.js. | |
778 * @param {string} parentId Id of the parent node. | |
779 * @param {number} start Start index of nodes to remove. | |
780 * @param {number} count Number of nodes to remove. | |
781 */ | |
782 removeByParentId: function(parentId, start, count) { | |
783 var parent = parentId ? parentLookup[parentId] : this; | |
784 if (!parent) | |
785 return; | |
786 | |
787 parent.startBatchUpdates(); | |
788 while (count-- > 0) | |
789 parent.remove(start); | |
790 parent.endBatchUpdates(); | |
791 | |
792 cr.dispatchSimpleEvent(this, 'change'); | |
793 }, | |
794 | |
795 /** | |
796 * Loads the immediate children of given parent node. | |
797 * This is used by cookies_view.js. | |
798 * @param {string} parentId Id of the parent node. | |
799 * @param {Array} children The immediate children of parent node. | |
800 */ | |
801 loadChildren: function(parentId, children) { | |
802 if (parentId) | |
803 delete lookupRequests[parentId]; | |
804 var parent = parentId ? parentLookup[parentId] : this; | |
805 if (!parent) | |
806 return; | |
807 | |
808 parent.startBatchUpdates(); | |
809 parent.clear(); | |
810 this.addByParent_(parent, 0, children); | |
811 parent.endBatchUpdates(); | |
812 }, | |
813 }; | |
814 | |
815 return { | |
816 CookiesList: CookiesList | |
817 }; | |
818 }); | |
OLD | NEW |