CLOSED TREE: TraceMonkey merge head. (a=blockers)
[mozilla-central.git] / browser / components / preferences / cookies.js
blob635fc0c8dd5844f05e9d7595dcda491c287e7354
1 # -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 # ***** BEGIN LICENSE BLOCK *****
3 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 # The contents of this file are subject to the Mozilla Public License Version
6 # 1.1 (the "License"); you may not use this file except in compliance with
7 # the License. You may obtain a copy of the License at
8 # http://www.mozilla.org/MPL/
10 # Software distributed under the License is distributed on an "AS IS" basis,
11 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 # for the specific language governing rights and limitations under the
13 # License.
15 # The Original Code is the Firefox Preferences System.
17 # The Initial Developer of the Original Code is
18 # Ben Goodger.
19 # Portions created by the Initial Developer are Copyright (C) 2005
20 # the Initial Developer. All Rights Reserved.
22 # Contributor(s):
23 #   Ben Goodger <ben@mozilla.org>
24 #   Ehsan Akhgari <ehsan.akhgari@gmail.com>
26 # Alternatively, the contents of this file may be used under the terms of
27 # either the GNU General Public License Version 2 or later (the "GPL"), or
28 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 # in which case the provisions of the GPL or the LGPL are applicable instead
30 # of those above. If you wish to allow use of your version of this file only
31 # under the terms of either the GPL or the LGPL, and not to allow others to
32 # use your version of this file under the terms of the MPL, indicate your
33 # decision by deleting the provisions above and replace them with the notice
34 # and other provisions required by the GPL or the LGPL. If you do not delete
35 # the provisions above, a recipient may use your version of this file under
36 # the terms of any one of the MPL, the GPL or the LGPL.
38 # ***** END LICENSE BLOCK *****
40 const nsICookie = Components.interfaces.nsICookie;
42 var gCookiesWindow = {
43   _cm               : Components.classes["@mozilla.org/cookiemanager;1"]
44                                 .getService(Components.interfaces.nsICookieManager),
45   _ds               : Components.classes["@mozilla.org/intl/scriptabledateformat;1"]
46                                 .getService(Components.interfaces.nsIScriptableDateFormat),
47   _hosts            : {},
48   _hostOrder        : [],
49   _tree             : null,
50   _bundle           : null,
52   init: function () {
53     var os = Components.classes["@mozilla.org/observer-service;1"]
54                        .getService(Components.interfaces.nsIObserverService);
55     os.addObserver(this, "cookie-changed", false);
56     os.addObserver(this, "perm-changed", false);
58     this._bundle = document.getElementById("bundlePreferences");
59     this._tree = document.getElementById("cookiesList");
61     this._populateList(true);
63     document.getElementById("filter").focus();
64   },
66   uninit: function () {
67     var os = Components.classes["@mozilla.org/observer-service;1"]
68                        .getService(Components.interfaces.nsIObserverService);
69     os.removeObserver(this, "cookie-changed");
70     os.removeObserver(this, "perm-changed");
71   },
73   _populateList: function (aInitialLoad) {
74     this._loadCookies();
75     this._tree.treeBoxObject.view = this._view;
76     if (aInitialLoad)
77       this.sort("rawHost");
78     if (this._view.rowCount > 0)
79       this._tree.view.selection.select(0);
81     if (aInitialLoad) {
82       if ("arguments" in window &&
83           window.arguments[0] &&
84           window.arguments[0].filterString)
85         this.setFilter(window.arguments[0].filterString);
86     }
87     else {
88       if (document.getElementById("filter").value != "")
89         this.filter();
90     }
92     this._saveState();
93   },
95   _cookieEquals: function (aCookieA, aCookieB, aStrippedHost) {
96     return aCookieA.rawHost == aStrippedHost &&
97            aCookieA.name == aCookieB.name &&
98            aCookieA.path == aCookieB.path;
99   },
101   observe: function (aCookie, aTopic, aData) {
102     if (aTopic != "cookie-changed")
103       return;
105     if (aCookie instanceof Components.interfaces.nsICookie) {
106       var strippedHost = this._makeStrippedHost(aCookie.host);
107       if (aData == "changed")
108         this._handleCookieChanged(aCookie, strippedHost);
109       else if (aData == "added")
110         this._handleCookieAdded(aCookie, strippedHost);
111     }
112     else if (aData == "cleared") {
113       this._hosts = {};
114       this._hostOrder = [];
116       var oldRowCount = this._view._rowCount;
117       this._view._rowCount = 0;
118       this._tree.treeBoxObject.rowCountChanged(0, -oldRowCount);
119       this._view.selection.clearSelection();
120     }
121     else if (aData == "reload") {
122       // first, clear any existing entries
123       this.observe(aCookie, aTopic, "cleared");
125       // then, reload the list
126       this._populateList(false);
127     }
129     // We don't yet handle aData == "deleted" - it's a less common case
130     // and is rather complicated as selection tracking is difficult
131   },
133   _handleCookieChanged: function (changedCookie, strippedHost) {
134     var rowIndex = 0;
135     var cookieItem = null;
136     if (!this._view._filtered) {
137       for (var i = 0; i < this._hostOrder.length; ++i) { // (var host in this._hosts) {
138         ++rowIndex;
139         var hostItem = this._hosts[this._hostOrder[i]]; // var hostItem = this._hosts[host];
140         if (this._hostOrder[i] == strippedHost) { // host == strippedHost) {
141           // Host matches, look for the cookie within this Host collection
142           // and update its data
143           for (var j = 0; j < hostItem.cookies.length; ++j) {
144             ++rowIndex;
145             var currCookie = hostItem.cookies[j];
146             if (this._cookieEquals(currCookie, changedCookie, strippedHost)) {
147               currCookie.value    = changedCookie.value;
148               currCookie.isSecure = changedCookie.isSecure;
149               currCookie.isDomain = changedCookie.isDomain;
150               currCookie.expires  = changedCookie.expires;
151               cookieItem = currCookie;
152               break;
153             }
154           }
155         }
156         else if (hostItem.open)
157           rowIndex += hostItem.cookies.length;
158       }
159     }
160     else {
161       // Just walk the filter list to find the item. It doesn't matter that
162       // we don't update the main Host collection when we do this, because
163       // when the filter is reset the Host collection is rebuilt anyway.
164       for (rowIndex = 0; rowIndex < this._view._filterSet.length; ++rowIndex) {
165         currCookie = this._view._filterSet[rowIndex];
166         if (this._cookieEquals(currCookie, changedCookie, strippedHost)) {
167           currCookie.value    = changedCookie.value;
168           currCookie.isSecure = changedCookie.isSecure;
169           currCookie.isDomain = changedCookie.isDomain;
170           currCookie.expires  = changedCookie.expires;
171           cookieItem = currCookie;
172           break;
173         }
174       }
175     }
177     // Make sure the tree display is up to date...
178     this._tree.treeBoxObject.invalidateRow(rowIndex);
179     // ... and if the cookie is selected, update the displayed metadata too
180     if (cookieItem != null && this._view.selection.currentIndex == rowIndex)
181       this._updateCookieData(cookieItem);
182   },
184   _handleCookieAdded: function (changedCookie, strippedHost) {
185     var rowCountImpact = 0;
186     var addedHost = { value: 0 };
187     this._addCookie(strippedHost, changedCookie, addedHost);
188     if (!this._view._filtered) {
189       // The Host collection for this cookie already exists, and it's not open,
190       // so don't increment the rowCountImpact becaues the user is not going to
191       // see the additional rows as they're hidden.
192       if (addedHost.value || this._hosts[strippedHost].open)
193         ++rowCountImpact;
194     }
195     else {
196       // We're in search mode, and the cookie being added matches
197       // the search condition, so add it to the list.
198       var c = this._makeCookieObject(strippedHost, changedCookie);
199       if (this._cookieMatchesFilter(c)) {
200         this._view._filterSet.push(this._makeCookieObject(strippedHost, changedCookie));
201         ++rowCountImpact;
202       }
203     }
204     // Now update the tree display at the end (we could/should re run the sort
205     // if any to get the position correct.)
206     var oldRowCount = this._rowCount;
207     this._view._rowCount += rowCountImpact;
208     this._tree.treeBoxObject.rowCountChanged(oldRowCount - 1, rowCountImpact);
210     document.getElementById("removeAllCookies").disabled = this._view._filtered;
211   },
213   _view: {
214     _filtered   : false,
215     _filterSet  : [],
216     _filterValue: "",
217     _rowCount   : 0,
218     _cacheValid : 0,
219     _cacheItems : [],
220     get rowCount() {
221       return this._rowCount;
222     },
224     _getItemAtIndex: function (aIndex) {
225       if (this._filtered)
226         return this._filterSet[aIndex];
228       var start = 0;
229       var count = 0, hostIndex = 0;
231       var cacheIndex = Math.min(this._cacheValid, aIndex);
232       if (cacheIndex > 0) {
233         var cacheItem = this._cacheItems[cacheIndex];
234         start = cacheItem['start'];
235         count = hostIndex = cacheItem['count'];
236       }
238       for (var i = start; i < gCookiesWindow._hostOrder.length; ++i) { // var host in gCookiesWindow._hosts) {
239         var currHost = gCookiesWindow._hosts[gCookiesWindow._hostOrder[i]]; // gCookiesWindow._hosts[host];
240         if (!currHost) continue;
241         if (count == aIndex)
242           return currHost;
243         hostIndex = count;
245         var cacheEntry = { 'start' : i, 'count' : count };
246         var cacheStart = count;
248         if (currHost.open) {
249           if (count < aIndex && aIndex <= (count + currHost.cookies.length)) {
250             // We are looking for an entry within this host's children,
251             // enumerate them looking for the index.
252             ++count;
253             for (var i = 0; i < currHost.cookies.length; ++i) {
254               if (count == aIndex) {
255                 var cookie = currHost.cookies[i];
256                 cookie.parentIndex = hostIndex;
257                 return cookie;
258               }
259               ++count;
260             }
261           }
262           else {
263             // A host entry was open, but we weren't looking for an index
264             // within that host entry's children, so skip forward over the
265             // entry's children. We need to add one to increment for the
266             // host value too.
267             count += currHost.cookies.length + 1;
268           }
269         }
270         else
271           ++count;
273         for (var j = cacheStart; j < count; j++)
274           this._cacheItems[j] = cacheEntry;
275         this._cacheValid = count - 1;
276       }
277       return null;
278     },
280     _removeItemAtIndex: function (aIndex, aCount) {
281       var removeCount = aCount === undefined ? 1 : aCount;
282       if (this._filtered) {
283         // remove the cookies from the unfiltered set so that they
284         // don't reappear when the filter is changed. See bug 410863.
285         for (var i = aIndex; i < aIndex + removeCount; ++i) {
286           var item = this._filterSet[i];
287           var parent = gCookiesWindow._hosts[item.rawHost];
288           for (var j = 0; j < parent.cookies.length; ++j) {
289             if (item == parent.cookies[j]) {
290               parent.cookies.splice(j, 1);
291               break;
292             }
293           }
294         }
295         this._filterSet.splice(aIndex, removeCount);
296         return;
297       }
299       var item = this._getItemAtIndex(aIndex);
300       if (!item) return;
301       this._invalidateCache(aIndex - 1);
302       if (item.container)
303         gCookiesWindow._hosts[item.rawHost] = null;
304       else {
305         var parent = this._getItemAtIndex(item.parentIndex);
306         for (var i = 0; i < parent.cookies.length; ++i) {
307           var cookie = parent.cookies[i];
308           if (item.rawHost == cookie.rawHost &&
309               item.name == cookie.name && item.path == cookie.path)
310             parent.cookies.splice(i, removeCount);
311         }
312       }
313     },
315     _invalidateCache: function (aIndex) {
316       this._cacheValid = Math.min(this._cacheValid, aIndex);
317     },
319     getCellText: function (aIndex, aColumn) {
320       if (!this._filtered) {
321         var item = this._getItemAtIndex(aIndex);
322         if (!item)
323           return "";
324         if (aColumn.id == "domainCol")
325           return item.rawHost;
326         else if (aColumn.id == "nameCol")
327           return item.name;
328       }
329       else {
330         if (aColumn.id == "domainCol")
331           return this._filterSet[aIndex].rawHost;
332         else if (aColumn.id == "nameCol")
333           return this._filterSet[aIndex].name;
334       }
335       return "";
336     },
338     _selection: null,
339     get selection () { return this._selection; },
340     set selection (val) { this._selection = val; return val; },
341     getRowProperties: function (aIndex, aProperties) {},
342     getCellProperties: function (aIndex, aColumn, aProperties) {},
343     getColumnProperties: function (aColumn, aProperties) {},
344     isContainer: function (aIndex) {
345       if (!this._filtered) {
346         var item = this._getItemAtIndex(aIndex);
347         if (!item) return false;
348         return item.container;
349       }
350       return false;
351     },
352     isContainerOpen: function (aIndex) {
353       if (!this._filtered) {
354         var item = this._getItemAtIndex(aIndex);
355         if (!item) return false;
356         return item.open;
357       }
358       return false;
359     },
360     isContainerEmpty: function (aIndex) {
361       if (!this._filtered) {
362         var item = this._getItemAtIndex(aIndex);
363         if (!item) return false;
364         return item.cookies.length == 0;
365       }
366       return false;
367     },
368     isSeparator: function (aIndex) { return false; },
369     isSorted: function (aIndex) { return false; },
370     canDrop: function (aIndex, aOrientation) { return false; },
371     drop: function (aIndex, aOrientation) {},
372     getParentIndex: function (aIndex) {
373       if (!this._filtered) {
374         var item = this._getItemAtIndex(aIndex);
375         // If an item has no parent index (i.e. it is at the top level) this
376         // function MUST return -1 otherwise we will go into an infinite loop.
377         // Containers are always top level items in the cookies tree, so make
378         // sure to return the appropriate value here.
379         if (!item || item.container) return -1;
380         return item.parentIndex;
381       }
382       return -1;
383     },
384     hasNextSibling: function (aParentIndex, aIndex) {
385       if (!this._filtered) {
386         // |aParentIndex| appears to be bogus, but we can get the real
387         // parent index by getting the entry for |aIndex| and reading the
388         // parentIndex field.
389         // The index of the last item in this host collection is the
390         // index of the parent + the size of the host collection, and
391         // aIndex has a next sibling if it is less than this value.
392         var item = this._getItemAtIndex(aIndex);
393         if (item) {
394           if (item.container) {
395             for (var i = aIndex + 1; i < this.rowCount; ++i) {
396               var subsequent = this._getItemAtIndex(i);
397               if (subsequent.container)
398                 return true;
399             }
400             return false;
401           }
402           else {
403             var parent = this._getItemAtIndex(item.parentIndex);
404             if (parent && parent.container)
405               return aIndex < item.parentIndex + parent.cookies.length;
406           }
407         }
408       }
409       return aIndex < this.rowCount - 1;
410     },
411     hasPreviousSibling: function (aIndex) {
412       if (!this._filtered) {
413         var item = this._getItemAtIndex(aIndex);
414         if (!item) return false;
415         var parent = this._getItemAtIndex(item.parentIndex);
416         if (parent && parent.container)
417           return aIndex > item.parentIndex + 1;
418       }
419       return aIndex > 0;
420     },
421     getLevel: function (aIndex) {
422       if (!this._filtered) {
423         var item = this._getItemAtIndex(aIndex);
424         if (!item) return 0;
425         return item.level;
426       }
427       return 0;
428     },
429     getImageSrc: function (aIndex, aColumn) {},
430     getProgressMode: function (aIndex, aColumn) {},
431     getCellValue: function (aIndex, aColumn) {},
432     setTree: function (aTree) {},
433     toggleOpenState: function (aIndex) {
434       if (!this._filtered) {
435         var item = this._getItemAtIndex(aIndex);
436         if (!item) return;
437         this._invalidateCache(aIndex);
438         var multiplier = item.open ? -1 : 1;
439         var delta = multiplier * item.cookies.length;
440         this._rowCount += delta;
441         item.open = !item.open;
442         gCookiesWindow._tree.treeBoxObject.rowCountChanged(aIndex + 1, delta);
443         gCookiesWindow._tree.treeBoxObject.invalidateRow(aIndex);
444       }
445     },
446     cycleHeader: function (aColumn) {},
447     selectionChanged: function () {},
448     cycleCell: function (aIndex, aColumn) {},
449     isEditable: function (aIndex, aColumn) {
450       return false;
451     },
452     isSelectable: function (aIndex, aColumn) {
453       return false;
454     },
455     setCellValue: function (aIndex, aColumn, aValue) {},
456     setCellText: function (aIndex, aColumn, aValue) {},
457     performAction: function (aAction) {},
458     performActionOnRow: function (aAction, aIndex) {},
459     performActionOnCell: function (aAction, aindex, aColumn) {}
460   },
462   _makeStrippedHost: function (aHost) {
463     var formattedHost = aHost.charAt(0) == "." ? aHost.substring(1, aHost.length) : aHost;
464     return formattedHost.substring(0, 4) == "www." ? formattedHost.substring(4, formattedHost.length) : formattedHost;
465   },
467   _addCookie: function (aStrippedHost, aCookie, aHostCount) {
468     if (!(aStrippedHost in this._hosts) || !this._hosts[aStrippedHost]) {
469       this._hosts[aStrippedHost] = { cookies   : [],
470                                      rawHost   : aStrippedHost,
471                                      level     : 0,
472                                      open      : false,
473                                      container : true };
474       this._hostOrder.push(aStrippedHost);
475       ++aHostCount.value;
476     }
478     var c = this._makeCookieObject(aStrippedHost, aCookie);
479     this._hosts[aStrippedHost].cookies.push(c);
480   },
482   _makeCookieObject: function (aStrippedHost, aCookie) {
483     var host = aCookie.host;
484     var formattedHost = host.charAt(0) == "." ? host.substring(1, host.length) : host;
485     var c = { name        : aCookie.name,
486               value       : aCookie.value,
487               isDomain    : aCookie.isDomain,
488               host        : aCookie.host,
489               rawHost     : aStrippedHost,
490               path        : aCookie.path,
491               isSecure    : aCookie.isSecure,
492               expires     : aCookie.expires,
493               level       : 1,
494               container   : false };
495     return c;
496   },
498   _loadCookies: function () {
499     var e = this._cm.enumerator;
500     var hostCount = { value: 0 };
501     this._hosts = {};
502     this._hostOrder = [];
503     while (e.hasMoreElements()) {
504       var cookie = e.getNext();
505       if (cookie && cookie instanceof Components.interfaces.nsICookie) {
506         var strippedHost = this._makeStrippedHost(cookie.host);
507         this._addCookie(strippedHost, cookie, hostCount);
508       }
509       else
510         break;
511     }
512     this._view._rowCount = hostCount.value;
513   },
515   formatExpiresString: function (aExpires) {
516     if (aExpires) {
517       var date = new Date(1000 * aExpires);
518       return this._ds.FormatDateTime("", this._ds.dateFormatLong,
519                                      this._ds.timeFormatSeconds,
520                                      date.getFullYear(),
521                                      date.getMonth() + 1,
522                                      date.getDate(),
523                                      date.getHours(),
524                                      date.getMinutes(),
525                                      date.getSeconds());
526     }
527     return this._bundle.getString("expireAtEndOfSession");
528   },
530   _updateCookieData: function (aItem) {
531     var seln = this._view.selection;
532     var ids = ["name", "value", "host", "path", "isSecure", "expires"];
533     var properties;
535     if (aItem && !aItem.container && seln.count > 0) {
536       properties = { name: aItem.name, value: aItem.value, host: aItem.host,
537                      path: aItem.path, expires: this.formatExpiresString(aItem.expires),
538                      isDomain: aItem.isDomain ? this._bundle.getString("domainColon")
539                                               : this._bundle.getString("hostColon"),
540                      isSecure: aItem.isSecure ? this._bundle.getString("forSecureOnly")
541                                               : this._bundle.getString("forAnyConnection") };
542       for (var i = 0; i < ids.length; ++i)
543         document.getElementById(ids[i]).disabled = false;
544     }
545     else {
546       var noneSelected = this._bundle.getString("noCookieSelected");
547       properties = { name: noneSelected, value: noneSelected, host: noneSelected,
548                      path: noneSelected, expires: noneSelected,
549                      isSecure: noneSelected };
550       for (i = 0; i < ids.length; ++i)
551         document.getElementById(ids[i]).disabled = true;
552     }
553     for (var property in properties)
554       document.getElementById(property).value = properties[property];
555   },
557   onCookieSelected: function () {
558     var properties, item;
559     var seln = this._tree.view.selection;
560     if (!this._view._filtered)
561       item = this._view._getItemAtIndex(seln.currentIndex);
562     else
563       item = this._view._filterSet[seln.currentIndex];
565     this._updateCookieData(item);
567     var rangeCount = seln.getRangeCount();
568     var selectedCookieCount = 0;
569     for (var i = 0; i < rangeCount; ++i) {
570       var min = {}; var max = {};
571       seln.getRangeAt(i, min, max);
572       for (var j = min.value; j <= max.value; ++j) {
573         item = this._view._getItemAtIndex(j);
574         if (!item) continue;
575         if (item.container && !item.open)
576           selectedCookieCount += item.cookies.length;
577         else if (!item.container)
578           ++selectedCookieCount;
579       }
580     }
581     var item = this._view._getItemAtIndex(seln.currentIndex);
582     if (item && seln.count == 1 && item.container && item.open)
583       selectedCookieCount += 2;
585     var removeCookie = document.getElementById("removeCookie");
586     var removeCookies = document.getElementById("removeCookies");
587     removeCookie.parentNode.selectedPanel =
588       selectedCookieCount == 1 ? removeCookie : removeCookies;
590     document.getElementById("removeAllCookies").disabled = this._view._filtered;
591     removeCookie.disabled = removeCookies.disabled = !(seln.count > 0);
592   },
594   deleteCookie: function () {
595 #   // Selection Notes
596 #   // - Selection always moves to *NEXT* adjacent item unless item
597 #   //   is last child at a given level in which case it moves to *PREVIOUS*
598 #   //   item
599 #   //
600 #   // Selection Cases (Somewhat Complicated)
601 #   //
602 #   // 1) Single cookie selected, host has single child
603 #   //    v cnn.com
604 #   //    //// cnn.com ///////////// goksdjf@ ////
605 #   //    > atwola.com
606 #   //
607 #   //    Before SelectedIndex: 1   Before RowCount: 3
608 #   //    After  SelectedIndex: 0   After  RowCount: 1
609 #   //
610 #   // 2) Host selected, host open
611 #   //    v goats.com ////////////////////////////
612 #   //         goats.com             sldkkfjl
613 #   //         goat.scom             flksj133
614 #   //    > atwola.com
615 #   //
616 #   //    Before SelectedIndex: 0   Before RowCount: 4
617 #   //    After  SelectedIndex: 0   After  RowCount: 1
618 #   //
619 #   // 3) Host selected, host closed
620 #   //    > goats.com ////////////////////////////
621 #   //    > atwola.com
622 #   //
623 #   //    Before SelectedIndex: 0   Before RowCount: 2
624 #   //    After  SelectedIndex: 0   After  RowCount: 1
625 #   //
626 #   // 4) Single cookie selected, host has many children
627 #   //    v goats.com
628 #   //         goats.com             sldkkfjl
629 #   //    //// goats.com /////////// flksjl33 ////
630 #   //    > atwola.com
631 #   //
632 #   //    Before SelectedIndex: 2   Before RowCount: 4
633 #   //    After  SelectedIndex: 1   After  RowCount: 3
634 #   //
635 #   // 5) Single cookie selected, host has many children
636 #   //    v goats.com
637 #   //    //// goats.com /////////// flksjl33 ////
638 #   //         goats.com             sldkkfjl
639 #   //    > atwola.com
640 #   //
641 #   //    Before SelectedIndex: 1   Before RowCount: 4
642 #   //    After  SelectedIndex: 1   After  RowCount: 3
643     var seln = this._view.selection;
644     var tbo = this._tree.treeBoxObject;
646     if (seln.count < 1) return;
648     var nextSelected = 0;
649     var rowCountImpact = 0;
650     var deleteItems = [];
651     if (!this._view._filtered) {
652       var ci = seln.currentIndex;
653       nextSelected = ci;
654       var invalidateRow = -1;
655       var item = this._view._getItemAtIndex(ci);
656       if (item.container) {
657         rowCountImpact -= (item.open ? item.cookies.length : 0) + 1;
658         deleteItems = deleteItems.concat(item.cookies);
659         if (!this._view.hasNextSibling(-1, ci))
660           --nextSelected;
661         this._view._removeItemAtIndex(ci);
662       }
663       else {
664         var parent = this._view._getItemAtIndex(item.parentIndex);
665         --rowCountImpact;
666         if (parent.cookies.length == 1) {
667           --rowCountImpact;
668           deleteItems.push(item);
669           if (!this._view.hasNextSibling(-1, ci))
670             --nextSelected;
671           if (!this._view.hasNextSibling(-1, item.parentIndex))
672             --nextSelected;
673           this._view._removeItemAtIndex(item.parentIndex);
674           invalidateRow = item.parentIndex;
675         }
676         else {
677           deleteItems.push(item);
678           if (!this._view.hasNextSibling(-1, ci))
679             --nextSelected;
680           this._view._removeItemAtIndex(ci);
681         }
682       }
683       this._view._rowCount += rowCountImpact;
684       tbo.rowCountChanged(ci, rowCountImpact);
685       if (invalidateRow != -1)
686         tbo.invalidateRow(invalidateRow);
687     }
688     else {
689       var rangeCount = seln.getRangeCount();
690       for (var i = 0; i < rangeCount; ++i) {
691         var min = {}; var max = {};
692         seln.getRangeAt(i, min, max);
693         nextSelected = min.value;
694         for (var j = min.value; j <= max.value; ++j) {
695           deleteItems.push(this._view._getItemAtIndex(j));
696           if (!this._view.hasNextSibling(-1, max.value))
697             --nextSelected;
698         }
699         var delta = max.value - min.value + 1;
700         this._view._removeItemAtIndex(min.value, delta);
701         rowCountImpact = -1 * delta;
702         this._view._rowCount += rowCountImpact;
703         tbo.rowCountChanged(min.value, rowCountImpact);
704       }
705     }
707     var psvc = Components.classes["@mozilla.org/preferences-service;1"]
708                          .getService(Components.interfaces.nsIPrefBranch);
709     var blockFutureCookies = false;
710     if (psvc.prefHasUserValue("network.cookie.blockFutureCookies"))
711       blockFutureCookies = psvc.getBoolPref("network.cookie.blockFutureCookies");
712     for (i = 0; i < deleteItems.length; ++i) {
713       var item = deleteItems[i];
714       this._cm.remove(item.host, item.name, item.path, blockFutureCookies);
715     }
717     if (nextSelected < 0)
718       seln.clearSelection();
719     else {
720       seln.select(nextSelected);
721       this._tree.focus();
722     }
723   },
725   deleteAllCookies: function () {
726     this._cm.removeAll();
727     this._tree.focus();
728   },
730   onCookieKeyPress: function (aEvent) {
731     if (aEvent.keyCode == 46)
732       this.deleteCookie();
733   },
735   _lastSortProperty : "",
736   _lastSortAscending: false,
737   sort: function (aProperty) {
738     var ascending = (aProperty == this._lastSortProperty) ? !this._lastSortAscending : true;
739     // Sort the Non-Filtered Host Collections
740     if (aProperty == "rawHost") {
741       function sortByHost(a, b) {
742         return a.toLowerCase().localeCompare(b.toLowerCase());
743       }
744       this._hostOrder.sort(sortByHost);
745       if (!ascending)
746         this._hostOrder.reverse();
747     }
749     function sortByProperty(a, b) {
750       return a[aProperty].toLowerCase().localeCompare(b[aProperty].toLowerCase());
751     }
752     for (var host in this._hosts) {
753       var cookies = this._hosts[host].cookies;
754       cookies.sort(sortByProperty);
755       if (!ascending)
756         cookies.reverse();
757     }
758     // Sort the Filtered List, if in Filtered mode
759     if (this._view._filtered) {
760       this._view._filterSet.sort(sortByProperty);
761       if (!ascending)
762         this._view._filterSet.reverse();
763     }
765     // Adjust the Sort Indicator
766     var domainCol = document.getElementById("domainCol");
767     var nameCol = document.getElementById("nameCol");
768     var sortOrderString = ascending ? "ascending" : "descending";
769     if (aProperty == "rawHost") {
770       domainCol.setAttribute("sortDirection", sortOrderString);
771       nameCol.removeAttribute("sortDirection");
772     }
773     else {
774       nameCol.setAttribute("sortDirection", sortOrderString);
775       domainCol.removeAttribute("sortDirection");
776     }
778     this._view._invalidateCache(0);
779     this._view.selection.clearSelection();
780     this._view.selection.select(0);
781     this._tree.treeBoxObject.invalidate();
782     this._tree.treeBoxObject.ensureRowIsVisible(0);
784     this._lastSortAscending = ascending;
785     this._lastSortProperty = aProperty;
786   },
788   clearFilter: function () {
789     // Revert to single-select in the tree
790     this._tree.setAttribute("seltype", "single");
792     // Clear the Tree Display
793     this._view._filtered = false;
794     this._view._rowCount = 0;
795     this._tree.treeBoxObject.rowCountChanged(0, -this._view._filterSet.length);
796     this._view._filterSet = [];
798     // Just reload the list to make sure deletions are respected
799     this._loadCookies();
800     this._tree.treeBoxObject.view = this._view;
802     // Restore sort order
803     var sortby = this._lastSortProperty;
804     if (sortby == "") {
805       this._lastSortAscending = false;
806       this.sort("rawHost");
807     }
808     else {
809       this._lastSortAscending = !this._lastSortAscending;
810       this.sort(sortby);
811     }
813     // Restore open state
814     for (var i = 0; i < this._openIndices.length; ++i)
815       this._view.toggleOpenState(this._openIndices[i]);
816     this._openIndices = [];
818     // Restore selection
819     this._view.selection.clearSelection();
820     for (i = 0; i < this._lastSelectedRanges.length; ++i) {
821       var range = this._lastSelectedRanges[i];
822       this._view.selection.rangedSelect(range.min, range.max, true);
823     }
824     this._lastSelectedRanges = [];
826     document.getElementById("cookiesIntro").value = this._bundle.getString("cookiesAll");
827   },
829   _cookieMatchesFilter: function (aCookie) {
830     return aCookie.rawHost.indexOf(this._view._filterValue) != -1 ||
831            aCookie.name.indexOf(this._view._filterValue) != -1 ||
832            aCookie.value.indexOf(this._view._filterValue) != -1;
833   },
835   _filterCookies: function (aFilterValue) {
836     this._view._filterValue = aFilterValue;
837     var cookies = [];
838     for (var i = 0; i < gCookiesWindow._hostOrder.length; ++i) { //var host in gCookiesWindow._hosts) {
839       var currHost = gCookiesWindow._hosts[gCookiesWindow._hostOrder[i]]; // gCookiesWindow._hosts[host];
840       if (!currHost) continue;
841       for (var j = 0; j < currHost.cookies.length; ++j) {
842         var cookie = currHost.cookies[j];
843         if (this._cookieMatchesFilter(cookie))
844           cookies.push(cookie);
845       }
846     }
847     return cookies;
848   },
850   _lastSelectedRanges: [],
851   _openIndices: [],
852   _saveState: function () {
853     // Save selection
854     var seln = this._view.selection;
855     this._lastSelectedRanges = [];
856     var rangeCount = seln.getRangeCount();
857     for (var i = 0; i < rangeCount; ++i) {
858       var min = {}; var max = {};
859       seln.getRangeAt(i, min, max);
860       this._lastSelectedRanges.push({ min: min.value, max: max.value });
861     }
863     // Save open states
864     this._openIndices = [];
865     for (i = 0; i < this._view.rowCount; ++i) {
866       var item = this._view._getItemAtIndex(i);
867       if (item && item.container && item.open)
868         this._openIndices.push(i);
869     }
870   },
872   filter: function () {
873     var filter = document.getElementById("filter").value;
874     if (filter == "") {
875       gCookiesWindow.clearFilter();
876       return;
877     }
878     var view = gCookiesWindow._view;
879     view._filterSet = gCookiesWindow._filterCookies(filter);
880     if (!view._filtered) {
881       // Save Display Info for the Non-Filtered mode when we first
882       // enter Filtered mode.
883       gCookiesWindow._saveState();
884       view._filtered = true;
885     }
886     // Move to multi-select in the tree
887     gCookiesWindow._tree.setAttribute("seltype", "multiple");
889     // Clear the display
890     var oldCount = view._rowCount;
891     view._rowCount = 0;
892     gCookiesWindow._tree.treeBoxObject.rowCountChanged(0, -oldCount);
893     // Set up the filtered display
894     view._rowCount = view._filterSet.length;
895     gCookiesWindow._tree.treeBoxObject.rowCountChanged(0, view.rowCount);
897     // if the view is not empty then select the first item
898     if (view.rowCount > 0)
899       view.selection.select(0);
901     document.getElementById("cookiesIntro").value = gCookiesWindow._bundle.getString("cookiesFiltered");
902   },
904   setFilter: function (aFilterString) {
905     document.getElementById("filter").value = aFilterString;
906     this.filter();
907   },
909   focusFilterBox: function () {
910     var filter = document.getElementById("filter");
911     filter.focus();
912     filter.select();
913   }