1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/lib/xpcom",
  5     "firebug/lib/object",
  6     "firebug/lib/locale",
  7     "firebug/lib/domplate",
  8     "firebug/lib/dom",
  9     "firebug/lib/options",
 10     "firebug/lib/persist",
 11     "firebug/lib/string",
 12     "firebug/lib/http",
 13     "firebug/lib/css",
 14     "firebug/lib/events",
 15     "firebug/lib/array",
 16     "firebug/cookies/baseObserver",
 17     "firebug/cookies/menuUtils",
 18     "firebug/cookies/cookieReps",
 19     "firebug/cookies/cookieUtils",
 20     "firebug/cookies/cookie",
 21     "firebug/cookies/breakpoints",
 22     "firebug/cookies/cookieObserver",
 23     "firebug/cookies/cookieClipboard",
 24     "firebug/chrome/tabWatcher",
 25     "firebug/cookies/httpObserver",
 26     "firebug/lib/system",
 27     "firebug/cookies/cookie",
 28     "firebug/cookies/cookiePermissions",
 29     "firebug/cookies/editCookie",
 30     "firebug/trace/traceListener",
 31     "firebug/trace/traceModule",
 32     "firebug/chrome/firefox",
 33     "firebug/cookies/legacy",
 34 ],
 35 function(Xpcom, Obj, Locale, Domplate, Dom, Options, Persist, Str, Http, Css, Events, Arr,
 36     BaseObserver, MenuUtils, CookieReps, CookieUtils, Cookier, Breakpoints, CookieObserver,
 37     CookieClipboard, TabWatcher, HttpObserver, System, Cookie, CookiePermissions, EditCookie,
 38     TraceListener, TraceModule, Firefox) {
 39 
 40 with (Domplate) {
 41 
 42 // ********************************************************************************************* //
 43 // Constants
 44 
 45 const Cc = Components.classes;
 46 const Ci = Components.interfaces;
 47 
 48 // Firefox Preferences
 49 const networkPrefDomain = "network.cookie";
 50 const cookieBehaviorPref = "cookieBehavior";
 51 const cookieLifeTimePref = "lifetimePolicy";
 52 
 53 // Cookies preferences
 54 const clearWhenDeny = "cookies.clearWhenDeny";
 55 const defaultExpireTime = "cookies.defaultExpireTime";
 56 const removeConfirmation = "cookies.removeConfirmation";
 57 const removeSessionConfirmation = "cookies.removeSessionConfirmation";
 58 
 59 // Services
 60 const cookieManager = Xpcom.CCSV("@mozilla.org/cookiemanager;1", "nsICookieManager2");
 61 const observerService = Xpcom.CCSV("@mozilla.org/observer-service;1", "nsIObserverService");
 62 const prompts = Xpcom.CCSV("@mozilla.org/embedcomp/prompt-service;1", "nsIPromptService");
 63 
 64 // Preferences
 65 const PrefService = Cc["@mozilla.org/preferences-service;1"];
 66 const prefService = PrefService.getService(Ci.nsIPrefService);
 67 const prefs = PrefService.getService(Ci.nsIPrefBranch);
 68 
 69 // Cookie panel ID.
 70 const panelName = "cookies";
 71 
 72 // Helper array for prematurely created contexts
 73 var contexts = new Array();
 74 
 75 // Register stylesheet in Firebug. This method is introduced in Firebug 1.6
 76 Firebug.registerStylesheet("chrome://firebug/skin/cookies/cookies.css");
 77 
 78 // ********************************************************************************************* //
 79 // Module Implementation
 80 
 81 /**
 82  * @module This class represents a <i>module</i> for Cookies panel.
 83  * The module supports activation (enable/disable of the Cookies panel).
 84  * This functionality has been introduced in Firebug 1.2 and makes possible
 85  * to control activity of Firebug panels in order to avoid (performance) expensive
 86  * features.
 87  */
 88 Firebug.CookieModule = Obj.extend(Firebug.ActivableModule,
 89 /** @lends Firebug.CookieModule */
 90 {
 91     contexts: contexts,
 92 
 93     // Set to true if all hooks for monitoring cookies are registered; otherwise false.
 94     observersRegistered: false,
 95 
 96     /**
 97      * Called by Firebug when Firefox window is opened.
 98      *
 99      * @param {String} prefDomain Preference domain (e.g. extensions.firebug)
100      * @param {Array} prefNames Default Firebug preference array.
101      */
102     initialize: function(prefDomain, prefNames)
103     {
104         if (FBTrace.DBG_COOKIES)
105             FBTrace.sysout("cookies.CookieModule.initialize; ");
106 
107         this.traceListener = new TraceListener("cookies.", "DBG_COOKIES", true,
108             "chrome://firebug/skin/cookies/trace.css");
109 
110         TraceModule.addListener(this.traceListener);
111 
112         this.panelName = panelName;
113         this.description = Locale.$STR("cookies.modulemanager.description");
114 
115         Firebug.ActivableModule.initialize.apply(this, arguments);
116 
117         var permTooltip = Firebug.chrome.$("fcPermTooltip");
118         permTooltip.fcEnabled = true;
119 
120         // All the necessary observers are registered by default. Even if the 
121         // panel can be disabled (entirely or for a specific host) there is
122         // no simple way to find out this now, as the context isn't available. 
123         // All will be unregistered again in the initContext (if necessary).
124         // There is no big overhead, the initContext is called just after the
125         // first document request.
126         //this.registerObservers();
127 
128         // Register listener for NetInfoBody (if the API is available) so,
129         // a new tab (Cookies) can be appended into the Net panel request info.
130         var netInfoBody = Firebug.NetMonitor.NetInfoBody;
131         if ("addListener" in netInfoBody)
132             netInfoBody.addListener(this.NetInfoBody);
133 
134         // Register listener within the Console panel. If document.cookie property
135         // is logged, formatted output is used.
136         //Firebug.Console.addListener(this.ConsoleListener);
137 
138         // Register debugger listener for providing cookie-breakpoints.
139         //Firebug.Debugger.addListener(this.DebuggerListener);
140 
141         // Dynamically overlay Break on Next button in FB 1.5.1
142         // There is a small decoration coming from each panel.
143         var bonStack = Firebug.chrome.$("fbBreakOnNextButtonStack");
144         if (bonStack)
145         {
146             var image = document.createElement("image");
147             image.setAttribute("id", "fbBreakOnImageCookies");
148             image.setAttribute("class", "fbBreakOnImage");
149             image.setAttribute("src", "chrome://firebug/skin/cookies/breakOnCookieSingle.png");
150             bonStack.appendChild(image);
151         }
152 
153         Firebug.registerUIListener(this);
154     },
155 
156     initializeUI: function()
157     {
158         Firebug.ActivableModule.initializeUI.apply(this, arguments);
159 
160         // Append the styleesheet to a new console popup panel introduced in Firebug 1.6
161         this.addStyleSheet(null);
162 
163         Dom.collapse(Firebug.chrome.$("fbConsoleFilter-cookies"), false);
164     },
165 
166     /**
167      * Peforms clean up when Firebug is destroyed.
168      * Called by the framework when Firebug is closed for an existing Firefox window.
169      */
170     shutdown: function()
171     {
172         this.unregisterObservers();
173 
174         // Support for trace-console customization in Firebug 1.3
175         TraceModule.removeListener(this.traceListener);
176 
177         var netInfoBody = Firebug.NetMonitor.NetInfoBody;
178         if ("removeListener" in netInfoBody)
179             netInfoBody.removeListener(this.NetInfoBody);
180 
181         //Firebug.Console.removeListener(this.ConsoleListener);
182         //Firebug.Debugger.removeListener(this.DebuggerListener);
183 
184         Firebug.unregisterUIListener(this);
185     },
186 
187     registerObservers: function()
188     {
189         if (this.observersRegistered)
190         {
191             if (FBTrace.DBG_COOKIES)
192                 FBTrace.sysout("cookies.cookieModule.registerObservers; Observers ALREADY registered");
193             return;
194         }
195 
196         observerService.addObserver(HttpObserver, "http-on-modify-request", false);
197         observerService.addObserver(HttpObserver, "http-on-examine-response", false);
198         observerService.addObserver(PermissionObserver, "perm-changed", false);
199         registerCookieObserver(CookieObserver);
200         prefs.addObserver(networkPrefDomain, PrefObserver, false);
201 
202         this.observersRegistered = true;
203 
204         if (FBTrace.DBG_COOKIES)
205             FBTrace.sysout("cookies.cookieModule.registerObservers;");
206     },
207 
208     unregisterObservers: function(context)
209     {
210         if (!this.observersRegistered)
211         {
212             if (FBTrace.DBG_COOKIES)
213                 FBTrace.sysout("cookies.cookieModule.registerObservers; " +
214                     "Observers ALREADY un-registered");
215             return;
216         }
217 
218         observerService.removeObserver(HttpObserver, "http-on-modify-request");
219         observerService.removeObserver(HttpObserver, "http-on-examine-response");
220         observerService.removeObserver(PermissionObserver, "perm-changed");
221         unregisterCookieObserver(CookieObserver);
222         prefs.removeObserver(networkPrefDomain, PrefObserver);
223 
224         this.observersRegistered = false;
225 
226         if (FBTrace.DBG_COOKIES)
227             FBTrace.sysout("cookies.cookieModule.unregisterObservers;");
228     },
229 
230     // Helper context
231     initTempContext: function(tempContext)
232     {
233         tempContext.cookieTempObserver = registerCookieObserver(new CookieTempObserver(tempContext));
234 
235         // Create sub-context for cookies.
236         tempContext.cookies = {};
237         tempContext.cookies.activeHosts = [];
238     },
239 
240     destroyTempContext: function(tempContext, context)
241     {
242         if (!tempContext)
243             return;
244 
245         if (FBTrace.DBG_COOKIES)
246         {
247             FBTrace.sysout("cookies.Copy " + tempContext.events.length +
248                 " events to real-context.");
249 
250             var message = "cookies.Copy active hosts (";
251             for (var host in tempContext.cookies.activeHosts)
252                 message += host + ", ";
253             message = message.substring(0, message.length - 2);
254             message += ") from temp context into the real context.";
255             FBTrace.sysout(message, tempContext);
256         }
257 
258         // Copy all active hosts on the page. In case of redirects or embedded IFrames, there
259         // can be more hosts (domains) involved on the page. Cookies must be displayed for
260         // all of them.
261         context.cookies.activeHosts = cloneMap(tempContext.cookies.activeHosts);
262 
263         // Clone all active (received) cookies on the page.
264         // This is probably not necessary, as the first cookie is received
265         // in http-on-examine-response and at that time the real context
266         // is already created.
267         context.cookies.activeCookies = cloneMap(tempContext.cookies.activeCookies);
268 
269         // Fire all lost cookie events (those from the temp context).
270         var events = tempContext.events;
271         for (var i=0; i<events.length; i++) {
272             var e = events[i];
273             if (FBTrace.DBG_COOKIES)
274                 FBTrace.sysout("cookies.Fire fake cookie event: " + e.topic + ", " + e.data + "\n");
275             CookieObserver.observe(e.subject, e.topic, e.data);
276         }
277 
278         delete tempContext.cookies.activeHosts;
279         delete tempContext.cookies.activeCookies;
280         delete tempContext.cookies;
281 
282         // Unregister temporary cookie observer.
283         tempContext.cookieTempObserver = unregisterCookieObserver(tempContext.cookieTempObserver);
284     },
285 
286     /**
287      * Called by the framework when a context is created for Firefox tab.
288      * 
289      *  @param {Firebug.TabContext} Context for the current Firefox tab.
290      */
291     initContext: function(context)
292     {
293         var tabId = Firebug.getTabIdForWindow(context.window);
294 
295         if (FBTrace.DBG_COOKIES)
296             FBTrace.sysout("cookies.INIT real context for: " + tabId + ", " +
297                 context.getName());
298 
299         // Create sub-context for cookies. 
300         // xxxHonza: the cookies object exists within the context even if 
301         // the panel is disabled.
302         context.cookies = {};
303         context.cookies.activeHosts = [];
304 
305         // Initialize custom path filter for this context
306         context.cookies.pathFilter = "/";
307 
308         // List of breakpoints.
309         context.cookies.breakpoints = new CookieBreakpointGroup();
310         context.cookies.breakpoints.load(context);
311 
312         // The temp context isn't created e.g. for empty tabs, chrome pages.
313         var tempContext = contexts[tabId];
314         if (tempContext)
315         {
316             this.destroyTempContext(tempContext, context);
317             delete contexts[tabId];
318 
319             if (FBTrace.DBG_COOKIES)
320                 FBTrace.sysout("cookies.DESTROY temporary context, tabId: " + tempContext.tabId);
321         }
322 
323         // The base class must be called after the context for Cookies panel is 
324         // properly initialized. The panel can be created inside this function
325         // (within Firebug.ActivableModule.enablePanel), which can result in
326         // calling CookiePanel.initialize method. This method directly calls
327         // CookiePanel.refresh, which needs the context.cookies object ready.
328         Firebug.ActivableModule.initContext.apply(this, arguments);
329 
330         // Unregister all observers if the panel is disabled.
331         if (!this.isEnabled(context))
332             this.unregisterObservers(context);
333     },
334 
335     destroyContext: function(context) 
336     {
337         Firebug.ActivableModule.destroyContext.apply(this, arguments);
338 
339         if (!context.cookies)
340         {
341             if (FBTrace.DBG_COOKIES)
342             {
343                 var tabId = Firebug.getTabIdForWindow(context.window);
344                 FBTrace.sysout("cookies.DESTROY context ERROR: No context.cookies available, tabId: " +
345                     tabId + ", " + context.getName());
346             }
347             return;
348         }
349 
350         context.cookies.breakpoints.store(context);
351 
352         for (var p in context.cookies)
353             delete context.cookies[p];
354 
355         delete context.cookies;
356 
357         if (FBTrace.DBG_COOKIES)
358         {
359             var tabId = Firebug.getTabIdForWindow(context.window);
360             FBTrace.sysout("cookies.DESTROY context, tabId: " + tabId +
361                 ", " + context.getName());
362         }
363     },
364 
365     addStyleSheet: function(panel)
366     {
367         // Use registration function instead (introduced in Firebug 1.6)
368         if (Firebug.registerStylesheet)
369             return;
370 
371         function privateAppend(doc)
372         {
373             // Make sure the stylesheet isn't appended twice.
374             if (!Firebug.chrome.$("fcStyles", doc))
375             {
376                 var styleSheet = createStyleSheet(doc, "chrome://firebug/skin/cookies/cookies.css");
377                 styleSheet.setAttribute("id", "fcStyles");
378                 addStyleSheet(doc, styleSheet);
379             }
380         }
381 
382         if (panel)
383             privateAppend(panel.document)
384 
385         // Firebug 1.6 introduces another panel for console preview on other panels
386         // The allows to use command line in other panels too.
387         var preview = Firebug.chrome.$("fbCommandPreviewBrowser");
388         if (preview)
389             privateAppend(preview.contentDocument);
390     },
391 
392     updateOption: function(name, value)
393     {
394         if (name == "consoleFilterTypes")
395         {
396             this.updateConsoleFilter();
397         }
398     },
399 
400     updateConsoleFilter: function()
401     {
402         if (FBTrace.DBG_COOKIES)
403             FBTrace.sysout("cookies.updateConsoleFilter;");
404 
405         if (!Firebug.currentContext)
406             return;
407 
408         // The panel can be disabled.
409         var panel = Firebug.currentContext.getPanel("console");
410         if (!panel)
411             return;
412 
413         var panelNode = panel.panelNode;
414         var className = "hideType-cookies";
415         var filterTypes = Firebug.consoleFilterTypes;
416 
417         Css.setClass(panelNode, className);
418 
419         var positiveFilters = ["all", "cookies"];
420         for (var i=0; i<positiveFilters.length; i++)
421         {
422             if (filterTypes.indexOf(positiveFilters[i]) >= 0)
423             {
424                 Css.removeClass(panelNode, className);
425                 break;
426             }
427         }
428     },
429 
430     showPanel: function(browser, panel)
431     {
432         // Update panel's toolbar
433         var isCookiePanel = panel && panel.name == panelName;
434 
435         // Firebug 1.4, chrome changes.
436         var chrome = browser.chrome ? browser.chrome : Firebug.chrome;
437 
438         var cookieButtons = Firebug.chrome.$("fbCookieButtons");
439         Dom.collapse(cookieButtons, !isCookiePanel);
440 
441         // The console panel can be displayed sooner than the Cookies
442         // panel, in such a case the Stylesheet must be ready as
443         // there are cookies logs in the console.
444         // Cookie table is also used within the net panel.
445         if (panel && (panel.name == "console" || panel.name == "net"))
446             this.addStyleSheet(panel);
447     },
448 
449     watchWindow: function(context, win) 
450     {
451         context.window.addEventListener("beforeunload", this.onBeforeUnload, false);
452     },
453 
454     onBeforeUnload: function(event) 
455     {
456         var view = event.target.defaultView;
457         var context = TabWatcher.getContextByWindow(view);
458         if (!context)
459             return;
460 
461         var panel = context.getPanel(panelName, true);
462         if (panel)
463             panel.clear();
464 
465         if (FBTrace.DBG_COOKIES || FBTrace.DBG_ERRORS)
466         {
467             var tabId = Firebug.getTabIdForWindow(view);
468 
469             if (FBTrace.DBG_COOKIES)
470                 FBTrace.sysout("cookies.On before unload tab:  " + tabId);
471 
472             if (contexts[tabId])
473             {
474                 delete contexts[tabId];
475 
476                 if (FBTrace.DBG_COOKIES)
477                     FBTrace.sysout("cookies.CookieModule.onBeforeUnload; There is a temp context leak!");
478             }
479         }
480     },
481 
482     /**
483      * Creates a new cookie in the browser.
484      * This method is used by {@link EditCookie} dialog and also when a cookie is
485      * pasted from the clipboard.
486      *
487      * @param {Cookie} Cookie object with appropriate properties. See {@link Cookie} object.
488      */
489     createCookie: function(cookie)
490     {
491         try
492         {
493             var uri = cookie.getURI();
494             if (!uri)
495                 return;
496 
497             var c = cookie.cookie;
498 
499             // Fix for issue 34. The domain must be included in the cookieString if it 
500             // starts with "." But don't include it otherwise, since the "." would be 
501             // appended by the service.
502             var host = cookie.cookie.host;
503             var cookieString = cookie.toString(!(host.charAt(0) == "."));
504 
505             // Fix for issue 37: httpOnly cookies, and issue 47: Cannot change the HttpOnly flag
506             // HttpOnly cookies can't be changed by setCookie string
507             // See also: https://bugzilla.mozilla.org/show_bug.cgi?id=178993
508             //cookieService.setCookieString(uri, null, cookieString, null);
509 
510             // Doesn't work in FF4 (issue 95)
511             //cookieService.setCookieStringFromHttp(uri, uri, null, cookieString,
512             //    c.expires, null);
513 
514             //xxxHonza: in what cases the cookie should be removed?
515             //var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
516             //cm.remove(c.host, c.name, c.path, false);
517 
518             var isSession = c.expires ? false : true;
519             var cm2 = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
520             cm2.add(c.host, c.path, c.name, c.rawValue, c.isSecure, c.isHttpOnly, isSession,
521                 c.expires || Math.round((new Date()).getTime() / 1000 + 9999999999));
522 
523             if (FBTrace.DBG_COOKIES)
524                 FBTrace.sysout("cookies.createCookie: set cookie string: " + cookieString, cookie);
525 
526             // xxxHonza: this shouldn't be necessary, but sometimes the CookieObserver
527             // is not triggered.
528             TabWatcher.iterateContexts(function(context)
529             {
530                 context.getPanel(panelName).refresh();
531             });
532         }
533         catch (e)
534         {
535             if (FBTrace.DBG_ERRORS)
536                 FBTrace.sysout("cookies.createCookie: set cookie string ERROR " +
537                     cookieString, e);
538         }
539     },
540 
541     removeCookie: function(host, name, path)
542     {
543         cookieManager.remove(host, name, path, false);
544 
545         // xxxHonza: this shouldn't be necessary, but sometimes the CookieObserver
546         // is not triggered.
547         TabWatcher.iterateContexts(function(context)
548         {
549             context.getPanel(panelName).refresh();
550         });
551     },
552 
553     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
554     // Support for ActivableModule 1.6
555 
556     /**
557      * It's just here to exists (calling base class only)
558      */
559     isEnabled: function(context)
560     {
561         return Firebug.ActivableModule.isEnabled.apply(this, arguments);
562     },
563 
564     /**
565      * Called when an observer (e.g. panel) is added/removed into/from the model.
566      * This is the moment when the model needs to decide whether to activate.
567      */
568     onObserverChange: function(observer)
569     {
570         if (this.hasObservers())
571             TabWatcher.iterateContexts(Firebug.CookieModule.registerObservers);
572         else
573             TabWatcher.iterateContexts(Firebug.CookieModule.unregisterObservers);
574 
575         this.setStatus();
576     },
577 
578     onSuspendFirebug: function()
579     {
580         TabWatcher.iterateContexts(Firebug.CookieModule.unregisterObservers);
581 
582         this.setStatus();
583 
584         if (FBTrace.DBG_COOKIES)
585             FBTrace.sysout("cookies.onSuspendFirebug");
586     },
587 
588     onResumeFirebug: function(context)
589     {
590         if (Firebug.CookieModule.isAlwaysEnabled())
591             TabWatcher.iterateContexts(Firebug.CookieModule.registerObservers);
592 
593         this.setStatus();
594 
595         if (FBTrace.DBG_COOKIES)
596             FBTrace.sysout("cookies.onResumeFirebug");
597     },
598 
599     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
600 
601     setStatus: function()
602     {
603         var fbStatus = Firefox.getElementById("firebugStatus");
604         if (fbStatus)
605         {
606             if (this.hasObservers())
607                 fbStatus.setAttribute(panelName, "on");
608             else
609                 fbStatus.removeAttribute(panelName);
610         }
611         else
612         {
613             if (FBTrace.DBG_ERRORS)
614                 FBTrace.sysout("cookies.setStatus ERROR no firebugStatus element");
615         }
616     },
617 
618     getMenuLabel: function(option, location)
619     {
620         var host = getURIHost(location);
621 
622         // In case of local files or system pages use this labels instead of host.
623         // xxxHonza: the panel should be automatically disabled for local files
624         // and system pages as there are no cookies associated.
625         // These options shouldn't be available at all.
626         if (isSystemURL(location.spec))
627             host = Locale.$STR("cookies.SystemPages");
628         else if (!getURIHost(location))
629             host = Locale.$STR("cookies.LocalFiles");
630 
631         // Translate these two options in panel activable menu from cookies.properties
632         switch (option)
633         {
634         case "disable-site":
635             return Locale.$STRF("cookies.HostDisable", [host]);
636         case "enable-site":
637             return Locale.$STRF("cookies.HostEnable", [host]);
638         }
639 
640         return Firebug.ActivableModule.getMenuLabel.apply(this, arguments);
641     },
642 
643     // xxxHonza: This method is overriden just to provide translated strings from
644     // cookies.properties file.
645     openPermissions: function(event, context)
646     {
647         Events.cancelEvent(event);
648 
649         var browserURI = Firebug.chrome.getBrowserURI(context);
650         var host = this.getHostForURI(browserURI);
651 
652         var params = {
653             permissionType: this.getPrefDomain(),
654             windowTitle: Locale.$STR(this.panelName + ".Permissions"),
655             introText: Locale.$STR(this.panelName + ".PermissionsIntro"),
656             blockVisible: true,
657             sessionVisible: false,
658             allowVisible: true,
659             prefilledHost: host,
660         };
661 
662         openWindow("Browser:Permissions", "chrome://browser/content/preferences/permissions.xul",
663             "", params);
664     },
665 
666     // UI Commands
667     onRemoveAllShowTooltip: function(tooltip, context)
668     {
669         tooltip.label = Locale.$STR("cookies.removeall.tooltip");
670         return true;
671     },
672 
673     onRemoveAllSessionShowTooltip: function(tooltip, context)
674     {
675         tooltip.label = Locale.$STR("cookies.removeallsession.tooltip");
676         return true;
677     },
678 
679     /**
680      * Removes cookies defined for a website
681      * @param {Object} context context, in which the cookies are defined
682      * @param {Object} [filter] filter to define, which cookies should be removed
683      *   (format: {session: true/false, host: string})
684      */
685     removeCookies: function(context, filter)
686     {
687         var panel = context.getPanel(panelName, true);
688         if (!panel)
689             return;
690 
691         for (var host in context.cookies.activeHosts)
692         {
693             var cookieEnumerator = cookieManager.getCookiesFromHost(host);
694 
695             while (cookieEnumerator.hasMoreElements())
696             {
697                 var cookie = cookieEnumerator.getNext().QueryInterface(Ci.nsICookie2);
698 
699                 if (!filter || ((!filter.session || cookie.isSession) && (!filter.host || filter.host == cookie.host)))
700                     cookieManager.remove(cookie.host, cookie.name, cookie.path, false);
701             }
702         }
703     },
704 
705     onRemoveAll: function(context)
706     {
707         if (Options.get(removeConfirmation))
708         {
709             var check = {value: false};
710             var flags = prompts.BUTTON_POS_0 * prompts.BUTTON_TITLE_YES +  
711             prompts.BUTTON_POS_1 * prompts.BUTTON_TITLE_NO;  
712 
713             if (!prompts.confirmEx(context.chrome.window, Locale.$STR("Firebug"),
714                 Locale.$STR("cookies.confirm.removeall"), flags, "", "", "",
715                 Locale.$STR("Do_not_show_this_message_again"), check) == 0)
716             {
717                 return;
718             }
719 
720             // Update 'Remove Cookies' confirmation option according to the value
721             // of the dialog's "do not show again" checkbox.
722             Options.set(removeConfirmation, !check.value);
723         }
724 
725         Firebug.CookieModule.removeCookies(context);
726     },
727     
728     onRemoveAllSession: function(context)
729     {
730         if (Options.get(removeSessionConfirmation))
731         {
732             var check = {value: false};
733             var flags = prompts.BUTTON_POS_0 * prompts.BUTTON_TITLE_YES +  
734                 prompts.BUTTON_POS_1 * prompts.BUTTON_TITLE_NO;  
735 
736             if (!prompts.confirmEx(context.chrome.window, Locale.$STR("Firebug"),
737                 Locale.$STR("cookies.confirm.removeallsession"), flags, "", "", "",
738                 Locale.$STR("Do_not_show_this_message_again"), check) == 0)
739             {
740                 return;
741             }
742 
743             // Update 'Remove Session Cookies' confirmation option according to the value
744             // of the dialog's "do not show again" checkbox.
745             Options.set(removeSessionConfirmation, !check.value)
746         }
747 
748         Firebug.CookieModule.removeCookies(context, {session: true});
749     },
750 
751     onRemoveAllFromHost: function(context, host)
752     {
753         if (Options.get(removeConfirmation))
754         {
755             var check = {value: false};
756             var flags = prompts.BUTTON_POS_0 * prompts.BUTTON_TITLE_YES +  
757                 prompts.BUTTON_POS_1 * prompts.BUTTON_TITLE_NO;  
758 
759             if (!prompts.confirmEx(context.chrome.window, Locale.$STR("Firebug"),
760                 Locale.$STRF("cookies.confirm.Remove_All_From_Host", [host]), flags, "", "", "",
761                 Locale.$STR("Do_not_show_this_message_again"), check) == 0)
762             {
763                 return;
764             }
765 
766             // Update 'Remove Cookies' confirmation option according to the value
767             // of the dialog's "do not show again" checkbox.
768             Options.set(removeConfirmation, !check.value);
769         }
770 
771         Firebug.CookieModule.removeCookies(context, {host: host});
772     },
773 
774     onCreateCookieShowTooltip: function(tooltip, context)
775     {
776         var host = context.window.location.host;
777         tooltip.label = Locale.$STRF("cookies.createcookie.tooltip", [host]);
778         return true;
779     },
780 
781     onCreateCookie: function(context)
782     {
783         if (FBTrace.DBG_COOKIES)
784             FBTrace.sysout("cookies.onCreateCookie");
785 
786         // There is an excepion if the window is closed or not initialized (empty tab)
787         var host;
788         try {
789             host = context.window.location.host
790         }
791         catch (err) {
792             alert(Locale.$STR("cookies.message.There_is_no_active_page"));
793             return;
794         }
795 
796         // Name and domain.
797         var cookie = new Object();
798         cookie.name = this.getDefaultCookieName(context);
799         cookie.host = host;
800 
801         // The edit dialog uses raw value.
802         cookie.rawValue = Locale.$STR("cookies.createcookie.defaultvalue");
803 
804         // Default path
805         var path = context.window.location.pathname || "/";
806         cookie.path = path.substr(0, (path.lastIndexOf("/") || 1));
807 
808         // Set defaul expiration time.
809         cookie.expires = this.getDefaultCookieExpireTime();
810 
811         var params = {
812             cookie: cookie,
813             action: "create",
814             window: context.window,
815             EditCookie: EditCookie,
816             Firebug: Firebug,
817             FBTrace: FBTrace,
818         };
819 
820         var parent = context.chrome.window;
821         parent.openDialog("chrome://firebug/content/cookies/editCookie.xul",
822             "_blank", "chrome,centerscreen,resizable=yes,modal=yes",
823             params);
824     },
825 
826     getDefaultCookieName: function(context, defaultName)
827     {
828         var counter = 0;
829         var cookieDefaultName = defaultName || "Cookie";
830         var cookieName = cookieDefaultName;
831         var exists = false;
832         var panel = context.getPanel(panelName);
833 
834         do
835         {
836             exists = false;
837 
838             var row = Dom.getElementByClass(panel.panelNode, "cookieRow");
839             while (row)
840             {
841                 var rep = row.repObject;
842 
843                 // If the cookie is expanded, there is a row without the repObject
844                 if (rep && rep.cookie.name == cookieName)
845                 {
846                     counter++;
847                     exists = true;
848                     cookieName = cookieDefaultName + "-" + counter;
849                     break;
850                 }
851                 row = row.nextSibling;
852             }
853         } while (exists)
854 
855         return cookieName;
856     },
857 
858     getDefaultCookieExpireTime: function()
859     {
860         // Get default expire time interval (in seconds) and add it to the
861         // current time.
862         var defaultInterval = Options.get(defaultExpireTime);
863         var now = new Date();
864         now.setTime(now.getTime() + (defaultInterval * 1000));
865 
866         // Return final expiration time.
867         return (now.getTime() / 1000);
868     },
869 
870     /**
871      * Exports all existing cookies in the browser into a cookies.txt file.
872      * This action is available in the Cookies panel toolbar.
873      */
874     onExportAll: function(context)
875     {
876         try 
877         {
878             var fp = Xpcom.CCIN("@mozilla.org/filepicker;1", "nsIFilePicker");
879             fp.init(window, null, Ci.nsIFilePicker.modeSave);
880             fp.appendFilters(Ci.nsIFilePicker.filterAll | Ci.nsIFilePicker.filterText);
881             fp.filterIndex = 1;
882             fp.defaultString = "cookies.txt";
883 
884             var rv = fp.show();
885             if (rv == Ci.nsIFilePicker.returnOK || rv == Ci.nsIFilePicker.returnReplace)
886             {
887                 var foStream = Xpcom.CCIN("@mozilla.org/network/file-output-stream;1", "nsIFileOutputStream");
888                 foStream.init(fp.file, 0x02 | 0x08 | 0x20, 0666, 0); // write, create, truncate
889 
890                 var e = cookieManager.enumerator;
891                 while(e.hasMoreElements())
892                 {
893                     var cookie = e.getNext();
894                     cookie = cookie.QueryInterface(Ci.nsICookie2);
895                     var cookieWrapper = new Cookie(CookieUtils.makeCookieObject(cookie));
896                     var cookieInfo = cookieWrapper.toText();
897                     foStream.write(cookieInfo, cookieInfo.length);
898                 }
899 
900                 foStream.close();
901             }
902         }
903         catch (err)
904         {
905             if (FBTrace.DBG_COOKIES)
906                 FBTrace.sysout("cookies.onExportAll EXCEPTION", err);
907         }
908     },
909 
910     onExportForSiteShowTooltip: function(tooltip, context)
911     {
912         var host = context.window.location.host;
913         tooltip.label = Locale.$STRF("cookies.export.Export_For_Site_Tooltip", [host]);
914         return true;
915     },
916 
917     /**
918      * Exports cookies for the current site into a cookies.txt file
919      * This action is available in the Cookies panel toolbar.
920      */
921     onExportForSite: function(context)
922     {
923         try 
924         {
925             var fp = Xpcom.CCIN("@mozilla.org/filepicker;1", "nsIFilePicker");
926             fp.init(window, null, Ci.nsIFilePicker.modeSave);
927             fp.appendFilters(Ci.nsIFilePicker.filterAll | Ci.nsIFilePicker.filterText);
928             fp.filterIndex = 1;
929             fp.defaultString = "cookies.txt";
930 
931             var rv = fp.show();
932             if (rv == Ci.nsIFilePicker.returnOK || rv == Ci.nsIFilePicker.returnReplace)
933             {
934                 var foStream = Xpcom.CCIN("@mozilla.org/network/file-output-stream;1",
935                     "nsIFileOutputStream");
936                 foStream.init(fp.file, 0x02 | 0x08 | 0x20, 0666, 0); // write, create, truncate
937 
938                 var panel = context.getPanel(panelName, true);
939                 var tbody = Dom.getElementByClass(panel.panelNode, "cookieTable").firstChild;
940                 for (var row = tbody.firstChild; row; row = row.nextSibling)
941                 {
942                     if (Css.hasClass(row, "cookieRow") && row.repObject)
943                     {
944                         var cookieInfo = row.repObject.toText();
945                         foStream.write(cookieInfo, cookieInfo.length);
946                     }
947                 }
948 
949                 foStream.close();
950             }
951         }
952         catch (err)
953         {
954             if (FBTrace.DBG_COOKIES)
955                 FBTrace.sysout("cookies.onExportForSite EXCEPTION", err);
956         }
957     },
958 
959     onFilter: function(context, pref)
960     {
961         var value = Options.get(pref);
962         Options.set(pref, !value);
963 
964         TabWatcher.iterateContexts(function(context)
965         {
966             var panel = context.getPanel(panelName, true);
967             if (panel)
968                 panel.refresh();
969         });
970     },
971 
972     onFilterPopupShowing: function(menu)
973     {
974         var items = menu.getElementsByTagName("menuitem");
975         for (var i=0; i<items.length; i++)
976         {
977             var item = items[i];
978             var prefValue = Options.get(item.value);
979             if (prefValue)
980                 item.setAttribute("checked", "true");
981             else
982                 item.removeAttribute("checked");
983         }
984 
985         return true;
986     },
987 
988     // Custom path filter 
989     onFilterPanelShowing: function(filterPanel, context)
990     {
991         if (FBTrace.DBG_COOKIES)
992             FBTrace.sysout("cookies.onFilterPanelShowing ", filterPanel);
993 
994         // Initialize filter input field.
995         filterPanel.init(context.cookies.pathFilter);
996 
997         // A menu does not take the keyboard focus and keyboard messages are 
998         // sent to the window. In order to avoid unwante shortcuts execution
999         // register a window keypress listeners for the time when the filter
1000         // popup is displayed and stop propagation of these events.
1001         // https://developer.mozilla.org/en/XUL/PopupGuide/PopupKeys
1002         window.addEventListener("keypress", this.onFilterKeyPress, true);
1003         return true;
1004     },
1005 
1006     onFilterPanelHiding: function(filterPanel, context)
1007     {
1008         window.removeEventListener("keypress", this.onFilterKeyPress, true);
1009         return true;
1010     },
1011 
1012     onFilterKeyPress: function(event) 
1013     {
1014         // Stop propagation of keypress events when filter popup is displayed.
1015         event.stopPropagation();
1016     },
1017 
1018     onFilterPanelApply: function(context)
1019     {
1020         var parentMenu = Firebug.chrome.$("fcFilterMenuPopup");
1021         var filterPanel = Firebug.chrome.$("fcCustomPathFilterPanel");
1022 
1023         if (FBTrace.DBG_COOKIES)
1024             FBTrace.sysout("cookies.onApplyPathFilter, filter: " + filterPanel.value,
1025                 filterPanel);
1026 
1027         // Use the filter from panel.
1028         context.cookies.pathFilter = filterPanel.value;
1029 
1030         // Refresh cookie list.
1031         var panel = context.getPanel(panelName);
1032         panel.refresh();
1033 
1034         // Close menu.
1035         parentMenu.hidePopup();
1036     },
1037 
1038     onViewAll: function(context) 
1039     {
1040         parent.openDialog("chrome://browser/content/preferences/cookies.xul",
1041             "_blank", "chrome,resizable=yes", null);
1042     },
1043 
1044     onViewExceptions: function(context)
1045     {
1046         var params = {  
1047             blockVisible   : true,
1048             sessionVisible : true,
1049             allowVisible   : true,
1050             prefilledHost  : "",
1051             permissionType : "cookie",
1052             windowTitle    : Locale.$STR("cookies.ExceptionsTitle"),
1053             introText      : Locale.$STR("cookies.Intro")
1054         };
1055 
1056         parent.openDialog("chrome://browser/content/preferences/permissions.xul",
1057             "_blank","chrome,resizable=yes", params);
1058     },
1059 
1060     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1061     // Console Panel Options
1062 
1063     /**
1064      * Extend Console panel's option menu.
1065      */
1066     onOptionsMenu: function(context, panel, items)
1067     {
1068         if (panel.name != "console")
1069             return;
1070 
1071         var cookies = [
1072             MenuUtils.optionMenu(context, "cookies.showCookieEvents",
1073                 "cookies.tip.showCookieEvents", Firebug.prefDomain, "cookies.logEvents"),
1074         ];
1075 
1076         // The option is disabled if the panel is disabled.
1077         if (!this.isEnabled(context))
1078             cookies[0].disabled = true;
1079 
1080         // Append new option at the right position.
1081         for (var i=0; i<items.length; i++)
1082         {
1083             var item = items[i];
1084             if (item.option == "showStackTrace")
1085             {
1086                 Arr.arrayInsert(items, i+1, cookies);
1087                 return;
1088             }
1089         }
1090 
1091         // If "showStackTrace" is not there append at the end.
1092         Arr.arrayInsert(items, items.length, cookies);
1093     },
1094 });
1095 
1096 // ********************************************************************************************* //
1097 // Custom info tab within Net panel
1098 
1099 /**
1100  * @domplate Represents domplate template for cookie body that is displayed if 
1101  * a cookie entry in the cookie list is expanded.
1102  */
1103 Firebug.CookieModule.NetInfoBody = domplate(Firebug.Rep,
1104 /** @lends Firebug.CookieModule.NetInfoBody */
1105 {
1106     tag:
1107         DIV({"class": "netInfoCookiesList"},
1108             DIV({"class": "netInfoHeadersGroup netInfoCookiesGroup", $collapsed: "$cookiesInfo|hideReceivedCookies"}, 
1109                 SPAN(Locale.$STR("cookies.netinfo.Received Cookies"))
1110             ),
1111             DIV({"class": "netInfoReceivedCookies netInfoCookies"}),
1112             DIV({"class": "netInfoHeadersGroup netInfoCookiesGroup", $collapsed: "$cookiesInfo|hideSentCookies"}, 
1113                 SPAN(Locale.$STR("cookies.netinfo.Sent Cookies"))
1114             ),
1115             DIV({"class": "netInfoSentCookies netInfoCookies"})
1116         ),
1117 
1118     hideReceivedCookies: function(cookiesInfo)
1119     {
1120         return !cookiesInfo.receivedCookies.length;
1121     },
1122 
1123     hideSentCookies: function(cookiesInfo)
1124     {
1125         return !cookiesInfo.sentCookies.length;
1126     },
1127 
1128     // NetInfoBody listener
1129     initTabBody: function(infoBox, file)
1130     {
1131         var sentCookiesHeader = this.findHeader(file.requestHeaders, "Cookie");
1132         var receivedCookiesHeader = this.findHeader(file.responseHeaders, "Set-Cookie");
1133 
1134         // Create tab only if there are some cookies.
1135         if (sentCookiesHeader || receivedCookiesHeader)
1136             Firebug.NetMonitor.NetInfoBody.appendTab(infoBox, "Cookies",
1137                 Locale.$STR("cookies.Panel"));
1138     },
1139 
1140     destroyTabBody: function(infoBox, file)
1141     {
1142     },
1143 
1144     updateTabBody: function(infoBox, file, context)
1145     {
1146         var tab = infoBox.selectedTab;
1147         if (!tab || tab.dataPresented || !Css.hasClass(tab, "netInfoCookiesTab"))
1148             return;
1149 
1150         tab.dataPresented = true;
1151 
1152         if (FBTrace.DBG_COOKIES)
1153             FBTrace.sysout("cookies.NetInfoBodyListener.updateTabBody",
1154                 [file.requestHeaders, file.responseHeaders]);
1155 
1156         var sentCookiesHeader = this.findHeader(file.requestHeaders, "Cookie");
1157         var receivedCookiesHeader = this.findHeader(file.responseHeaders, "Set-Cookie");
1158 
1159         // Parse all received cookies and generate UI.
1160         var receivedCookies = [];
1161         var sentCookies = [];
1162 
1163         // Parse received cookies.
1164         if (receivedCookiesHeader) {
1165             var cookies = receivedCookiesHeader.split("\n");
1166             for (var i=0; i<cookies.length; i++) {
1167                 var cookie = CookieUtils.parseFromString(cookies[i]);
1168                 if (!cookie.host)
1169                     cookie.host = file.request.URI.host;
1170                 receivedCookies.push(new Cookie(CookieUtils.makeCookieObject(cookie)));
1171             }
1172         }
1173 
1174         // Parse sent cookies.
1175         sentCookies = CookieUtils.parseSentCookiesFromString(sentCookiesHeader);
1176 
1177         // Create basic UI content
1178         var tabBody = Dom.getElementByClass(infoBox, "netInfoCookiesText");
1179         this.tag.replace({cookiesInfo: {
1180             receivedCookies: receivedCookies,
1181             sentCookies: sentCookies,
1182         }}, tabBody);
1183 
1184         // Generate UI for received cookies.
1185         if (receivedCookies.length) {
1186             CookieReps.CookieTable.render(receivedCookies,
1187                 Dom.getElementByClass(tabBody, "netInfoReceivedCookies"));
1188         }
1189 
1190         // Generate UI for sent cookies.
1191         if (sentCookies.length) {
1192             CookieReps.CookieTable.render(sentCookies,
1193                 Dom.getElementByClass(tabBody, "netInfoSentCookies"));
1194         }
1195     },
1196 
1197     // Helpers
1198     findHeader: function(headers, name)
1199     {
1200         if (!headers)
1201             return null;
1202 
1203         for (var i=0; i<headers.length; i++) {
1204             if (headers[i].name == name)
1205                 return headers[i].value;
1206         }
1207 
1208         return null;
1209     }
1210 });
1211 
1212 // ********************************************************************************************* //
1213 // Permission observer
1214 
1215 /**
1216  * @class Represents an observer for perm-changed event that is dispatched
1217  * by Firefox is cookie permissions are changed.
1218  */
1219 var PermissionObserver = Obj.extend(BaseObserver,
1220 /** @lends PermissionObserver */
1221 {
1222     observe: function(aSubject, aTopic, aData) 
1223     {
1224         if (aTopic != "perm-changed")
1225             return;
1226 
1227         if (FBTrace.DBG_COOKIES)
1228             FBTrace.sysout("cookies.observe: " + aTopic + ", " + aData);
1229 
1230         var fn = Obj.bind(CookiePermissions.updatePermButton, CookiePermissions);
1231         TabWatcher.iterateContexts(fn);
1232     }
1233 });
1234 
1235 // ********************************************************************************************* //
1236 
1237 function CookieBreakpointGroup()
1238 {
1239     this.breakpoints = [];
1240 }
1241 
1242 CookieBreakpointGroup.prototype = Obj.extend(new Firebug.Breakpoint.BreakpointGroup(),
1243 {
1244     name: "cookieBreakpoints",
1245     title: Locale.$STR("cookies.Cookie Breakpoints"),
1246 
1247     addBreakpoint: function(cookie)
1248     {
1249         this.breakpoints.push(new Breakpoints.Breakpoint(cookie));
1250     },
1251 
1252     removeBreakpoint: function(cookie)
1253     {
1254         var bp = this.findBreakpoint(cookie);
1255         Arr.remove(this.breakpoints, bp);
1256     },
1257 
1258     matchBreakpoint: function(bp, args)
1259     {
1260         var cookie = args[0];
1261         return (bp.name == cookie.name) &&
1262             (bp.host == cookie.host) &&
1263             (bp.path == cookie.path);
1264     },
1265 
1266     // Persistence
1267     load: function(context)
1268     {
1269         var panelState = Persist.getPersistedState(context, panelName);
1270         if (panelState.breakpoints)
1271             this.breakpoints = panelState.breakpoints;
1272     },
1273 
1274     store: function(context)
1275     {
1276         var panelState = Persist.getPersistedState(context, panelName);
1277         panelState.breakpoints = this.breakpoints;
1278     }
1279 });
1280 
1281 // ********************************************************************************************* //
1282 // Registration Helpers
1283 
1284 function registerCookieObserver(observer)
1285 {
1286     if (observer.registered)
1287         return;
1288 
1289     if (FBTrace.DBG_COOKIES)
1290         FBTrace.sysout("cookies.registerCookieObserver");
1291 
1292     observerService.addObserver(observer, "cookie-changed", false);
1293     observerService.addObserver(observer, "cookie-rejected", false);
1294 
1295     observer.registered = true;
1296 
1297     return observer;
1298 }
1299 
1300 function unregisterCookieObserver(observer)
1301 {
1302     if (!observer.registered)
1303         return;
1304 
1305     if (FBTrace.DBG_COOKIES)
1306         FBTrace.sysout("cookies.unregisterCookieObserver");
1307 
1308     observerService.removeObserver(observer, "cookie-changed");
1309     observerService.removeObserver(observer, "cookie-rejected");
1310 
1311     observer.registered = false;
1312 }
1313 
1314 // ********************************************************************************************* //
1315 // Preference observer
1316 
1317 // xxxHonza: is this still needed?
1318 /**
1319  * @class Represents an observer for nsPref:changed event dispatched when 
1320  * an user preference is changed (e.g. using about:config)
1321  */
1322 var PrefObserver = Obj.extend(BaseObserver,
1323 /** @lends PrefObserver */
1324 {
1325     observe: function(aSubject, aTopic, aData)
1326     {
1327         if (aTopic != "nsPref:changed")
1328             return;
1329 
1330         if (FBTrace.DBG_COOKIES)
1331             FBTrace.sysout("cookies.observe: " + aTopic + ", " + aData);
1332 
1333         if (aData == networkPrefDomain + "." + cookieBehaviorPref || 
1334             aData == networkPrefDomain + "." + cookieLifeTimePref) {
1335             var fn = CookiePermissions.updatePermButton;
1336             TabWatcher.iterateContexts(fn);
1337         }
1338     }
1339 });
1340 
1341 // ********************************************************************************************* //
1342 // Used till the real context isn't available (in initContext), bug if Firebug)
1343 
1344 function CookieTempObserver(tempContext) {
1345     this.tempContext = tempContext;
1346 }
1347 
1348 CookieTempObserver.prototype = Obj.extend(BaseObserver, {
1349     observe: function(subject, topic, data) {
1350         this.tempContext.appendCookieEvent(subject, topic, data);
1351     }
1352 });
1353 
1354 // ********************************************************************************************* //
1355 // Array Helpers
1356 
1357 function cloneMap(map)
1358 {
1359     var newMap = [];
1360     for (var item in map)
1361         newMap[item] = map[item];
1362 
1363     return newMap;
1364 }
1365 
1366 // ********************************************************************************************* //
1367 // Firebug Registration
1368 
1369 // Expose to XUL scope
1370 Firebug.CookieModule.Perm = CookiePermissions;
1371 
1372 // Expose for tests
1373 Firebug.CookieModule.CookieReps = CookieReps;
1374 
1375 Firebug.registerActivableModule(Firebug.CookieModule);
1376 
1377 return Firebug.CookieModule;
1378 
1379 // ********************************************************************************************* //
1380 }});
1381