1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5if (!chrome.cookies) {
6  chrome.cookies = chrome.experimental.cookies;
7}
8
9// A simple Timer class.
10function Timer() {
11  this.start_ = new Date();
12
13  this.elapsed = function() {
14    return (new Date()) - this.start_;
15  }
16
17  this.reset = function() {
18    this.start_ = new Date();
19  }
20}
21
22// Compares cookies for "key" (name, domain, etc.) equality, but not "value"
23// equality.
24function cookieMatch(c1, c2) {
25  return (c1.name == c2.name) && (c1.domain == c2.domain) &&
26         (c1.hostOnly == c2.hostOnly) && (c1.path == c2.path) &&
27         (c1.secure == c2.secure) && (c1.httpOnly == c2.httpOnly) &&
28         (c1.session == c2.session) && (c1.storeId == c2.storeId);
29}
30
31// Returns an array of sorted keys from an associative array.
32function sortedKeys(array) {
33  var keys = [];
34  for (var i in array) {
35    keys.push(i);
36  }
37  keys.sort();
38  return keys;
39}
40
41// Shorthand for document.querySelector.
42function select(selector) {
43  return document.querySelector(selector);
44}
45
46// An object used for caching data about the browser's cookies, which we update
47// as notifications come in.
48function CookieCache() {
49  this.cookies_ = {};
50
51  this.reset = function() {
52    this.cookies_ = {};
53  }
54
55  this.add = function(cookie) {
56    var domain = cookie.domain;
57    if (!this.cookies_[domain]) {
58      this.cookies_[domain] = [];
59    }
60    this.cookies_[domain].push(cookie);
61  };
62
63  this.remove = function(cookie) {
64    var domain = cookie.domain;
65    if (this.cookies_[domain]) {
66      var i = 0;
67      while (i < this.cookies_[domain].length) {
68        if (cookieMatch(this.cookies_[domain][i], cookie)) {
69          this.cookies_[domain].splice(i, 1);
70        } else {
71          i++;
72        }
73      }
74      if (this.cookies_[domain].length == 0) {
75        delete this.cookies_[domain];
76      }
77    }
78  };
79
80  // Returns a sorted list of cookie domains that match |filter|. If |filter| is
81  //  null, returns all domains.
82  this.getDomains = function(filter) {
83    var result = [];
84    sortedKeys(this.cookies_).forEach(function(domain) {
85      if (!filter || domain.indexOf(filter) != -1) {
86        result.push(domain);
87      }
88    });
89    return result;
90  }
91
92  this.getCookies = function(domain) {
93    return this.cookies_[domain];
94  };
95}
96
97
98var cache = new CookieCache();
99
100
101function removeAllForFilter() {
102  var filter = select("#filter").value;
103  var timer = new Timer();
104  cache.getDomains(filter).forEach(function(domain) {
105    removeCookiesForDomain(domain);
106  });
107}
108
109function removeAll() {
110  var all_cookies = [];
111  cache.getDomains().forEach(function(domain) {
112    cache.getCookies(domain).forEach(function(cookie) {
113      all_cookies.push(cookie);
114    });
115  });
116  cache.reset();
117  var count = all_cookies.length;
118  var timer = new Timer();
119  for (var i = 0; i < count; i++) {
120    removeCookie(all_cookies[i]);
121  }
122  timer.reset();
123  chrome.cookies.getAll({}, function(cookies) {
124    for (var i in cookies) {
125      cache.add(cookies[i]);
126      removeCookie(cookies[i]);
127    }
128  });
129}
130
131function removeCookie(cookie) {
132  var url = "http" + (cookie.secure ? "s" : "") + "://" + cookie.domain +
133            cookie.path;
134  chrome.cookies.remove({"url": url, "name": cookie.name});
135}
136
137function removeCookiesForDomain(domain) {
138  var timer = new Timer();
139  cache.getCookies(domain).forEach(function(cookie) {
140    removeCookie(cookie);
141  });
142}
143
144function resetTable() {
145  var table = select("#cookies");
146  while (table.rows.length > 1) {
147    table.deleteRow(table.rows.length - 1);
148  }
149}
150
151var reload_scheduled = false;
152
153function scheduleReloadCookieTable() {
154  if (!reload_scheduled) {
155    reload_scheduled = true;
156    setTimeout(reloadCookieTable, 250);
157  }
158}
159
160function reloadCookieTable() {
161  reload_scheduled = false;
162
163  var filter = select("#filter").value;
164
165  var domains = cache.getDomains(filter);
166
167  select("#filter_count").innerText = domains.length;
168  select("#total_count").innerText = cache.getDomains().length;
169
170  select("#delete_all_button").innerHTML = "";
171  if (domains.length) {
172    var button = document.createElement("button");
173    button.onclick = removeAllForFilter;
174    button.innerText = "delete all " + domains.length;
175    select("#delete_all_button").appendChild(button);
176  }
177
178  resetTable();
179  var table = select("#cookies");
180
181  domains.forEach(function(domain) {
182    var cookies = cache.getCookies(domain);
183    var row = table.insertRow(-1);
184    row.insertCell(-1).innerText = domain;
185    var cell = row.insertCell(-1);
186    cell.innerText = cookies.length;
187    cell.setAttribute("class", "cookie_count");
188
189    var button = document.createElement("button");
190    button.innerText = "delete";
191    button.onclick = (function(dom){
192      return function() {
193        removeCookiesForDomain(dom);
194      };
195    }(domain));
196    var cell = row.insertCell(-1);
197    cell.appendChild(button);
198    cell.setAttribute("class", "button");
199  });
200}
201
202function focusFilter() {
203  select("#filter").focus();
204}
205
206function resetFilter() {
207  var filter = select("#filter");
208  filter.focus();
209  if (filter.value.length > 0) {
210    filter.value = "";
211    reloadCookieTable();
212  }
213}
214
215var ESCAPE_KEY = 27;
216window.onkeydown = function(event) {
217  if (event.keyCode == ESCAPE_KEY) {
218    resetFilter();
219  }
220}
221
222function listener(info) {
223  cache.remove(info.cookie);
224  if (!info.removed) {
225    cache.add(info.cookie);
226  }
227  scheduleReloadCookieTable();
228}
229
230function startListening() {
231  chrome.cookies.onChanged.addListener(listener);
232}
233
234function stopListening() {
235  chrome.cookies.onChanged.removeListener(listener);
236}
237
238function onload() {
239  focusFilter();
240  var timer = new Timer();
241  chrome.cookies.getAll({}, function(cookies) {
242    startListening();
243    start = new Date();
244    for (var i in cookies) {
245      cache.add(cookies[i]);
246    }
247    timer.reset();
248    reloadCookieTable();
249  });
250}
251
252document.addEventListener('DOMContentLoaded', function() {
253  onload();
254  document.body.addEventListener('click', focusFilter);
255  document.querySelector('#remove_button').addEventListener('click', removeAll);
256  document.querySelector('#filter_div input').addEventListener(
257      'input', reloadCookieTable);
258  document.querySelector('#filter_div button').addEventListener(
259      'click', resetFilter);
260});
261