| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| cr.define('options', function() { |
| /** @const */ var DeletableItemList = options.DeletableItemList; |
| /** @const */ var DeletableItem = options.DeletableItem; |
| /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel; |
| /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel; |
| |
| // This structure maps the various cookie type names from C++ (hence the |
| // underscores) to arrays of the different types of data each has, along with |
| // the i18n name for the description of that data type. |
| /** @const */ var cookieInfo = { |
| 'cookie': [['name', 'label_cookie_name'], |
| ['content', 'label_cookie_content'], |
| ['domain', 'label_cookie_domain'], |
| ['path', 'label_cookie_path'], |
| ['sendfor', 'label_cookie_send_for'], |
| ['accessibleToScript', 'label_cookie_accessible_to_script'], |
| ['created', 'label_cookie_created'], |
| ['expires', 'label_cookie_expires']], |
| 'app_cache': [['manifest', 'label_app_cache_manifest'], |
| ['size', 'label_local_storage_size'], |
| ['created', 'label_cookie_created'], |
| ['accessed', 'label_cookie_last_accessed']], |
| 'database': [['name', 'label_cookie_name'], |
| ['desc', 'label_webdb_desc'], |
| ['size', 'label_local_storage_size'], |
| ['modified', 'label_local_storage_last_modified']], |
| 'local_storage': [['origin', 'label_local_storage_origin'], |
| ['size', 'label_local_storage_size'], |
| ['modified', 'label_local_storage_last_modified']], |
| 'indexed_db': [['origin', 'label_indexed_db_origin'], |
| ['size', 'label_indexed_db_size'], |
| ['modified', 'label_indexed_db_last_modified']], |
| 'file_system': [['origin', 'label_file_system_origin'], |
| ['persistent', 'label_file_system_persistent_usage'], |
| ['temporary', 'label_file_system_temporary_usage']], |
| 'server_bound_cert': [['serverId', 'label_server_bound_cert_server_id'], |
| ['certType', 'label_server_bound_cert_type'], |
| ['created', 'label_server_bound_cert_created']], |
| 'flash_lso': [['domain', 'label_cookie_domain']], |
| }; |
| |
| /** |
| * Returns the item's height, like offsetHeight but such that it works better |
| * when the page is zoomed. See the similar calculation in @{code cr.ui.List}. |
| * This version also accounts for the animation done in this file. |
| * @param {Element} item The item to get the height of. |
| * @return {number} The height of the item, calculated with zooming in mind. |
| */ |
| function getItemHeight(item) { |
| var height = item.style.height; |
| // Use the fixed animation target height if set, in case the element is |
| // currently being animated and we'd get an intermediate height below. |
| if (height && height.substr(-2) == 'px') |
| return parseInt(height.substr(0, height.length - 2)); |
| return item.getBoundingClientRect().height; |
| } |
| |
| /** |
| * Create tree nodes for the objects in the data array, and insert them all |
| * into the given list using its @{code splice} method at the given index. |
| * @param {Array.<Object>} data The data objects for the nodes to add. |
| * @param {number} start The index at which to start inserting the nodes. |
| * @return {Array.<CookieTreeNode>} An array of CookieTreeNodes added. |
| */ |
| function spliceTreeNodes(data, start, list) { |
| var nodes = data.map(function(x) { return new CookieTreeNode(x); }); |
| // Insert [start, 0] at the beginning of the array of nodes, making it |
| // into the arguments we want to pass to @{code list.splice} below. |
| nodes.splice(0, 0, start, 0); |
| list.splice.apply(list, nodes); |
| // Remove the [start, 0] prefix and return the array of nodes. |
| nodes.splice(0, 2); |
| return nodes; |
| } |
| |
| /** |
| * Adds information about an app that protects this data item to the |
| * @{code element}. |
| * @param {Element} element The DOM element the information should be |
| appended to. |
| * @param {{id: string, name: string}} appInfo Information about an app. |
| */ |
| function addAppInfo(element, appInfo) { |
| var img = element.ownerDocument.createElement('img'); |
| img.src = 'chrome://extension-icon/' + appInfo.id + '/16/1'; |
| element.title = loadTimeData.getString('label_protected_by_apps') + |
| ' ' + appInfo.name; |
| img.className = 'protecting-app'; |
| element.appendChild(img); |
| } |
| |
| var parentLookup = {}; |
| var lookupRequests = {}; |
| |
| /** |
| * Creates a new list item for sites data. Note that these are created and |
| * destroyed lazily as they scroll into and out of view, so they must be |
| * stateless. We cache the expanded item in @{code CookiesList} though, so it |
| * can keep state. (Mostly just which item is selected.) |
| * @param {Object} origin Data used to create a cookie list item. |
| * @param {CookiesList} list The list that will contain this item. |
| * @constructor |
| * @extends {DeletableItem} |
| */ |
| function CookieListItem(origin, list) { |
| var listItem = new DeletableItem(null); |
| listItem.__proto__ = CookieListItem.prototype; |
| |
| listItem.origin = origin; |
| listItem.list = list; |
| listItem.decorate(); |
| |
| // This hooks up updateOrigin() to the list item, makes the top-level |
| // tree nodes (i.e., origins) register their IDs in parentLookup, and |
| // causes them to request their children if they have none. Note that we |
| // have special logic in the setter for the parent property to make sure |
| // that we can still garbage collect list items when they scroll out of |
| // view, even though it appears that we keep a direct reference. |
| if (origin) { |
| origin.parent = listItem; |
| origin.updateOrigin(); |
| } |
| |
| return listItem; |
| } |
| |
| CookieListItem.prototype = { |
| __proto__: DeletableItem.prototype, |
| |
| /** @override */ |
| decorate: function() { |
| this.siteChild = this.ownerDocument.createElement('div'); |
| this.siteChild.className = 'cookie-site'; |
| this.dataChild = this.ownerDocument.createElement('div'); |
| this.dataChild.className = 'cookie-data'; |
| this.sizeChild = this.ownerDocument.createElement('div'); |
| this.sizeChild.className = 'cookie-size'; |
| this.itemsChild = this.ownerDocument.createElement('div'); |
| this.itemsChild.className = 'cookie-items'; |
| this.infoChild = this.ownerDocument.createElement('div'); |
| this.infoChild.className = 'cookie-details'; |
| this.infoChild.hidden = true; |
| |
| var remove = this.ownerDocument.createElement('button'); |
| remove.textContent = loadTimeData.getString('remove_cookie'); |
| remove.onclick = this.removeCookie_.bind(this); |
| this.infoChild.appendChild(remove); |
| var content = this.contentElement; |
| content.appendChild(this.siteChild); |
| content.appendChild(this.dataChild); |
| content.appendChild(this.sizeChild); |
| content.appendChild(this.itemsChild); |
| this.itemsChild.appendChild(this.infoChild); |
| if (this.origin && this.origin.data) { |
| this.siteChild.textContent = this.origin.data.title; |
| this.siteChild.setAttribute('title', this.origin.data.title); |
| } |
| this.itemList_ = []; |
| }, |
| |
| /** @type {boolean} */ |
| get expanded() { |
| return this.expanded_; |
| }, |
| set expanded(expanded) { |
| if (this.expanded_ == expanded) |
| return; |
| this.expanded_ = expanded; |
| if (expanded) { |
| var oldExpanded = this.list.expandedItem; |
| this.list.expandedItem = this; |
| this.updateItems_(); |
| if (oldExpanded) |
| oldExpanded.expanded = false; |
| this.classList.add('show-items'); |
| } else { |
| if (this.list.expandedItem == this) { |
| this.list.expandedItem = null; |
| } |
| this.style.height = ''; |
| this.itemsChild.style.height = ''; |
| this.classList.remove('show-items'); |
| } |
| }, |
| |
| /** |
| * The callback for the "remove" button shown when an item is selected. |
| * Requests that the currently selected cookie be removed. |
| * @private |
| */ |
| removeCookie_: function() { |
| if (this.selectedIndex_ >= 0) { |
| var item = this.itemList_[this.selectedIndex_]; |
| if (item && item.node) |
| chrome.send('removeCookie', [item.node.pathId]); |
| } |
| }, |
| |
| /** |
| * Disable animation within this cookie list item, in preparation for making |
| * changes that will need to be animated. Makes it possible to measure the |
| * contents without displaying them, to set animation targets. |
| * @private |
| */ |
| disableAnimation_: function() { |
| this.itemsHeight_ = getItemHeight(this.itemsChild); |
| this.classList.add('measure-items'); |
| }, |
| |
| /** |
| * Enable animation after changing the contents of this cookie list item. |
| * See @{code disableAnimation_}. |
| * @private |
| */ |
| enableAnimation_: function() { |
| if (!this.classList.contains('measure-items')) |
| this.disableAnimation_(); |
| this.itemsChild.style.height = ''; |
| // This will force relayout in order to calculate the new heights. |
| var itemsHeight = getItemHeight(this.itemsChild); |
| var fixedHeight = getItemHeight(this) + itemsHeight - this.itemsHeight_; |
| this.itemsChild.style.height = this.itemsHeight_ + 'px'; |
| // Force relayout before enabling animation, so that if we have |
| // changed things since the last layout, they will not be animated |
| // during subsequent layouts. |
| this.itemsChild.offsetHeight; |
| this.classList.remove('measure-items'); |
| this.itemsChild.style.height = itemsHeight + 'px'; |
| this.style.height = fixedHeight + 'px'; |
| }, |
| |
| /** |
| * Updates the origin summary to reflect changes in its items. |
| * Both CookieListItem and CookieTreeNode implement this API. |
| * This implementation scans the descendants to update the text. |
| */ |
| updateOrigin: function() { |
| var info = { |
| cookies: 0, |
| database: false, |
| localStorage: false, |
| appCache: false, |
| indexedDb: false, |
| fileSystem: false, |
| serverBoundCerts: 0, |
| }; |
| if (this.origin) |
| this.origin.collectSummaryInfo(info); |
| |
| var list = []; |
| if (info.cookies > 1) |
| list.push(loadTimeData.getStringF('cookie_plural', info.cookies)); |
| else if (info.cookies > 0) |
| list.push(loadTimeData.getString('cookie_singular')); |
| if (info.database || info.indexedDb) |
| list.push(loadTimeData.getString('cookie_database_storage')); |
| if (info.localStorage) |
| list.push(loadTimeData.getString('cookie_local_storage')); |
| if (info.appCache) |
| list.push(loadTimeData.getString('cookie_app_cache')); |
| if (info.fileSystem) |
| list.push(loadTimeData.getString('cookie_file_system')); |
| if (info.serverBoundCerts) |
| list.push(loadTimeData.getString('cookie_server_bound_cert')); |
| if (info.flashLSO) |
| list.push(loadTimeData.getString('cookie_flash_lso')); |
| |
| var text = ''; |
| for (var i = 0; i < list.length; ++i) { |
| if (text.length > 0) |
| text += ', ' + list[i]; |
| else |
| text = list[i]; |
| } |
| this.dataChild.textContent = text; |
| |
| var apps = info.appsProtectingThis; |
| for (var key in apps) { |
| addAppInfo(this.dataChild, apps[key]); |
| } |
| |
| if (info.quota && info.quota.totalUsage) |
| this.sizeChild.textContent = info.quota.totalUsage; |
| |
| if (this.expanded) |
| this.updateItems_(); |
| }, |
| |
| /** |
| * Updates the items section to reflect changes, animating to the new state. |
| * Removes existing contents and calls @{code CookieTreeNode.createItems}. |
| * @private |
| */ |
| updateItems_: function() { |
| this.disableAnimation_(); |
| this.itemsChild.textContent = ''; |
| this.infoChild.hidden = true; |
| this.selectedIndex_ = -1; |
| this.itemList_ = []; |
| if (this.origin) |
| this.origin.createItems(this); |
| this.itemsChild.appendChild(this.infoChild); |
| this.enableAnimation_(); |
| }, |
| |
| /** |
| * Append a new cookie node "bubble" to this list item. |
| * @param {CookieTreeNode} node The cookie node to add a bubble for. |
| * @param {Element} div The DOM element for the bubble itself. |
| * @return {number} The index the bubble was added at. |
| */ |
| appendItem: function(node, div) { |
| this.itemList_.push({node: node, div: div}); |
| this.itemsChild.appendChild(div); |
| return this.itemList_.length - 1; |
| }, |
| |
| /** |
| * The currently selected cookie node ("cookie bubble") index. |
| * @type {number} |
| * @private |
| */ |
| selectedIndex_: -1, |
| |
| /** |
| * Get the currently selected cookie node ("cookie bubble") index. |
| * @type {number} |
| */ |
| get selectedIndex() { |
| return this.selectedIndex_; |
| }, |
| |
| /** |
| * Set the currently selected cookie node ("cookie bubble") index to |
| * @{code itemIndex}, unselecting any previously selected node first. |
| * @param {number} itemIndex The index to set as the selected index. |
| */ |
| set selectedIndex(itemIndex) { |
| // Get the list index up front before we change anything. |
| var index = this.list.getIndexOfListItem(this); |
| // Unselect any previously selected item. |
| if (this.selectedIndex_ >= 0) { |
| var item = this.itemList_[this.selectedIndex_]; |
| if (item && item.div) |
| item.div.removeAttribute('selected'); |
| } |
| // Special case: decrementing -1 wraps around to the end of the list. |
| if (itemIndex == -2) |
| itemIndex = this.itemList_.length - 1; |
| // Check if we're going out of bounds and hide the item details. |
| if (itemIndex < 0 || itemIndex >= this.itemList_.length) { |
| this.selectedIndex_ = -1; |
| this.disableAnimation_(); |
| this.infoChild.hidden = true; |
| this.enableAnimation_(); |
| return; |
| } |
| // Set the new selected item and show the item details for it. |
| this.selectedIndex_ = itemIndex; |
| this.itemList_[itemIndex].div.setAttribute('selected', ''); |
| this.disableAnimation_(); |
| this.itemList_[itemIndex].node.setDetailText(this.infoChild, |
| this.list.infoNodes); |
| this.infoChild.hidden = false; |
| this.enableAnimation_(); |
| // If we're near the bottom of the list this may cause the list item to go |
| // beyond the end of the visible area. Fix it after the animation is done. |
| var list = this.list; |
| window.setTimeout(function() { list.scrollIndexIntoView(index); }, 150); |
| }, |
| }; |
| |
| /** |
| * {@code CookieTreeNode}s mirror the structure of the cookie tree lazily, and |
| * contain all the actual data used to generate the {@code CookieListItem}s. |
| * @param {Object} data The data object for this node. |
| * @constructor |
| */ |
| function CookieTreeNode(data) { |
| this.data = data; |
| this.children = []; |
| } |
| |
| CookieTreeNode.prototype = { |
| /** |
| * Insert the given list of cookie tree nodes at the given index. |
| * Both CookiesList and CookieTreeNode implement this API. |
| * @param {Array.<Object>} data The data objects for the nodes to add. |
| * @param {number} start The index at which to start inserting the nodes. |
| */ |
| insertAt: function(data, start) { |
| var nodes = spliceTreeNodes(data, start, this.children); |
| for (var i = 0; i < nodes.length; i++) |
| nodes[i].parent = this; |
| this.updateOrigin(); |
| }, |
| |
| /** |
| * Remove a cookie tree node from the given index. |
| * Both CookiesList and CookieTreeNode implement this API. |
| * @param {number} index The index of the tree node to remove. |
| */ |
| remove: function(index) { |
| if (index < this.children.length) { |
| this.children.splice(index, 1); |
| this.updateOrigin(); |
| } |
| }, |
| |
| /** |
| * Clears all children. |
| * Both CookiesList and CookieTreeNode implement this API. |
| * It is used by CookiesList.loadChildren(). |
| */ |
| clear: function() { |
| // We might leave some garbage in parentLookup for removed children. |
| // But that should be OK because parentLookup is cleared when we |
| // reload the tree. |
| this.children = []; |
| this.updateOrigin(); |
| }, |
| |
| /** |
| * The counter used by startBatchUpdates() and endBatchUpdates(). |
| * @type {number} |
| */ |
| batchCount_: 0, |
| |
| /** |
| * See cr.ui.List.startBatchUpdates(). |
| * Both CookiesList (via List) and CookieTreeNode implement this API. |
| */ |
| startBatchUpdates: function() { |
| this.batchCount_++; |
| }, |
| |
| /** |
| * See cr.ui.List.endBatchUpdates(). |
| * Both CookiesList (via List) and CookieTreeNode implement this API. |
| */ |
| endBatchUpdates: function() { |
| if (!--this.batchCount_) |
| this.updateOrigin(); |
| }, |
| |
| /** |
| * Requests updating the origin summary to reflect changes in this item. |
| * Both CookieListItem and CookieTreeNode implement this API. |
| */ |
| updateOrigin: function() { |
| if (!this.batchCount_ && this.parent) |
| this.parent.updateOrigin(); |
| }, |
| |
| /** |
| * Summarize the information in this node and update @{code info}. |
| * This will recurse into child nodes to summarize all descendants. |
| * @param {Object} info The info object from @{code updateOrigin}. |
| */ |
| collectSummaryInfo: function(info) { |
| if (this.children.length > 0) { |
| for (var i = 0; i < this.children.length; ++i) |
| this.children[i].collectSummaryInfo(info); |
| } else if (this.data && !this.data.hasChildren) { |
| if (this.data.type == 'cookie') { |
| info.cookies++; |
| } else if (this.data.type == 'database') { |
| info.database = true; |
| } else if (this.data.type == 'local_storage') { |
| info.localStorage = true; |
| } else if (this.data.type == 'app_cache') { |
| info.appCache = true; |
| } else if (this.data.type == 'indexed_db') { |
| info.indexedDb = true; |
| } else if (this.data.type == 'file_system') { |
| info.fileSystem = true; |
| } else if (this.data.type == 'quota') { |
| info.quota = this.data; |
| } else if (this.data.type == 'server_bound_cert') { |
| info.serverBoundCerts++; |
| } else if (this.data.type == 'flash_lso') { |
| info.flashLSO = true; |
| } |
| |
| var apps = this.data.appsProtectingThis; |
| if (apps) { |
| if (!info.appsProtectingThis) |
| info.appsProtectingThis = {}; |
| apps.forEach(function(appInfo) { |
| info.appsProtectingThis[appInfo.id] = appInfo; |
| }); |
| } |
| } |
| }, |
| |
| /** |
| * Create the cookie "bubbles" for this node, recursing into children |
| * if there are any. Append the cookie bubbles to @{code item}. |
| * @param {CookieListItem} item The cookie list item to create items in. |
| */ |
| createItems: function(item) { |
| if (this.children.length > 0) { |
| for (var i = 0; i < this.children.length; ++i) |
| this.children[i].createItems(item); |
| return; |
| } |
| |
| if (!this.data || this.data.hasChildren) |
| return; |
| |
| var text = ''; |
| switch (this.data.type) { |
| case 'cookie': |
| case 'database': |
| text = this.data.name; |
| break; |
| default: |
| text = loadTimeData.getString('cookie_' + this.data.type); |
| } |
| if (!text) |
| return; |
| |
| var div = item.ownerDocument.createElement('div'); |
| div.className = 'cookie-item'; |
| // Help out screen readers and such: this is a clickable thing. |
| div.setAttribute('role', 'button'); |
| div.tabIndex = 0; |
| div.textContent = text; |
| var apps = this.data.appsProtectingThis; |
| if (apps) |
| apps.forEach(addAppInfo.bind(null, div)); |
| |
| var index = item.appendItem(this, div); |
| div.onclick = function() { |
| item.selectedIndex = (item.selectedIndex == index) ? -1 : index; |
| }; |
| }, |
| |
| /** |
| * Set the detail text to be displayed to that of this cookie tree node. |
| * Uses preallocated DOM elements for each cookie node type from @{code |
| * infoNodes}, and inserts the appropriate elements to @{code element}. |
| * @param {Element} element The DOM element to insert elements to. |
| * @param {Object.<string, {table: Element, info: Object.<string, |
| * Element>}>} infoNodes The map from cookie node types to maps from |
| * cookie attribute names to DOM elements to display cookie attribute |
| * values, created by @{code CookiesList.decorate}. |
| */ |
| setDetailText: function(element, infoNodes) { |
| var table; |
| if (this.data && !this.data.hasChildren && cookieInfo[this.data.type]) { |
| var info = cookieInfo[this.data.type]; |
| var nodes = infoNodes[this.data.type].info; |
| for (var i = 0; i < info.length; ++i) { |
| var name = info[i][0]; |
| if (name != 'id' && this.data[name]) |
| nodes[name].textContent = this.data[name]; |
| else |
| nodes[name].textContent = ''; |
| } |
| table = infoNodes[this.data.type].table; |
| } |
| |
| while (element.childNodes.length > 1) |
| element.removeChild(element.firstChild); |
| |
| if (table) |
| element.insertBefore(table, element.firstChild); |
| }, |
| |
| /** |
| * The parent of this cookie tree node. |
| * @type {?CookieTreeNode|CookieListItem} |
| */ |
| get parent() { |
| // See below for an explanation of this special case. |
| if (typeof this.parent_ == 'number') |
| return this.list_.getListItemByIndex(this.parent_); |
| return this.parent_; |
| }, |
| set parent(parent) { |
| if (parent == this.parent) |
| return; |
| |
| if (parent instanceof CookieListItem) { |
| // If the parent is to be a CookieListItem, then we keep the reference |
| // to it by its containing list and list index, rather than directly. |
| // This allows the list items to be garbage collected when they scroll |
| // out of view (except the expanded item, which we cache). This is |
| // transparent except in the setter and getter, where we handle it. |
| if (this.parent_ == undefined || parent.listIndex != -1) { |
| // Setting the parent is somewhat tricky because the CookieListItem |
| // constructor has side-effects on the |origin| that it wraps. Every |
| // time a CookieListItem is created for an |origin|, it registers |
| // itself as the parent of the |origin|. |
| // The List implementation may create a temporary CookieListItem item |
| // that wraps the |origin| of the very first entry of the CokiesList, |
| // when the List is redrawn the first time. This temporary |
| // CookieListItem is fresh (has listIndex = -1) and is never inserted |
| // into the List. Therefore it gets never updated. This destroys the |
| // chain of parent pointers. |
| // This is the stack trace: |
| // CookieListItem |
| // CookiesList.createItem |
| // List.measureItem |
| // List.getDefaultItemSize_ |
| // List.getDefaultItemHeight_ |
| // List.getIndexForListOffset_ |
| // List.getItemsInViewPort |
| // List.redraw |
| // List.endBatchUpdates |
| // CookiesList.loadChildren |
| this.parent_ = parent.listIndex; |
| } |
| this.list_ = parent.list; |
| parent.addEventListener('listIndexChange', |
| this.parentIndexChanged_.bind(this)); |
| } else { |
| this.parent_ = parent; |
| } |
| |
| if (this.data && this.data.id) { |
| if (parent) |
| parentLookup[this.data.id] = this; |
| else |
| delete parentLookup[this.data.id]; |
| } |
| |
| if (this.data && this.data.hasChildren && |
| !this.children.length && !lookupRequests[this.data.id]) { |
| lookupRequests[this.data.id] = true; |
| chrome.send('loadCookie', [this.pathId]); |
| } |
| }, |
| |
| /** |
| * Called when the parent is a CookieListItem whose index has changed. |
| * See the code above that avoids keeping a direct reference to |
| * CookieListItem parents, to allow them to be garbage collected. |
| * @private |
| */ |
| parentIndexChanged_: function(event) { |
| if (typeof this.parent_ == 'number') { |
| this.parent_ = event.newValue; |
| // We set a timeout to update the origin, rather than doing it right |
| // away, because this callback may occur while the list items are |
| // being repopulated following a scroll event. Calling updateOrigin() |
| // immediately could trigger relayout that would reset the scroll |
| // position within the list, among other things. |
| window.setTimeout(this.updateOrigin.bind(this), 0); |
| } |
| }, |
| |
| /** |
| * The cookie tree path id. |
| * @type {string} |
| */ |
| get pathId() { |
| var parent = this.parent; |
| if (parent && parent instanceof CookieTreeNode) |
| return parent.pathId + ',' + this.data.id; |
| return this.data.id; |
| }, |
| }; |
| |
| /** |
| * Creates a new cookies list. |
| * @param {Object=} opt_propertyBag Optional properties. |
| * @constructor |
| * @extends {DeletableItemList} |
| */ |
| var CookiesList = cr.ui.define('list'); |
| |
| CookiesList.prototype = { |
| __proto__: DeletableItemList.prototype, |
| |
| /** @override */ |
| decorate: function() { |
| DeletableItemList.prototype.decorate.call(this); |
| this.classList.add('cookie-list'); |
| this.dataModel = new ArrayDataModel([]); |
| this.addEventListener('keydown', this.handleKeyLeftRight_.bind(this)); |
| var sm = new ListSingleSelectionModel(); |
| sm.addEventListener('change', this.cookieSelectionChange_.bind(this)); |
| sm.addEventListener('leadIndexChange', this.cookieLeadChange_.bind(this)); |
| this.selectionModel = sm; |
| this.infoNodes = {}; |
| this.fixedHeight = false; |
| var doc = this.ownerDocument; |
| // Create a table for each type of site data (e.g. cookies, databases, |
| // etc.) and save it so that we can reuse it for all origins. |
| for (var type in cookieInfo) { |
| var table = doc.createElement('table'); |
| table.className = 'cookie-details-table'; |
| var tbody = doc.createElement('tbody'); |
| table.appendChild(tbody); |
| var info = {}; |
| for (var i = 0; i < cookieInfo[type].length; i++) { |
| var tr = doc.createElement('tr'); |
| var name = doc.createElement('td'); |
| var data = doc.createElement('td'); |
| var pair = cookieInfo[type][i]; |
| name.className = 'cookie-details-label'; |
| name.textContent = loadTimeData.getString(pair[1]); |
| data.className = 'cookie-details-value'; |
| data.textContent = ''; |
| tr.appendChild(name); |
| tr.appendChild(data); |
| tbody.appendChild(tr); |
| info[pair[0]] = data; |
| } |
| this.infoNodes[type] = {table: table, info: info}; |
| } |
| }, |
| |
| /** |
| * Handles key down events and looks for left and right arrows, then |
| * dispatches to the currently expanded item, if any. |
| * @param {Event} e The keydown event. |
| * @private |
| */ |
| handleKeyLeftRight_: function(e) { |
| var id = e.keyIdentifier; |
| if ((id == 'Left' || id == 'Right') && this.expandedItem) { |
| var cs = this.ownerDocument.defaultView.getComputedStyle(this); |
| var rtl = cs.direction == 'rtl'; |
| if ((!rtl && id == 'Left') || (rtl && id == 'Right')) |
| this.expandedItem.selectedIndex--; |
| else |
| this.expandedItem.selectedIndex++; |
| this.scrollIndexIntoView(this.expandedItem.listIndex); |
| // Prevent the page itself from scrolling. |
| e.preventDefault(); |
| } |
| }, |
| |
| /** |
| * Called on selection model selection changes. |
| * @param {Event} ce The selection change event. |
| * @private |
| */ |
| cookieSelectionChange_: function(ce) { |
| ce.changes.forEach(function(change) { |
| var listItem = this.getListItemByIndex(change.index); |
| if (listItem) { |
| if (!change.selected) { |
| // We set a timeout here, rather than setting the item unexpanded |
| // immediately, so that if another item gets set expanded right |
| // away, it will be expanded before this item is unexpanded. It |
| // will notice that, and unexpand this item in sync with its own |
| // expansion. Later, this callback will end up having no effect. |
| window.setTimeout(function() { |
| if (!listItem.selected || !listItem.lead) |
| listItem.expanded = false; |
| }, 0); |
| } else if (listItem.lead) { |
| listItem.expanded = true; |
| } |
| } |
| }, this); |
| }, |
| |
| /** |
| * Called on selection model lead changes. |
| * @param {Event} pe The lead change event. |
| * @private |
| */ |
| cookieLeadChange_: function(pe) { |
| if (pe.oldValue != -1) { |
| var listItem = this.getListItemByIndex(pe.oldValue); |
| if (listItem) { |
| // See cookieSelectionChange_ above for why we use a timeout here. |
| window.setTimeout(function() { |
| if (!listItem.lead || !listItem.selected) |
| listItem.expanded = false; |
| }, 0); |
| } |
| } |
| if (pe.newValue != -1) { |
| var listItem = this.getListItemByIndex(pe.newValue); |
| if (listItem && listItem.selected) |
| listItem.expanded = true; |
| } |
| }, |
| |
| /** |
| * The currently expanded item. Used by CookieListItem above. |
| * @type {?CookieListItem} |
| */ |
| expandedItem: null, |
| |
| // from cr.ui.List |
| /** @override */ |
| createItem: function(data) { |
| // We use the cached expanded item in order to allow it to maintain some |
| // state (like its fixed height, and which bubble is selected). |
| if (this.expandedItem && this.expandedItem.origin == data) |
| return this.expandedItem; |
| return new CookieListItem(data, this); |
| }, |
| |
| // from options.DeletableItemList |
| /** @override */ |
| deleteItemAtIndex: function(index) { |
| var item = this.dataModel.item(index); |
| if (item) { |
| var pathId = item.pathId; |
| if (pathId) |
| chrome.send('removeCookie', [pathId]); |
| } |
| }, |
| |
| /** |
| * Insert the given list of cookie tree nodes at the given index. |
| * Both CookiesList and CookieTreeNode implement this API. |
| * @param {Array.<Object>} data The data objects for the nodes to add. |
| * @param {number} start The index at which to start inserting the nodes. |
| */ |
| insertAt: function(data, start) { |
| spliceTreeNodes(data, start, this.dataModel); |
| }, |
| |
| /** |
| * Remove a cookie tree node from the given index. |
| * Both CookiesList and CookieTreeNode implement this API. |
| * @param {number} index The index of the tree node to remove. |
| */ |
| remove: function(index) { |
| if (index < this.dataModel.length) |
| this.dataModel.splice(index, 1); |
| }, |
| |
| /** |
| * Clears the list. |
| * Both CookiesList and CookieTreeNode implement this API. |
| * It is used by CookiesList.loadChildren(). |
| */ |
| clear: function() { |
| parentLookup = {}; |
| this.dataModel.splice(0, this.dataModel.length); |
| this.redraw(); |
| }, |
| |
| /** |
| * Add tree nodes by given parent. |
| * @param {Object} parent The parent node. |
| * @param {number} start The index at which to start inserting the nodes. |
| * @param {Array} nodesData Nodes data array. |
| * @private |
| */ |
| addByParent_: function(parent, start, nodesData) { |
| if (!parent) |
| return; |
| |
| parent.startBatchUpdates(); |
| parent.insertAt(nodesData, start); |
| parent.endBatchUpdates(); |
| |
| cr.dispatchSimpleEvent(this, 'change'); |
| }, |
| |
| /** |
| * Add tree nodes by parent id. |
| * This is used by cookies_view.js. |
| * @param {string} parentId Id of the parent node. |
| * @param {number} start The index at which to start inserting the nodes. |
| * @param {Array} nodesData Nodes data array. |
| */ |
| addByParentId: function(parentId, start, nodesData) { |
| var parent = parentId ? parentLookup[parentId] : this; |
| this.addByParent_(parent, start, nodesData); |
| }, |
| |
| /** |
| * Removes tree nodes by parent id. |
| * This is used by cookies_view.js. |
| * @param {string} parentId Id of the parent node. |
| * @param {number} start The index at which to start removing the nodes. |
| * @param {number} count Number of nodes to remove. |
| */ |
| removeByParentId: function(parentId, start, count) { |
| var parent = parentId ? parentLookup[parentId] : this; |
| if (!parent) |
| return; |
| |
| parent.startBatchUpdates(); |
| while (count-- > 0) |
| parent.remove(start); |
| parent.endBatchUpdates(); |
| |
| cr.dispatchSimpleEvent(this, 'change'); |
| }, |
| |
| /** |
| * Loads the immediate children of given parent node. |
| * This is used by cookies_view.js. |
| * @param {string} parentId Id of the parent node. |
| * @param {Array} children The immediate children of parent node. |
| */ |
| loadChildren: function(parentId, children) { |
| if (parentId) |
| delete lookupRequests[parentId]; |
| var parent = parentId ? parentLookup[parentId] : this; |
| if (!parent) |
| return; |
| |
| parent.startBatchUpdates(); |
| parent.clear(); |
| this.addByParent_(parent, 0, children); |
| parent.endBatchUpdates(); |
| }, |
| }; |
| |
| return { |
| CookiesList: CookiesList |
| }; |
| }); |