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/lib/search",
 17     "firebug/cookies/menuUtils",
 18     "firebug/cookies/cookieReps",
 19     "firebug/cookies/headerResizer",
 20     "firebug/cookies/cookieObserver",
 21     "firebug/cookies/cookieUtils",
 22     "firebug/cookies/cookie",
 23     "firebug/cookies/breakpoints",
 24     "firebug/cookies/cookiePermissions",
 25     "firebug/cookies/cookieClipboard",
 26 ],
 27 function(Xpcom, Obj, Locale, Domplate, Dom, Options, Persist, Str, Http, Css, Events, Arr, Search,
 28     MenuUtils, CookieReps, HeaderResizer, CookieObserver, CookieUtils, Cookie, Breakpoints,
 29     CookiePermissions, CookieClipboard) {
 30 
 31 with (Domplate) {
 32 
 33 // ********************************************************************************************* //
 34 // Constants
 35 
 36 const Cc = Components.classes;
 37 const Ci = Components.interfaces;
 38 
 39 // Cookies preferences
 40 const showRejectedCookies = "cookies.showRejectedCookies";
 41 const lastSortedColumn = "cookies.lastSortedColumn";
 42 const hiddenColsPref = "cookies.hiddenColumns";
 43 const removeConfirmation = "cookies.removeConfirmation";
 44 
 45 // Services
 46 var cookieManager = Xpcom.CCSV("@mozilla.org/cookiemanager;1", "nsICookieManager2");
 47 
 48 const panelName = "cookies";
 49 
 50 // ********************************************************************************************* //
 51 // Panel Implementation
 52 
 53 /**
 54  * @panel This class represents the Cookies panel that is displayed within
 55  * Firebug UI.
 56  */
 57 function CookiePanel() {}
 58 
 59 CookiePanel.prototype = Obj.extend(Firebug.ActivablePanel,
 60 /** @lends CookiePanel */
 61 {
 62     name: panelName,
 63     title: Locale.$STR("cookies.Panel"),
 64     searchable: true,
 65     breakable: true,
 66     order: 200, // Place just after the Net panel.
 67 
 68     initialize: function(context, doc)
 69     {
 70         // xxxHonza
 71         // This initialization is made as soon as the Cookies panel
 72         // is opened the first time.
 73         // This means that columns are *not* resizeable within the console
 74         // (rejected cookies) till this activation isn't executed.
 75 
 76         // Initialize event listeners before the ancestor is called.
 77         var hcr = HeaderResizer;
 78         this.onMouseClick = Obj.bind(hcr.onMouseClick, hcr);
 79         this.onMouseDown = Obj.bind(hcr.onMouseDown, hcr);
 80         this.onMouseMove = Obj.bind(hcr.onMouseMove, hcr);
 81         this.onMouseUp = Obj.bind(hcr.onMouseUp, hcr);
 82         this.onMouseOut = Obj.bind(hcr.onMouseOut, hcr);
 83 
 84         this.onContextMenu = Obj.bind(this.onContextMenu, this);
 85 
 86         Firebug.ActivablePanel.initialize.apply(this, arguments);
 87 
 88         Firebug.ConsolePanel.prototype.addListener(this);
 89 
 90         // Just after the initialization, so the this.document member is set.
 91         Firebug.CookieModule.addStyleSheet(this);
 92 
 93         this.refresh();
 94     },
 95 
 96     shutdown: function()
 97     {
 98         Firebug.ConsolePanel.prototype.removeListener(this);
 99     },
100 
101     /**
102      * Renders list of cookies displayed within the Cookies panel.
103      */
104     refresh: function()
105     {
106         if (!Firebug.CookieModule.isEnabled(this.context))
107             return;
108 
109         // Create cookie list table.
110         this.table = CookieReps.CookieTable.createTable(this.panelNode);
111 
112         // Cookies are displayed only for web pages.
113         var location = this.context.window.location;
114         if (!location)
115             return;
116 
117         var protocol = location.protocol;
118         if (protocol.indexOf("http") != 0)
119             return;
120 
121         // Get list of cookies for the current page.
122         var cookies = [];
123         var iter = cookieManager.enumerator;
124         while (iter.hasMoreElements())
125         {
126             var cookie = iter.getNext();
127             if (!cookie)
128                 break;
129 
130             cookie = cookie.QueryInterface(Ci.nsICookie2);
131             if (!CookieObserver.isCookieFromContext(this.context, cookie))
132                 continue;
133 
134             var cookieWrapper = new Cookie(CookieUtils.makeCookieObject(cookie));
135             cookies.push(cookieWrapper);
136         }
137 
138         // If the filter allow it, display all rejected cookies as well.
139         if (Options.get(showRejectedCookies))
140         {
141             // xxxHonza the this.context.cookies is sometimes null, but
142             // this must be because FB isn't correctly initialized.
143             if (!this.context.cookies)
144             {
145                 if (FBTrace.DBG_COOKIES) 
146                 {
147                     FBTrace.sysout(
148                         "cookies.Cookie context isn't properly initialized - ERROR: " +
149                         this.context.getName());
150                 }
151                 return;
152             }
153 
154             var activeHosts = this.context.cookies.activeHosts;
155             for (var hostName in activeHosts)
156             {
157                 var host = activeHosts[hostName];
158                 if (!host.rejected)
159                     continue;
160 
161                 var receivedCookies = host.receivedCookies;
162                 if (receivedCookies)
163                     cookies = Arr.extendArray(cookies, receivedCookies);
164             }
165         }
166 
167         // Generate HTML list of cookies using domplate.
168         if (cookies.length)
169         {
170             var header = Dom.getElementByClass(this.table, "cookieHeaderRow");
171             var tag = CookieReps.CookieRow.cookieTag;
172             var row = tag.insertRows({cookies: cookies}, header)[0];
173             for (var i=0; i<cookies.length; i++)
174             {
175                 var cookie = cookies[i];
176                 cookie.row = row;
177 
178                 Breakpoints.updateBreakpoint(this.context, cookie);
179                 row = row.nextSibling;
180             }
181         }
182 
183         if (FBTrace.DBG_COOKIES)
184             FBTrace.sysout("cookies.Cookie list refreshed.", cookies);
185 
186         // Sort automaticaly the last sorted column. The preference stores
187         // two things: name of the sorted column and sort direction asc|desc.
188         // Example: colExpires asc
189         var prefValue = Options.get(lastSortedColumn);
190         if (prefValue) {
191             var values = prefValue.split(" ");
192             CookieReps.CookieTable.sortColumn(this.table, values[0], values[1]);
193         }
194 
195         // Update visibility of columns according to the preferences
196         var hiddenCols = Options.get(hiddenColsPref);
197         if (hiddenCols)
198             this.table.setAttribute("hiddenCols", hiddenCols);
199     },
200 
201     initializeNode: function(oldPanelNode)
202     {
203         if (FBTrace.DBG_COOKIES)
204             FBTrace.sysout("cookies.CookiePanel.initializeNode");
205 
206         // xxxHonza 
207         // This method isn't called when FB UI is detached. So, the columns
208         // are *not* resizable when FB is open in external window.
209 
210         // Register event handlers for table column resizing.
211         this.document.addEventListener("click", this.onMouseClick, true);
212         this.document.addEventListener("mousedown", this.onMouseDown, true);
213         this.document.addEventListener("mousemove", this.onMouseMove, true);
214         this.document.addEventListener("mouseup", this.onMouseUp, true);
215         this.document.addEventListener("mouseout", this.onMouseOut, true);
216 
217         this.panelNode.addEventListener("contextmenu", this.onContextMenu, false);
218     },
219 
220     destroyNode: function()
221     {
222         if (FBTrace.DBG_COOKIES)
223             FBTrace.sysout("cookies.CookiePanel.destroyNode");
224 
225         this.document.removeEventListener("mouseclick", this.onMouseClick, true);
226         this.document.removeEventListener("mousedown", this.onMouseDown, true);
227         this.document.removeEventListener("mousemove", this.onMouseMove, true);
228         this.document.removeEventListener("mouseup", this.onMouseUp, true);
229         this.document.removeEventListener("mouseout", this.onMouseOut, true);
230 
231         this.panelNode.removeEventListener("contextmenu", this.onContextMenu, false);
232     },
233 
234     onContextMenu: function(event)
235     {
236         Breakpoints.onContextMenu(this.context, event);
237     },
238 
239     detach: function(oldChrome, newChrome)
240     {
241         Firebug.ActivablePanel.detach.apply(this, arguments);
242     },
243 
244     reattach: function(doc)
245     {
246         Firebug.ActivablePanel.reattach.apply(this, arguments);
247     },
248 
249     clear: function()
250     {
251         if (this.panelNode)
252             Dom.clearNode(this.panelNode);
253 
254         this.table = null;
255     },
256 
257     show: function(state)
258     {
259         // Update permission button in the toolbar.
260         CookiePermissions.updatePermButton(this.context);
261 
262         // For backward compatibility with Firebug 1.1
263         //
264         // Firebug 1.6 removes Firebug.DisabledPanelPage, simplifies the activation
265         // and the following code is not necessary any more.
266         if (Firebug.ActivableModule && Firebug.DisabledPanelPage)
267         {
268             var shouldShow = Firebug.CookieModule.isEnabled(this.context);
269             this.showToolbarButtons("fbCookieButtons", shouldShow);
270             if (!shouldShow)
271             {
272                 // The activation model has been changed in Firebug 1.4. This is 
273                 // just to keep backward compatibility.
274                 if (Firebug.DisabledPanelPage.show)
275                     Firebug.DisabledPanelPage.show(this, Firebug.CookieModule);
276                 else
277                     Firebug.CookieModule.disabledPanelPage.show(this);
278                 return;
279             }
280         }
281         else
282         {
283             this.showToolbarButtons("fbCookieButtons", true); 
284         }
285 
286         if (Firebug.chrome.setGlobalAttribute)
287         {
288             Firebug.chrome.setGlobalAttribute("cmd_firebug_resumeExecution", "breakable", "true");
289             Firebug.chrome.setGlobalAttribute("cmd_firebug_resumeExecution", "tooltiptext",
290                 Locale.$STR("cookies.Break On Cookie"));
291         }
292     },
293 
294     hide: function()
295     {
296         this.showToolbarButtons("fbCookieButtons", false);
297     },
298 
299     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
300     // Options menu
301 
302     getOptionsMenuItems: function(context)
303     {
304         return [
305             MenuUtils.optionAllowGlobally(context, "cookies.AllowGlobally",
306                 "cookies.tip.AllowGlobally", "network.cookie", "cookieBehavior"),
307             /*MenuUtils.optionMenu(context, "cookies.clearWhenDeny",
308                 "cookies.tip.clearWhenDeny", Firebug.prefDomain, clearWhenDeny),*/
309             MenuUtils.optionMenu(context, "cookies.Confirm cookie removal",
310                 "cookies.tip.Confirm cookie removal", Firebug.prefDomain, removeConfirmation)
311         ];
312     },
313 
314     getContextMenuItems: function(object, target)
315     {
316         var items = [];
317 
318         // If the user clicked at a cookie row, the context menu is already
319         // initialized and so, bail out.
320         var cookieRow = Dom.getAncestorByClass(target, "cookieRow");
321         if (cookieRow)
322             return items;
323 
324         // Also bail out if the user clicked on the header.
325         var header = Dom.getAncestorByClass(target, "cookieHeaderRow");
326         if (header)
327             return items;
328 
329         // Make sure default items (cmd_copy) is removed.
330         CookieReps.Rep.getContextMenuItems.apply(this, arguments);
331 
332         return items;
333     },
334 
335     search: function(text)
336     {
337         if (!text)
338             return;
339 
340         // Make previously visible nodes invisible again
341         if (this.matchSet)
342         {
343             for (var i in this.matchSet)
344                 Css.removeClass(this.matchSet[i], "matched");
345         }
346 
347         this.matchSet = [];
348 
349         function findRow(node) { return Dom.getAncestorByClass(node, "cookieRow"); }
350         var search = new Search.TextSearch(this.panelNode, findRow);
351 
352         var cookieRow = search.find(text);
353         if (!cookieRow)
354             return false;
355 
356         for (; cookieRow; cookieRow = search.findNext())
357         {
358             Css.setClass(cookieRow, "matched");
359             this.matchSet.push(cookieRow);
360         }
361 
362         return true;
363     },
364 
365     getPopupObject: function(target)
366     {
367         var header = Dom.getAncestorByClass(target, "cookieHeaderRow");
368         if (header)
369             return CookieReps.CookieTable;
370 
371         return Firebug.ActivablePanel.getPopupObject.apply(this, arguments);
372     },
373 
374     findRepObject: function(cookie)
375     {
376         var strippedHost = CookieUtils.makeStrippedHost(cookie.host);
377 
378         var result = null;
379         this.enumerateCookies(function(rep)
380         {
381             if (rep.rawHost == strippedHost &&
382                 rep.cookie.name == cookie.name &&
383                 rep.cookie.path == cookie.path)
384             {
385                 result = rep;
386                 return true; // break iteration
387             }
388         });
389 
390         return result;
391     },
392 
393     supportsObject: function(object)
394     {
395         return object instanceof Cookie;
396     },
397 
398     updateSelection: function(cookie)
399     {
400         var repCookie = this.findRepObject(cookie.cookie);
401         if (!repCookie)
402             return;
403 
404         CookieReps.CookieRow.toggleRow(repCookie.row, true);
405         Dom.scrollIntoCenterView(repCookie.row);
406     },
407 
408     enumerateCookies: function(fn)
409     {
410         if (!this.table)
411             return;
412 
413         var rows = Dom.getElementsByClass(this.table, "cookieRow");
414         for (var i=0; i<rows.length; i++)
415         {
416             var cookie = Firebug.getRepObject(rows[i]);
417             if (!cookie)
418                 continue;
419 
420             if (fn(cookie))
421                 break;
422         }
423     },
424 
425     getEditor: function(target, value)
426     {
427         if (!this.conditionEditor)
428             this.conditionEditor = new Breakpoints.ConditionEditor(this.document);
429         return this.conditionEditor;
430     },
431 
432     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
433     // Support for Break On Next
434 
435     breakOnNext: function(breaking)
436     {
437         this.context.breakOnCookie = breaking;
438 
439         if (FBTrace.DBG_COOKIES)
440             FBTrace.sysout("cookies.breakOnNext; " + context.breakOnCookie + ", " +
441                 context.getName());
442     },
443 
444     shouldBreakOnNext: function()
445     {
446         return this.context.breakOnCookie;
447     },
448 
449     getBreakOnNextTooltip: function(enabled)
450     {
451         return (enabled ? Locale.$STR("cookies.Disable Break On Cookie") :
452             Locale.$STR("cookies.Break On Cookie"));
453     },
454 
455     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
456     // Console Panel Listeners
457 
458     onFilterSet: function(logTypes)
459     {
460         logTypes.cookies = 1;
461     },
462 
463     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
464     // Panel Activation
465 
466     onActivationChanged: function(enable)
467     {
468         if (FBTrace.DBG_COOKIES || FBTrace.DBG_ACTIVATION)
469             FBTrace.sysout("cookies.CookiePanel.onActivationChanged; " + enable);
470 
471         if (enable)
472         {
473             Firebug.CookieModule.addObserver(this);
474             Firebug.Debugger.addListener(Firebug.CookieModule.DebuggerListener);
475             Firebug.Console.addListener(Firebug.CookieModule.ConsoleListener);
476         }
477         else
478         {
479             Firebug.CookieModule.removeObserver(this);
480             Firebug.Debugger.removeListener(Firebug.CookieModule.DebuggerListener);
481             Firebug.Console.removeListener(Firebug.CookieModule.ConsoleListener);
482         }
483     },
484 
485     // Support for info tips.
486     showInfoTip: function(infoTip, target, x, y)
487     {
488         var row = Dom.getAncestorByClass(target, "cookieRow");
489         if (row && row.repObject)
490         {
491             if (Dom.getAncestorByClass(target, "cookieSizeCol") || 
492                 Dom.getAncestorByClass(target, "cookieRawSizeCol"))
493             {
494                 var infoTipCookieId = "cookiesize-"+row.repObject.name;
495                 if (infoTipCookieId == this.infoTipCookieId && row.repObject == this.infoTipCookie)
496                     return true;
497 
498                 this.infoTipCookieId = infoTipCookieId;
499                 this.infoTipCookie = row.repObject;
500                 return this.populateSizeInfoTip(infoTip, row.repObject);
501             }
502         }
503 
504         delete this.infoTipCookieId;
505         return false;
506     },
507     
508     populateSizeInfoTip: function(infoTip, cookie)
509     {
510         CookieReps.SizeInfoTip.render(cookie, infoTip);
511         return true;
512     },
513 }); 
514 
515 // ********************************************************************************************* //
516 // Cookie Breakpoints
517 
518 /**
519  * @class Represents {@link Firebug.Debugger} listener. This listener is reponsible for
520  * providing a list of cookie-breakpoints into the Breakpoints side-panel.
521  */
522 Firebug.CookieModule.DebuggerListener =
523 {
524     getBreakpoints: function(context, groups)
525     {
526         if (!context.cookies.breakpoints.isEmpty())
527             groups.push(context.cookies.breakpoints);
528     }
529 };
530 
531 // ********************************************************************************************* //
532 // Custom output in the Console panel for: document.cookie
533 
534 Firebug.CookieModule.ConsoleListener =
535 {
536     tag:
537         DIV({_repObject: "$object"},
538             DIV({"class": "documentCookieBody"})
539         ),
540 
541     log: function(context, object, className, sourceLink)
542     {
543         //xxxHonza: chromebug says it's null sometimes.
544         if (!context)
545             return;
546 
547         if (object !== context.window.document.cookie)
548             return;
549 
550         // Parse "document.cookie" string.
551         var cookies = CookieUtils.parseSentCookiesFromString(object);
552         if (!cookies || !cookies.length)
553             return;
554 
555         // Create empty log row that serves as a container for list of cookies
556         // crated from the document.cookie property.
557         var appendObject = Firebug.ConsolePanel.prototype.appendObject;
558         var row = Firebug.ConsoleBase.logRow(appendObject, object, context,
559             "documentCookie", this, null, true);
560 
561         var rowBody = Dom.getElementByClass(row, "documentCookieBody");
562         CookieReps.CookieTable.render(cookies, rowBody);
563     },
564 
565     logFormatted: function(context, objects, className, sourceLink)
566     {
567     }
568 };
569 
570 // ********************************************************************************************* //
571 // Registration
572 
573 Firebug.registerPanel(CookiePanel);
574 
575 return CookiePanel;
576 
577 // ********************************************************************************************* //
578 }});
579 
580