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