| 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 |