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/system",
 17     "firebug/cookies/baseObserver",
 18     "firebug/cookies/menuUtils",
 19     "firebug/cookies/cookieUtils",
 20     "firebug/cookies/cookie",
 21     "firebug/cookies/breakpoints",
 22     "firebug/cookies/cookieEvents",
 23     "firebug/cookies/cookiePermissions",
 24     "firebug/cookies/editCookie",
 25     "firebug/cookies/cookieClipboard",
 26 ],
 27 function(Xpcom, Obj, Locale, Domplate, Dom, Options, Persist, Str, Http, Css, Events, Arr, System,
 28     BaseObserver, MenuUtils, CookieUtils, Cookie, Breakpoints, CookieEvents,
 29     CookiePermissions, EditCookie, CookieClipboard) {
 30 
 31 with (Domplate) {
 32 
 33 // ********************************************************************************************* //
 34 // Constants
 35 
 36 const Cc = Components.classes;
 37 const Ci = Components.interfaces;
 38 
 39 const lastSortedColumn = "cookies.lastSortedColumn";
 40 const hiddenColsPref = "cookies.hiddenColumns";
 41 
 42 const panelName = "cookies";
 43 
 44 // ********************************************************************************************* //
 45 // Templates Helpers
 46 
 47 // Object with all rep CookieReps.
 48 var CookieReps = {};
 49 
 50 /**
 51  * @domplate Basic template for Cookies panel UI.
 52  */
 53 CookieReps.Rep = domplate(Firebug.Rep,
 54 {
 55     getContextMenuItems: function(cookie, target, context)
 56     {
 57         // xxxHonza not sure how to do this better if the default Firebug's "Copy"
 58         // command (cmd_copy) shouldn't be there.
 59         var popup = Firebug.chrome.$("fbContextMenu");
 60         if (popup.firstChild && popup.firstChild.getAttribute("command") == "cmd_copy")
 61             popup.removeChild(popup.firstChild);
 62     }
 63 });
 64 
 65 // ********************************************************************************************* //
 66 // Cookie Template (domplate)
 67 
 68 /**
 69  * @domplate Represents a domplate template for cookie entry in the cookie list.
 70  */
 71 CookieReps.CookieRow = domplate(CookieReps.Rep,
 72 /** @lends CookieReps.CookieRow */
 73 {
 74     inspectable: false,
 75 
 76     cookieTag:
 77         FOR("cookie", "$cookies",
 78             TR({"class": "cookieRow", _repObject: "$cookie", onclick: "$onClickRow",
 79                 $sessionCookie: "$cookie|isSessionCookie",
 80                 $rejectedCookie: "$cookie|isRejected"},
 81                 TD({"class": "cookieDebugCol cookieCol"},
 82                    DIV({"class": "sourceLine cookieRowHeader", onclick: "$onClickRowHeader"},
 83                         " "
 84                    )
 85                 ),
 86                 TD({"class": "cookieNameCol cookieCol"},
 87                     DIV({"class": "cookieNameLabel cookieLabel"}, "$cookie|getName")
 88                 ),
 89                 TD({"class": "cookieValueCol cookieCol"},
 90                     DIV({"class": "cookieValueLabel cookieLabel"}, 
 91                         SPAN("$cookie.cookie.value|getValue")
 92                     )
 93                 ),
 94                 TD({"class": "cookieRawValueCol cookieCol"},
 95                     DIV({"class": "cookieRawValueLabel cookieLabel"}, 
 96                         SPAN("$cookie.cookie.rawValue|getValue")
 97                     )
 98                 ),
 99                 TD({"class": "cookieDomainCol cookieCol"},
100                     SPAN({"class": "cookieDomainLabel cookieLabel", onclick: "$onClickDomain"}, 
101                         "$cookie|getDomain")
102                 ),
103                 TD({"class": "cookieRawSizeCol cookieCol"},
104                     DIV({"class": "cookieRawSizeLabel cookieLabel"}, "$cookie|getRawSize")
105                 ),
106                 TD({"class": "cookieSizeCol cookieCol"},
107                     DIV({"class": "cookieSizeLabel cookieLabel"}, "$cookie|getSize")
108                 ),
109                 TD({"class": "cookiePathCol cookieCol"},
110                     DIV({"class": "cookiePathLabel cookieLabel", "title": "$cookie|getPath"},
111                         SPAN("$cookie|getPath")
112                     )
113                 ),
114                 TD({"class": "cookieExpiresCol cookieCol"},
115                     DIV({"class": "cookieExpiresLabel cookieLabel"}, "$cookie|getExpires")
116                 ),
117                 TD({"class": "cookieHttpOnlyCol cookieCol"},
118                     DIV({"class": "cookieHttpOnlyLabel cookieLabel"}, "$cookie|isHttpOnly")
119                 ),
120                 TD({"class": "cookieSecurityCol cookieCol"},
121                     DIV({"class": "cookieSecurityLabel cookieLabel"}, "$cookie|isSecure")
122                 )
123             )
124         ),
125 
126     bodyRow:
127         TR({"class": "cookieInfoRow"},
128             TD({"class": "sourceLine cookieRowHeader"}),
129             TD({"class": "cookieInfoCol", colspan: 11})
130         ),
131 
132     bodyTag:
133         DIV({"class": "cookieInfoBody", _repObject: "$cookie"},
134             DIV({"class": "cookieInfoTabs"},
135                 A({"class": "cookieInfoValueTab cookieInfoTab", onclick: "$onClickTab",
136                     view: "Value"},
137                     Locale.$STR("cookies.info.valuetab.label")
138                 ),
139                 A({"class": "cookieInfoRawValueTab cookieInfoTab", onclick: "$onClickTab",
140                     view: "RawValue",
141                     $collapsed: "$cookie|hideRawValueTab"},
142                     Locale.$STR("cookies.info.rawdatatab.Raw Data")
143                 ),
144                 A({"class": "cookieInfoJsonTab cookieInfoTab", onclick: "$onClickTab",
145                     view: "Json",
146                     $collapsed: "$cookie|hideJsonTab"},
147                     Locale.$STR("cookies.info.jsontab.JSON")
148                 ),
149                 A({"class": "cookieInfoXmlTab cookieInfoTab", onclick: "$onClickTab",
150                     view: "Xml",
151                     $collapsed: "$cookie|hideXmlTab"},
152                     Locale.$STR("cookies.info.xmltab.XML")
153                 )
154             ),
155             DIV({"class": "cookieInfoValueText cookieInfoText"}),
156             DIV({"class": "cookieInfoRawValueText cookieInfoText"}),
157             DIV({"class": "cookieInfoJsonText cookieInfoText"}),
158             DIV({"class": "cookieInfoXmlText cookieInfoText"})
159         ),
160 
161     hideRawValueTab: function(cookie)
162     {
163         return (cookie.cookie.value == cookie.cookie.rawValue);
164     },
165 
166     hideJsonTab: function(cookie)
167     {
168         return cookie.getJsonValue() ? false : true;
169     },
170 
171     hideXmlTab: function(cookie)
172     {
173         return cookie.getXmlValue() ? false : true;
174     },
175 
176     getAction: function(cookie)
177     {
178         return cookie.action;
179     },
180 
181     getName: function(cookie)
182     {
183         return cookie.cookie.name;
184     },
185 
186     getValue: function(value)
187     {
188         return Str.escapeNewLines(Str.cropString(value));
189     },
190 
191     getDomain: function(cookie)
192     {
193         if (!cookie.cookie.host)
194             return "";
195 
196         return cookie.cookie.host;
197     },
198 
199     getExpires: function(cookie)
200     {
201         if (cookie.cookie.expires == undefined)
202             return "";
203 
204         // The first character is space so, if the table is sorted according
205         // to this column, all "Session" cookies are displayed at the begining.
206         if (cookie.cookie.expires == 0)
207             return " " + Locale.$STR("cookies.Session");
208 
209         try
210         {
211             // Format the expires date using the current locale.
212             var date = new Date(cookie.cookie.expires * 1000);
213             return date.toLocaleString();
214         }
215         catch (err)
216         {
217             if (FBTrace.DBG_ERRORS)
218                 FBTrace.sysout("cookies.CookieRow.getExpires; EXCEPTION " + err, err);
219         }
220 
221         return "";
222     },
223 
224     isHttpOnly: function(cookie)
225     {
226         return cookie.cookie.isHttpOnly ? "HttpOnly" : "";
227     },
228 
229     isSessionCookie: function(cookie)
230     {
231         return !cookie.cookie.expires;
232     },
233 
234     isRejected: function(cookie)
235     {
236         return !!cookie.cookie.rejected;
237     },
238     
239     getRawSize: function(cookie)
240     {
241         var size = cookie.cookie.name.length + cookie.cookie.rawValue.length;
242         return Str.formatSize(size);
243     },
244 
245     getSize: function(cookie)
246     {
247         var size = cookie.cookie.name.length + cookie.cookie.value.length;
248         return Str.formatSize(size);
249     },
250 
251     getPath: function(cookie)
252     {
253         var path = cookie.cookie.path;
254         return path ? path : "";
255     },
256 
257     isDomainCookie: function(cookie)
258     {
259         return cookie.cookie.isDomain ? Locale.$STR("cookies.domain.label") : "";
260     },
261 
262     isSecure: function(cookie)
263     {
264         return cookie.cookie.isSecure ? Locale.$STR("cookies.secure.label") : "";
265     },
266 
267     // Firebug rep support
268     supportsObject: function(cookie)
269     {
270         return cookie instanceof Cookie;
271     },
272 
273     browseObject: function(cookie, context)
274     {
275         return false;
276     },
277 
278     getRealObject: function(cookie, context)
279     {
280         return cookie.cookie;
281     },
282 
283     getContextMenuItems: function(cookie, target, context)
284     {
285         CookieReps.Rep.getContextMenuItems.apply(this, arguments);
286 
287         var items = [];
288         var rejected = cookie.cookie.rejected;
289 
290         if (!rejected)
291         {
292             items.push({
293               label: Locale.$STR("cookies.Cut"),
294               nol10n: true,
295               command: Obj.bindFixed(this.onCut, this, cookie)
296             });
297         }
298 
299         items.push({
300           label: Locale.$STR("cookies.Copy"),
301           nol10n: true,
302           command: Obj.bindFixed(this.onCopy, this, cookie)
303         });
304 
305         if (!rejected)
306         {
307             items.push({
308               label: Locale.$STR("cookies.Paste"),
309               nol10n: true,
310               disabled: CookieClipboard.isCookieAvailable() ? false : true,
311               command: Obj.bindFixed(this.onPaste, this, cookie)
312             });
313             items.push("-");
314         }
315 
316         items.push({
317           label: Locale.$STR("cookies.CopyAll"),
318           nol10n: true,
319           command: Obj.bindFixed(this.onCopyAll, this, cookie)
320         });
321 
322         if (!rejected)
323         {
324             items.push("-");
325             items.push({
326               label: Locale.$STR("cookies.Delete"),
327               nol10n: true,
328               command: Obj.bindFixed(this.onRemove, this, cookie)
329             });
330 
331             items.push("-");
332             items.push({
333               label: Locale.$STR("cookies.Edit"),
334               nol10n: true,
335               command: Obj.bindFixed(this.onEdit, this, cookie)
336             });
337 
338             if (cookie.cookie.rawValue)
339             {
340                 items.push({
341                   label: Locale.$STR("cookies.Clear Value"),
342                   nol10n: true,
343                   command: Obj.bindFixed(this.onClearValue, this, cookie)
344                 });
345             }
346         }
347 
348         // Permissions
349         var permItems = CookiePermissions.getContextMenuItems(cookie, target, context);
350         if (permItems)
351             items = items.concat(permItems);
352 
353         // Breakpoints
354         var breakOnItems = Breakpoints.getContextMenuItems(cookie, target, context);
355         if (breakOnItems)
356             items = items.concat(breakOnItems);
357 
358         return items;
359     },
360 
361     // Context menu commands
362     onCut: function(clickedCookie)
363     {
364         this.onCopy(clickedCookie);
365         this.onRemove(clickedCookie);
366     },
367 
368     onCopy: function(clickedCookie)
369     {
370         CookieClipboard.copyTo(clickedCookie);
371     },
372 
373     onCopyAll: function(clickedCookie)
374     {
375         var text = "";
376         var tbody = Dom.getAncestorByClass(clickedCookie.row, "cookieTable").firstChild;
377         for (var row = tbody.firstChild; row; row = row.nextSibling) {
378             if (Css.hasClass(row, "cookieRow") && row.repObject)
379                 text += row.repObject.toString() + "\n";
380         }
381 
382         System.copyToClipboard(text);
383     },
384 
385     onPaste: function(clickedCookie) // clickedCookie can be null if the user clicks within panel area.
386     {
387         var context = Firebug.currentContext;
388         var values = CookieClipboard.getFrom();
389         if (!values || !context)
390             return;
391 
392         if (FBTrace.DBG_COOKIES)
393             FBTrace.sysout("cookies.Get cookie values from clipboard", values);
394 
395         // Change name so it's unique and use the current host.
396         values.name = Firebug.CookieModule.getDefaultCookieName(context, values.name);
397         values.host = context.browser.currentURI.host;
398 
399         values.rawValue = values.value;
400         values.value = unescape(values.value);
401 
402         // If the expire time isn't set use the default value.
403         if (values.expires == undefined)
404             values.expires = Firebug.CookieModule.getDefaultCookieExpireTime();
405 
406         // Create/modify cookie.
407         var cookie = new Cookie(values);
408         Firebug.CookieModule.createCookie(cookie);
409 
410         if (FBTrace.DBG_COOKIES)
411             checkList(context.getPanel(panelName, true));
412     },
413 
414     onRemove: function(cookie)
415     {
416         // Get the real XPCOM cookie object and remove it.
417         var realCookie = cookie.cookie;
418         if (!cookie.cookie.rejected)
419             Firebug.CookieModule.removeCookie(realCookie.host, realCookie.name, realCookie.path);
420     },
421 
422     onEdit: function(cookie)
423     {
424         var params = {
425             cookie: cookie.cookie,
426             action: "edit",
427             window: null,
428             EditCookie: EditCookie,
429             Firebug: Firebug,
430             FBTrace: FBTrace,
431         };
432 
433         var parent = Firebug.currentContext.chrome.window;
434         return parent.openDialog("chrome://firebug/content/cookies/editCookie.xul",
435             "_blank", "chrome,centerscreen,resizable=yes,modal=yes",
436             params);
437     },
438 
439     onClearValue: function(cookie)
440     {
441         if (FBTrace.DBG_COOKIES)
442             FBTrace.sysout("cookies.onClearValue;", cookie);
443 
444         var newCookie = new Cookie(cookie.cookie);
445         newCookie.cookie.rawValue = "";
446         Firebug.CookieModule.createCookie(newCookie);
447     },
448 
449     // Event handlers
450     onClickDomain: function(event)
451     {
452         if (Events.isLeftClick(event))
453         {
454             var domain = event.target.innerHTML;
455             if (domain)
456             {
457                 Events.cancelEvent(event);
458                 event.cancelBubble = true;
459                 //xxxHonza www.google.com (more windows are opened)
460                 // openNewTab(domain);
461             }
462         }
463     },
464 
465     onClickRowHeader: function(event)
466     {
467         Events.cancelEvent(event);
468 
469         var rowHeader = event.target;
470         if (!Css.hasClass(rowHeader, "cookieRowHeader"))
471             return;
472 
473         var row = Dom.getAncestorByClass(event.target, "cookieRow");
474         if (!row)
475             return;
476 
477         var context = Firebug.getElementPanel(row).context;
478         Breakpoints.onBreakOnCookie(context, row.repObject);
479     },
480 
481     onClickRow: function(event)
482     {
483         if (FBTrace.DBG_COOKIES)
484             FBTrace.sysout("cookies.Click on cookie row.", event);
485 
486         if (Events.isLeftClick(event))
487         {
488             var row = Dom.getAncestorByClass(event.target, "cookieRow");
489             if (row)
490             {
491                 this.toggleRow(row);
492                 Events.cancelEvent(event);
493             }
494         }
495     },
496 
497     toggleRow: function(row, forceOpen)
498     {
499         var opened = Css.hasClass(row, "opened");
500         if (opened && forceOpen)
501             return;
502 
503         Css.toggleClass(row, "opened");
504 
505         if (Css.hasClass(row, "opened"))
506         {
507             var bodyRow = this.bodyRow.insertRows({}, row)[0];
508             var bodyCol = Dom.getElementByClass(bodyRow, "cookieInfoCol");
509             var cookieInfo = this.bodyTag.replace({cookie: row.repObject}, bodyCol);
510 
511             // If JSON or XML tabs are available select them by default.
512             if (this.selectTabByName(cookieInfo, "Json"))
513                 return;
514 
515             if (this.selectTabByName(cookieInfo, "Xml"))
516                 return;
517 
518             this.selectTabByName(cookieInfo, "Value");
519         }
520         else
521         {
522             row.parentNode.removeChild(row.nextSibling);
523         }
524     },
525 
526     selectTabByName: function(cookieInfoBody, tabName)
527     {
528         var tab = Dom.getChildByClass(cookieInfoBody, "cookieInfoTabs",
529             "cookieInfo" + tabName + "Tab");
530 
531         // Don't select collapsed tabs. 
532         if (tab && !Css.hasClass(tab, "collapsed"))
533             return this.selectTab(tab);
534 
535         return false;
536     },
537 
538     onClickTab: function(event)
539     {
540         this.selectTab(event.currentTarget);
541     },
542 
543     selectTab: function(tab)
544     {
545         var cookieInfoBody = tab.parentNode.parentNode;
546 
547         var view = tab.getAttribute("view");
548         if (cookieInfoBody.selectedTab)
549         {
550             cookieInfoBody.selectedTab.removeAttribute("selected");
551             cookieInfoBody.selectedText.removeAttribute("selected");
552         }
553 
554         var textBodyName = "cookieInfo" + view + "Text";
555 
556         cookieInfoBody.selectedTab = tab;
557         cookieInfoBody.selectedText = Dom.getChildByClass(cookieInfoBody, textBodyName);
558 
559         cookieInfoBody.selectedTab.setAttribute("selected", "true");
560         cookieInfoBody.selectedText.setAttribute("selected", "true");
561 
562         var cookie = Firebug.getRepObject(cookieInfoBody);
563         var context = Firebug.getElementPanel(cookieInfoBody).context;
564         this.updateInfo(cookieInfoBody, cookie, context);
565 
566         return true;
567     },
568 
569     updateRow: function(cookie, context)
570     {
571         var panel = context.getPanel(panelName, true);
572         if (!panel)
573             return;
574 
575         var parent = cookie.row.parentNode;
576         var nextSibling = cookie.row.nextSibling;
577         parent.removeChild(cookie.row);
578 
579         var row = CookieReps.CookieRow.cookieTag.insertRows({cookies: [cookie]}, 
580             panel.table.lastChild.lastChild)[0];
581 
582         var opened = Css.hasClass(cookie.row, "opened");
583 
584         cookie.row = row;
585         row.repObject = cookie;
586 
587         if (nextSibling && row.nextSibling != nextSibling)
588         {
589             parent.removeChild(cookie.row);
590             parent.insertBefore(row, nextSibling);
591         }
592 
593         if (opened)
594             Css.setClass(row, "opened");
595 
596         Breakpoints.updateBreakpoint(context, cookie);
597     },
598 
599     updateInfo: function(cookieInfoBody, cookie, context)
600     {
601         var tab = cookieInfoBody.selectedTab;
602         if (Css.hasClass(tab, "cookieInfoValueTab"))
603         {
604             var valueBox = Dom.getChildByClass(cookieInfoBody, "cookieInfoValueText");
605             if (!cookieInfoBody.valuePresented)
606             {
607                 cookieInfoBody.valuePresented = true;
608 
609                 var text = cookie.cookie.value;
610                 if (text != undefined)
611                     Str.insertWrappedText(text, valueBox);
612             }
613         }
614         else if (Css.hasClass(tab, "cookieInfoRawValueTab"))
615         {
616             var valueBox = Dom.getChildByClass(cookieInfoBody, "cookieInfoRawValueText");
617             if (!cookieInfoBody.rawValuePresented)
618             {
619                 cookieInfoBody.rawValuePresented = true;
620 
621                 var text = cookie.cookie.rawValue;
622                 if (text != undefined)
623                     Str.insertWrappedText(text, valueBox);
624             }
625         }
626         else if (Css.hasClass(tab, "cookieInfoJsonTab"))
627         {
628             var valueBox = Dom.getChildByClass(cookieInfoBody, "cookieInfoJsonText");
629             if (!cookieInfoBody.jsonPresented)
630             {
631                 cookieInfoBody.jsonPresented = true;
632 
633                 var jsonObject = cookie.getJsonValue();
634                 if (jsonObject) {
635                     Firebug.DOMPanel.DirTable.tag.replace(
636                         {object: jsonObject, toggles: this.toggles}, valueBox);
637                 }
638             }
639         }
640         else if (Css.hasClass(tab, "cookieInfoXmlTab"))
641         {
642             var valueBox = Dom.getChildByClass(cookieInfoBody, "cookieInfoXmlText");
643             if (!cookieInfoBody.xmlPresented)
644             {
645                 cookieInfoBody.xmlPresented = true;
646 
647                 var docElem = cookie.getXmlValue();
648                 if (docElem) {
649                     var tag = Firebug.HTMLPanel.CompleteElement.getNodeTag(docElem);
650                     tag.replace({object: docElem}, valueBox);
651                 }
652             }
653         }
654     },
655 
656     updateTabs: function(cookieInfoBody, cookie, context)
657     {
658         // Iterate over all info-tabs and update visibility.
659         var cookieInfoTabs = Dom.getElementByClass(cookieInfoBody, "cookieInfoTabs");
660         var tab = cookieInfoTabs.firstChild;
661         while (tab)
662         {
663             var view = tab.getAttribute("view");
664             var hideTabCallback = CookieReps.CookieRow["hide" + view + "Tab"];
665             if (hideTabCallback)
666             {
667                 if (hideTabCallback(cookie))
668                     Css.setClass(tab, "collapsed");
669                 else
670                     Css.removeClass(tab, "collapsed");
671             }
672 
673             tab = tab.nextSibling;
674         }
675 
676         // If the selected tab was collapsed, make sure another one is selected.
677         if (Css.hasClass(cookieInfoBody.selectedTab, "collapsed"))
678         {
679             if (this.selectTabByName(cookieInfoBody, "Json"))
680                 return;
681 
682             if (this.selectTabByName(cookieInfoBody, "Xml"))
683                 return;
684 
685             this.selectTabByName(cookieInfoBody, "Value");
686         }
687     }
688 });
689 
690 // ********************************************************************************************* //
691 // Console Event Templates (domplate)
692 
693 /**
694  * @domplate This template is used for displaying cookie-changed events
695  * (except of "clear") in the Console tab.
696  */
697 CookieReps.CookieChanged = domplate(CookieReps.Rep,
698 {
699     inspectable: false,
700 
701     // Console
702     tag:
703         DIV({"class": "cookieEvent", _repObject: "$object"},
704             TABLE({cellpadding: 0, cellspacing: 0},
705                 TBODY(
706                     TR(
707                         TD({width: "100%"},
708                             SPAN(Locale.$STR("cookies.console.cookie"), " "),
709                             SPAN({"class": "cookieNameLabel", onclick: "$onClick"}, 
710                                 "$object|getName", 
711                                 " "),
712                             SPAN({"class": "cookieActionLabel"}, 
713                                 "$object|getAction", 
714                                 ".  "),
715                             SPAN({"class": "cookieValueLabel"}, 
716                                 "$object|getValue")
717                         ),
718                         TD(
719                             SPAN({"class": "cookieDomainLabel", onclick: "$onClickDomain",
720                                 title: "$object|getOriginalURI"}, "$object|getDomain"),
721                             SPAN(" ") 
722                         )
723                     )
724                 )
725             )
726         ),
727 
728     // Event handlers
729     onClick: function(event)
730     {
731         if (!Events.isLeftClick(event))
732             return;
733 
734         var target = event.target;
735 
736         // Get associated nsICookie object.
737         var cookieEvent = Firebug.getRepObject(target);
738         if (!cookieEvent)
739             return;
740 
741         var cookieWrapper = new Cookie(CookieUtils.makeCookieObject(cookieEvent.cookie));
742         var context = Firebug.getElementPanel(target).context;
743         context.chrome.select(cookieWrapper, panelName);
744     },
745 
746     onClickDomain: function(event)
747     {
748     },
749 
750     getOriginalURI: function(cookieEvent)
751     {
752         var context = cookieEvent.context;
753         var strippedHost = cookieEvent.rawHost;
754 
755         if (!context.cookies.activeCookies)
756             return strippedHost;
757 
758         var cookie = cookieEvent.cookie;
759         var activeCookies = context.cookies.activeCookies[cookie.host];
760         if (!activeCookies)
761             return strippedHost;
762 
763         var activeCookie = activeCookies[CookieUtils.getCookieId(cookie)];
764 
765         var originalURI;
766         if (activeCookie)
767             originalURI = activeCookie.originalURI.spec;
768         else 
769             originalURI = cookieEvent.rawHost;
770 
771         if (FBTrace.DBG_COOKIES)
772         {
773             FBTrace.sysout("cookies.context.cookies.activeCookies[" + cookie.host + "]",
774                 activeCookies);
775 
776             FBTrace.sysout("cookies.Original URI for: " + CookieUtils.getCookieId(cookie) + 
777                 " is: " + originalURI, activeCookie);
778         }
779 
780         return originalURI;
781     },
782 
783     getAction: function(cookieEvent)
784     {
785         // Return properly localized action.
786         switch(cookieEvent.action)
787         {
788           case "deleted":
789               return Locale.$STR("cookies.console.deleted");
790           case "added":
791               return Locale.$STR("cookies.console.added");
792           case "changed":
793               return Locale.$STR("cookies.console.changed");
794           case "cleared":
795               return Locale.$STR("cookies.console.cleared");
796         }
797 
798         return "";
799     },
800 
801     getName: function(cookieEvent)
802     {
803         return cookieEvent.cookie.name;
804     },
805 
806     getValue: function(cookieEvent)
807     {
808         return Str.cropString(cookieEvent.cookie.value, 75);
809     },
810 
811     getDomain: function(cookieEvent)
812     {
813         return cookieEvent.cookie.host;
814     },
815 
816     // Firebug rep support
817     supportsObject: function(cookieEvent)
818     {
819         return cookieEvent instanceof CookieEvents.CookieChangedEvent;
820     },
821 
822     browseObject: function(cookieEvent, context)
823     {
824         return false;
825     },
826 
827     getRealObject: function(cookieEvent, context)
828     {
829         return cookieEvent;
830     },
831 
832     // Context menu
833     getContextMenuItems: function(cookieEvent, target, context)
834     {
835         CookieReps.Rep.getContextMenuItems.apply(this, arguments);
836     }
837 });
838 
839 // ********************************************************************************************* //
840 
841 /**
842  * @domplate Represents a domplate template for displaying rejected cookies.
843  */
844 CookieReps.CookieRejected = domplate(CookieReps.Rep,
845 /** @lends CookieReps.CookieRejected */
846 {
847     inspectable: false,
848 
849     tag:
850         DIV({"class": "cookieEvent", _repObject: "$object"},
851             TABLE({cellpadding: 0, cellspacing: 0},
852                 TBODY(
853                     TR(
854                         TD({width: "100%"},
855                             SPAN({"class": "cookieRejectedLabel"},
856                                 Locale.$STR("cookies.console.cookiesrejected")),
857                             " ",
858                             SPAN({"class": "cookieRejectedList"},
859                                 "$object|getCookieList")
860                         ),
861                         TD(
862                             SPAN({"class": "cookieDomainLabel", onclick: "$onClickDomain"}, 
863                                 "$object|getDomain"),
864                             SPAN(" ") 
865                         )
866                     )
867                 )
868             )
869         ),
870 
871     supportsObject: function(object)
872     {
873         return object instanceof CookieEvents.CookieRejectedEvent;
874     },
875 
876     getDomain: function(cookieEvent)
877     {
878         return cookieEvent.uri.host;
879     },
880 
881     getCookieList: function(cookieEvent)
882     {
883         var context = cookieEvent.context;
884         var activeHost = context.cookies.activeHosts[cookieEvent.uri.host];
885         var cookies = activeHost.receivedCookies;
886         if (!cookies)
887             return Locale.$STR("cookies.console.nocookiesreceived");
888 
889         var label = "";
890         for (var i=0; i<cookies.length; i++)
891             label += cookies[i].cookie.name + ((i<cookies.length-1) ? ", " : "");
892 
893         return Str.cropString(label, 75);
894     },
895 
896     onClickDomain: function(event)
897     {
898     },
899 
900     // Context menu
901     getContextMenuItems: function(cookie, target, context)
902     {
903         CookieReps.Rep.getContextMenuItems.apply(this, arguments);
904     }
905 });
906 
907 // ********************************************************************************************* //
908 
909 /**
910  * @domplate Represents a domplate template for cookie cleared event that is
911  * visualised in Firebug Console panel.
912  */
913 CookieReps.CookieCleared = domplate(CookieReps.Rep,
914 /** @lends CookieReps.CookieCleared */
915 {
916     inspectable: false,
917 
918     tag:
919         DIV({_repObject: "$object"},
920             DIV("$object|getLabel")
921         ),
922 
923     supportsObject: function(object)
924     {
925         return object instanceof CookieEvents.CookieClearedEvent;
926     },
927 
928     getLabel: function()
929     {
930         return Locale.$STR("cookies.console.cookiescleared");
931     },
932 
933     // Context menu
934     getContextMenuItems: function(cookie, target, context)
935     {
936         CookieReps.Rep.getContextMenuItems.apply(this, arguments);
937     }
938 });
939 
940 
941 CookieReps.SizeInfoTip = domplate(Firebug.Rep,
942 {
943     tag:
944         TABLE({"class": "sizeInfoTip", "id": "cookiesSizeInfoTip", role:"presentation"},
945             TBODY(
946                 FOR("size", "$sizeInfo",
947                     TAG("$size|sizeTag", {size: "$size"})
948                 )
949             )
950         ),
951 
952     sizeTag:
953         TR({"class": "sizeInfoRow"},
954             TD({"class": "sizeInfoLabelCol"}, "$size.label"),
955             TD({"class": "sizeInfoSizeCol"}, "$size|formatSize"),
956             TD({"class": "sizeInfoDetailCol"}, "$size|formatNumber")
957         ),
958 
959     formatSize: function(size)
960     {
961         return Str.formatSize(size.size);
962     },
963 
964     formatNumber: function(size)
965     {
966         return size.size && size.size >= 1024 ? "(" + Str.formatNumber(size.size) + " B)" : "";
967     },
968 
969     render: function(cookie, parentNode)
970     {
971         var size = cookie.getSize();
972         var rawSize = cookie.getRawSize();
973         var sizeInfo = [];
974 
975         sizeInfo.push({label: Locale.$STR("cookie.sizeinfo.Size"), size: size});
976 
977         if (size != rawSize)
978             sizeInfo.push({label: Locale.$STR("cookie.sizeinfo.Raw_Size"), size: rawSize});
979 
980         this.tag.replace({sizeInfo: sizeInfo}, parentNode);
981     },
982 });
983 
984 // ********************************************************************************************* //
985 // Header Template (domplate)
986 
987 /**
988  * @domplate Represents a template for basic cookie list layout. This
989  * template also includes a header and related functionality (such as sorting).
990  */
991 CookieReps.CookieTable = domplate(CookieReps.Rep,
992 /** @lends CookieReps.CookieTable */
993 {
994     inspectable: false,
995 
996     tableTag:
997         TABLE({"class": "cookieTable", cellpadding: 0, cellspacing: 0, hiddenCols: ""},
998             TBODY(
999                 TR({"class": "cookieHeaderRow", onclick: "$onClickHeader"},
1000                     TD({id: "cookieBreakpointBar", width: "1%", "class": "cookieHeaderCell"},
1001                         " "
1002                     ),
1003                     TD({id: "colName", role: "columnheader",
1004                         "class": "cookieHeaderCell alphaValue a11yFocus"},
1005                         DIV({"class": "cookieHeaderCellBox",
1006                             title: Locale.$STR("cookies.header.name.tooltip")},
1007                         Locale.$STR("cookies.header.name"))
1008                     ),
1009                     TD({id: "colValue", role: "columnheader",
1010                         "class": "cookieHeaderCell alphaValue a11yFocus"},
1011                         DIV({"class": "cookieHeaderCellBox",
1012                             title: Locale.$STR("cookies.header.value.tooltip")}, 
1013                         Locale.$STR("cookies.header.value"))
1014                     ),
1015                     TD({id: "colRawValue", role: "columnheader",
1016                         "class": "cookieHeaderCell alphaValue a11yFocus"},
1017                         DIV({"class": "cookieHeaderCellBox",
1018                             title: Locale.$STR("cookies.header.rawValue.tooltip")}, 
1019                             Locale.$STR("cookies.header.rawValue"))
1020                     ),
1021                     TD({id: "colDomain", role: "columnheader",
1022                         "class": "cookieHeaderCell alphaValue a11yFocus"},
1023                         DIV({"class": "cookieHeaderCellBox",
1024                             title: Locale.$STR("cookies.header.domain.tooltip")}, 
1025                         Locale.$STR("cookies.header.domain"))
1026                     ),
1027                     TD({id: "colRawSize", role: "columnheader",
1028                         "class": "cookieHeaderCell a11yFocus"},
1029                         DIV({"class": "cookieHeaderCellBox",
1030                             title: Locale.$STR("cookies.header.size.tooltip")}, 
1031                         Locale.$STR("cookies.header.rawSize"))
1032                     ),
1033                     TD({id: "colSize", role: "columnheader",
1034                         "class": "cookieHeaderCell a11yFocus"},
1035                         DIV({"class": "cookieHeaderCellBox",
1036                             title: Locale.$STR("cookies.header.size.tooltip")}, 
1037                         Locale.$STR("cookies.header.size"))
1038                     ),
1039                     TD({id: "colPath", role: "columnheader",
1040                         "class": "cookieHeaderCell alphaValue a11yFocus"},
1041                         DIV({"class": "cookieHeaderCellBox",
1042                             title: Locale.$STR("cookies.header.path.tooltip")}, 
1043                         Locale.$STR("cookies.header.path"))
1044                     ),
1045                     TD({id: "colExpires", role: "columnheader",
1046                         "class": "cookieHeaderCell a11yFocus"},
1047                         DIV({"class": "cookieHeaderCellBox",
1048                             title: Locale.$STR("cookies.header.expires.tooltip")}, 
1049                         Locale.$STR("cookies.header.expires"))
1050                     ),
1051                     TD({id: "colHttpOnly", role: "columnheader",
1052                         "class": "cookieHeaderCell alphaValue a11yFocus"},
1053                         DIV({"class": "cookieHeaderCellBox",
1054                             title: Locale.$STR("cookies.header.httponly.tooltip")}, 
1055                         Locale.$STR("cookies.header.httponly"))
1056                     ),
1057                     TD({id: "colSecurity", role: "columnheader",
1058                         "class": "cookieHeaderCell alphaValue a11yFocus"},
1059                         DIV({"class": "cookieHeaderCellBox",
1060                             title: Locale.$STR("cookies.header.security.tooltip")}, 
1061                         Locale.$STR("cookies.header.security"))
1062                     )
1063                 )
1064             )
1065         ),
1066 
1067     onClickHeader: function(event)
1068     {
1069         if (FBTrace.DBG_COOKIES)
1070             FBTrace.sysout("cookies.onClickHeader");
1071 
1072         if (!Events.isLeftClick(event))
1073             return;
1074 
1075         var table = Dom.getAncestorByClass(event.target, "cookieTable");
1076         var column = Dom.getAncestorByClass(event.target, "cookieHeaderCell");
1077         this.sortColumn(table, column);
1078     },
1079 
1080     sortColumn: function(table, col, direction)
1081     {
1082         if (!col)
1083             return;
1084 
1085         if (typeof(col) == "string")
1086         {
1087             var doc = table.ownerDocument;
1088             col = doc.getElementById(col);
1089         }
1090 
1091         if (!col)
1092             return;
1093 
1094         var numerical = !Css.hasClass(col, "alphaValue");
1095 
1096         var colIndex = 0;
1097         for (col = col.previousSibling; col; col = col.previousSibling)
1098             ++colIndex;
1099 
1100         this.sort(table, colIndex, numerical, direction);
1101     },
1102 
1103     sort: function(table, colIndex, numerical, direction)
1104     {
1105         var tbody = table.lastChild;
1106         var headerRow = tbody.firstChild;
1107 
1108         // Remove class from the currently sorted column
1109         var headerSorted = Dom.getChildByClass(headerRow, "cookieHeaderSorted");
1110         Css.removeClass(headerSorted, "cookieHeaderSorted");
1111 
1112         // Mark new column as sorted.
1113         var header = headerRow.childNodes[colIndex];
1114         Css.setClass(header, "cookieHeaderSorted");
1115 
1116         // If the column is already using required sort direction, bubble out.
1117         if ((direction == "desc" && header.sorted == 1) ||
1118             (direction == "asc" && header.sorted == -1))
1119             return;
1120 
1121         var values = [];
1122         for (var row = tbody.childNodes[1]; row; row = row.nextSibling)
1123         {
1124             var cell = row.childNodes[colIndex];
1125             var value = numerical ? parseFloat(cell.textContent) : cell.textContent;
1126 
1127             // Issue 43, expires date is formatted in the UI, so use the original cookie
1128             // value instead.
1129             if (Css.hasClass(cell, "cookieExpiresCol"))
1130                 value = row.repObject.cookie.expires;
1131 
1132             if (Css.hasClass(row, "opened"))
1133             {
1134                 var cookieInfoRow = row.nextSibling;
1135                 values.push({row: row, value: value, info: cookieInfoRow});
1136                 row = cookieInfoRow;
1137             }
1138             else
1139             {
1140                 values.push({row: row, value: value});
1141             }
1142         }
1143 
1144         values.sort(function(a, b) { return a.value < b.value ? -1 : 1; });
1145 
1146         if ((header.sorted && header.sorted == 1) || (!header.sorted && direction == "asc"))
1147         {
1148             Css.removeClass(header, "sortedDescending");
1149             Css.setClass(header, "sortedAscending");
1150 
1151             header.sorted = -1;
1152 
1153             for (var i = 0; i < values.length; ++i)
1154             {
1155                 tbody.appendChild(values[i].row);
1156                 if (values[i].info)
1157                     tbody.appendChild(values[i].info);
1158             }
1159         }
1160         else
1161         {
1162             Css.removeClass(header, "sortedAscending");
1163             Css.setClass(header, "sortedDescending");
1164 
1165             header.sorted = 1;
1166 
1167             for (var i = values.length-1; i >= 0; --i)
1168             {
1169                 tbody.appendChild(values[i].row);
1170                 if (values[i].info)
1171                     tbody.appendChild(values[i].info);
1172             }
1173         }
1174 
1175         // Remember last sorted column & direction in preferences.
1176         var prefValue = header.getAttribute("id") + " " + (header.sorted > 0 ? "desc" : "asc");
1177         Options.set(lastSortedColumn, prefValue);
1178     },
1179 
1180     supportsObject: function(object)
1181     {
1182         return (object == this);
1183     },
1184 
1185     /**
1186      * Provides menu items for header context menu.
1187      */
1188     getContextMenuItems: function(object, target, context)
1189     {
1190         CookieReps.Rep.getContextMenuItems.apply(this, arguments);
1191 
1192         var items = [];
1193 
1194         // Iterate over all columns and create a menu item for each.
1195         var table = context.getPanel(panelName, true).table;
1196         var hiddenCols = table.getAttribute("hiddenCols");
1197 
1198         var lastVisibleIndex;
1199         var visibleColCount = 0;
1200 
1201         var header = Dom.getAncestorByClass(target, "cookieHeaderRow");
1202 
1203         // Skip the first column for breakpoints.
1204         var columns = Arr.cloneArray(header.childNodes);
1205         columns.shift();
1206 
1207         for (var i=0; i<columns.length; i++)
1208         {
1209             var column = columns[i];
1210             var visible = (hiddenCols.indexOf(column.id) == -1);
1211 
1212             items.push({
1213                 label: column.textContent,
1214                 type: "checkbox",
1215                 checked: visible,
1216                 nol10n: true,
1217                 command: Obj.bindFixed(this.onShowColumn, this, context, column.id)
1218             });
1219 
1220             if (visible)
1221             {
1222                 lastVisibleIndex = i;
1223                 visibleColCount++;
1224             }
1225         }
1226 
1227         // If the last column is visible, disable its menu item.
1228         if (visibleColCount == 1)
1229             items[lastVisibleIndex].disabled = true;
1230 
1231         items.push("-");
1232         items.push({
1233             label: Locale.$STR("net.header.Reset Header"),
1234             nol10n: true, 
1235             command: Obj.bindFixed(this.onResetColumns, this, context)
1236         });
1237 
1238         return items;
1239     },
1240 
1241     onShowColumn: function(context, colId)
1242     {
1243         var table = context.getPanel(panelName, true).table;
1244         var hiddenCols = table.getAttribute("hiddenCols");
1245 
1246         // If the column is already presented in the list of hidden columns,
1247         // remove it, otherwise append.
1248         var index = hiddenCols.indexOf(colId);
1249         if (index >= 0)
1250         {
1251             table.setAttribute("hiddenCols", hiddenCols.substr(0,index-1) +
1252                 hiddenCols.substr(index+colId.length));
1253         }
1254         else
1255         {
1256             table.setAttribute("hiddenCols", hiddenCols + " " + colId);
1257         }
1258 
1259         // Store current state into the preferences.
1260         Options.set(hiddenColsPref, table.getAttribute("hiddenCols"));
1261     },
1262 
1263     onResetColumns: function(context)
1264     {
1265         var panel = context.getPanel(panelName, true);
1266         var header = Dom.getElementByClass(panel.panelNode, "cookieHeaderRow");
1267 
1268         // Reset widths
1269         var columns = header.childNodes;
1270         for (var i=0; i<columns.length; i++)
1271         {
1272             var col = columns[i];
1273             if (col.style)
1274                 col.style.width = "";
1275         }
1276 
1277         // Reset visibility.
1278         Options.clear(hiddenColsPref);
1279         panel.table.setAttribute("hiddenCols", Options.get(hiddenColsPref));
1280 
1281         // Reset also sorting (no sorting by default)
1282         var headerRow = panel.table.getElementsByClassName("cookieHeaderRow")[0];
1283         var headerSorted = Dom.getChildByClass(headerRow, "cookieHeaderSorted");
1284         Css.removeClass(headerSorted, "cookieHeaderSorted");
1285         Options.set(lastSortedColumn, "");
1286         panel.refresh();
1287     },
1288 
1289     createTable: function(parentNode)
1290     {
1291         // Create cookie table UI.
1292         var table = this.tableTag.replace({}, parentNode, this);
1293 
1294         // Update columns width according to the preferences.
1295         var header = Dom.getElementByClass(table, "cookieHeaderRow");
1296         var columns = header.getElementsByTagName("td");
1297         for (var i=0; i<columns.length; i++)
1298         {
1299             var col = columns[i];
1300             var colId = col.getAttribute("id");
1301             if (!colId || !col.style)
1302                 continue;
1303 
1304             var width = Options.get("cookies." + colId + ".width");
1305             if (width)
1306                 col.style.width = width + "px";
1307         }
1308 
1309         return table;
1310     },
1311 
1312     render: function(cookies, parentNode)
1313     {
1314         // Create basic cookie-list structure.
1315         var table = this.createTable(parentNode);
1316         var header = Dom.getElementByClass(table, "cookieHeaderRow");
1317 
1318         var tag = CookieReps.CookieRow.cookieTag;
1319         return tag.insertRows({cookies: cookies}, header);
1320     }
1321 });
1322 
1323 // ********************************************************************************************* //
1324 
1325 var OBJECTLINK = FirebugReps.OBJECTLINK;
1326 
1327 // xxxHonza: TODO
1328 CookieReps.CookieRep = domplate(CookieReps.Rep,
1329 {
1330     tag:
1331         OBJECTLINK(
1332             SPAN({"class": "objectTitle"}, "$object|getTitle")
1333         ),
1334 
1335     className: "cookie",
1336 
1337     supportsObject: function(cookie)
1338     {
1339         return cookie instanceof Cookie;
1340     },
1341 
1342     getTitle: function(cookie)
1343     {
1344         return cookie.cookie.name;
1345     },
1346 
1347     getTooltip: function(cookie)
1348     {
1349         return cookie.cookie.value;
1350     }
1351 });
1352 
1353 // ********************************************************************************************* //
1354 // Debug helpers
1355 
1356 function checkList(panel)
1357 {
1358     if (!FBTrace.DBG_COOKIES)
1359         return;
1360 
1361     if (!panel || !this.panelNode)
1362         return; 
1363 
1364     var row = Dom.getElementByClass(this.panelNode, "cookieRow");
1365     while (row)
1366     {
1367         var rep = row.repObject;
1368         if ((rep.cookie.name != row.firstChild.firstChild.innerHTML) ||
1369             (rep.cookie.path != row.childNodes[3].firstChild.innerHTML))
1370         {
1371             FBTrace("---> Check failed!");
1372             FBTrace("--->" + rep.rawHost + ", " + rep.cookie.name + ", " +
1373                 rep.cookie.path);
1374             FBTrace("    " + row.firstChild.firstChild.innerHTML + ", " +
1375                 row.childNodes[3].firstChild.innerHTML);
1376         }
1377 
1378         row = row.nextSibling;
1379     }
1380 
1381     return null;
1382 }
1383 
1384 // ********************************************************************************************* //
1385 // Firebug Registration
1386 
1387 Firebug.registerRep(
1388     //CookieReps.CookieRep,          // Cookie
1389     CookieReps.CookieTable,          // Cookie table with list of cookies
1390     CookieReps.CookieRow,            // Entry in the cookie table
1391     CookieReps.CookieChanged,        // Console: "cookie-changed" event
1392     CookieReps.CookieRejected,       // Console: "cookie-rejected" event
1393     CookieReps.CookieCleared         // Console: cookies "cleared" event
1394 );
1395 
1396 return CookieReps;
1397 
1398 // ********************************************************************************************* //
1399 }});
1400 
1401