1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/lib/object",
  5     "firebug/lib/locale",
  6     "firebug/lib/string",
  7     "firebug/lib/domplate",
  8     "firebug/lib/dom",
  9     "firebug/lib/css",
 10     "firebug/lib/events",
 11     "firebug/cookies/cookieUtils"
 12 ],
 13 function(Obj, Locale, Str, Domplate, Dom, Css, Events, CookieUtils) {
 14 
 15 with (Domplate) {
 16 
 17 // ********************************************************************************************* //
 18 // Constants
 19 
 20 const panelName = "cookies";
 21 
 22 // ********************************************************************************************* //
 23 // Implementation
 24 
 25 var Breakpoints =
 26 {
 27     breakOnCookie: function(context, cookie, action)
 28     {
 29         if (FBTrace.DBG_COOKIES)
 30             FBTrace.sysout("cookies.breakOnCookie; " + action);
 31 
 32         var halt = false;
 33         var conditionIsFalse = false;
 34 
 35         // If there is an enabled breakpoint with condition:
 36         // 1) break if the condition is evaluated to true.
 37         var bp = context.cookies.breakpoints.findBreakpoint(CookieUtils.makeCookieObject(cookie));
 38         if (bp && bp.checked)
 39         {
 40             halt = true;
 41             if (bp.condition)
 42             {
 43                 halt = bp.evaluateCondition(context, cookie);
 44                 conditionIsFalse = !halt;
 45             }
 46         }
 47 
 48         // 2) If break on next flag is set and there is no condition evaluated to false,
 49         // break with "break on next" breaking cause (this new breaking cause can override
 50         // an existing one that is set when evaluating a breakpoint condition).
 51         if (context.breakOnCookie && !conditionIsFalse)
 52         {
 53             context.breakingCause = {
 54                 title: Locale.$STR("cookies.Break On Cookie"),
 55                 message: Str.cropString(unescape(cookie.name + "; " + cookie.value), 200)
 56             };
 57             halt = true;
 58         }
 59 
 60         // Ignore if there is no reason to break.
 61         if (!halt)
 62             return;
 63 
 64         // Even if the execution was stopped at breakpoint reset the global
 65         // breakOnCookie flag.
 66         context.breakOnCookie = false;
 67 
 68         this.breakNow(context);
 69 
 70         // Clear breakpoint associated with removed cookie.
 71         if (action == "deleted")
 72         {
 73             breakpoints.removeBreakpoint(bp);
 74             context.invalidatePanels("breakpoints");
 75         }
 76     },
 77 
 78     breakNow: function(context)
 79     {
 80         if (Firebug.Breakpoint && Firebug.Breakpoint.updatePanelTab)
 81         {
 82             var panel = context.getPanel(panelName, true);
 83             Firebug.Breakpoint.updatePanelTab(panel, false);
 84             Firebug.Breakpoint.breakNow(context.getPanel(panelName, true));
 85         }
 86     },
 87 
 88     getContextMenuItems: function(cookie, target, context)
 89     {
 90         var items = [];
 91         items.push("-");
 92 
 93         var cookieName = Str.cropString(cookie.cookie.name, 40);
 94         var bp = context.cookies.breakpoints.findBreakpoint(cookie.cookie);
 95 
 96         items.push({
 97             nol10n: true,
 98             tooltiptext: Locale.$STRF("cookies.menu.tooltip.Break On Cookie", [cookieName]),
 99             label: Locale.$STRF("cookies.menu.Break On Cookie", [cookieName]),
100             type: "checkbox",
101             checked: bp != null,
102             command: Obj.bindFixed(this.onBreakOnCookie, this, context, cookie),
103         });
104 
105         if (bp)
106         {
107             items.push(
108                 {label: "cookies.menu.Edit Breakpoint Condition",
109                     command: Obj.bindFixed(this.editBreakpointCondition, this, context, cookie) }
110             );
111         }
112 
113         return items;
114     },
115 
116     onBreakOnCookie: function(context, cookie)
117     {
118         if (FBTrace.DBG_COOKIES)
119             FBTrace.sysout("cookies.breakOnCookie; ", context);
120 
121         var breakpoints = context.cookies.breakpoints;
122 
123         // Remove an existing or create a new breakpoint.
124         var row = cookie.row;
125         cookie = cookie.cookie;
126         var bp = breakpoints.findBreakpoint(cookie);
127         if (bp)
128         {
129             breakpoints.removeBreakpoint(cookie);
130             row.removeAttribute("breakpoint");
131             row.removeAttribute("disabledBreakpoint");
132         }
133         else
134         {
135             breakpoints.addBreakpoint(cookie);
136             row.setAttribute("breakpoint", "true");
137         }
138     },
139 
140     updateBreakpoint: function(context, cookie)
141     {
142         // Make sure a breakpoint is displayed.
143         var bp = context.cookies.breakpoints.findBreakpoint(cookie.cookie)
144         if (!bp)
145             return;
146 
147         var row = cookie.row;
148         row.setAttribute("breakpoint", "true");
149         row.setAttribute("disabledBreakpoint", bp.checked ? "false" : "true");
150     },
151 
152     onContextMenu: function(context, event)
153     {
154         if (!Css.hasClass(event.target, "sourceLine"))
155             return;
156 
157         var row = Dom.getAncestorByClass(event.target, "cookieRow");
158         if (!row)
159             return;
160 
161         var cookie = row.repObject;
162         var bp = context.cookies.breakpoints.findBreakpoint(cookie.cookie);
163         if (!bp)
164             return;
165 
166         this.editBreakpointCondition(context, cookie);
167         Events.cancelEvent(event);
168     },
169 
170     editBreakpointCondition: function(context, cookie)
171     {
172         var bp = context.cookies.breakpoints.findBreakpoint(cookie.cookie);
173         if (!bp)
174             return;
175 
176         var condition = bp ? bp.condition : "";
177 
178         var panel = context.getPanel(panelName);
179         panel.selectedSourceBox = cookie.row;
180         Firebug.Editor.startEditing(cookie.row, condition);
181     },
182 }
183 
184 // ********************************************************************************************* //
185 // Cookie Breakpoints
186 
187 Breakpoints.BreakpointTemplate = Domplate.domplate(Firebug.Rep,
188 {
189     inspectable: false,
190 
191     tag:
192         DIV({"class": "breakpointRow focusRow", $disabled: "$bp|isDisabled", _repObject: "$bp",
193             role: "option", "aria-checked": "$bp.checked"},
194             DIV({"class": "breakpointBlockHead"},
195                 INPUT({"class": "breakpointCheckbox", type: "checkbox",
196                     _checked: "$bp.checked", tabindex: "-1", onclick: "$onEnable"}),
197                 SPAN("$bp|getTitle"),
198                 DIV({"class": "breakpointMutationType"}, "$bp|getType"),
199                 IMG({"class": "closeButton", src: "blank.gif", onclick: "$onRemove"})
200             ),
201             DIV({"class": "breakpointCode"},
202                 SPAN("$bp|getValue")
203             )
204         ),
205 
206     getTitle: function(bp)
207     {
208         return bp.name;
209     },
210 
211     getValue: function(bp)
212     {
213         return bp.host + bp.path;
214     },
215 
216     getType: function(bp)
217     {
218         return Locale.$STR("Break On Cookie Change");
219     },
220 
221     isDisabled: function(bp)
222     {
223         return !bp.checked;
224     },
225 
226     onRemove: function(event)
227     {
228         Events.cancelEvent(event);
229 
230         var bpPanel = Firebug.getElementPanel(event.target);
231         var context = bpPanel.context;
232 
233         if (!Css.hasClass(event.target, "closeButton"))
234             return;
235 
236         // Remove from list of breakpoints.
237         var row = Dom.getAncestorByClass(event.target, "breakpointRow");
238         context.cookies.breakpoints.removeBreakpoint(row.repObject);
239 
240         bpPanel.refresh();
241 
242         var cookiePanel = context.getPanel(panelName, true);
243         if (!cookiePanel)
244             return;
245 
246         var cookie = cookiePanel.findRepObject(row.repObject);
247         if (cookie)
248         {
249             cookie.row.removeAttribute("breakpoint");
250             cookie.row.removeAttribute("disabledBreakpoint");
251         }
252     },
253 
254     onEnable: function(event)
255     {
256         var checkBox = event.target;
257         var bpRow = Dom.getAncestorByClass(checkBox, "breakpointRow");
258 
259         if (checkBox.checked)
260         {
261             Css.removeClass(bpRow, "disabled");
262             bpRow.setAttribute("aria-checked", "true");
263         }
264         else
265         {
266             Css.setClass(bpRow, "disabled");
267             bpRow.setAttribute("aria-checked", "false");
268         }
269 
270         var bp = bpRow.repObject;
271         bp.checked = checkBox.checked;
272 
273         var bpPanel = Firebug.getElementPanel(checkBox);
274 
275         var cookiePanel = bpPanel.context.getPanel(panelName, true);
276         if (!cookiePanel)
277             return;
278 
279         var cookie = cookiePanel.findRepObject(bp);
280         if (cookie)
281             cookie.row.setAttribute("disabledBreakpoint", bp.checked ? "false" : "true");
282     },
283 
284     supportsObject: function(object)
285     {
286         return object instanceof Breakpoints.Breakpoint;
287     }
288 });
289 
290 // ********************************************************************************************* //
291 // Editor for Cookie breakpoint condition.
292 
293 Breakpoints.ConditionEditor = function(doc)
294 {
295     Firebug.Breakpoint.ConditionEditor.apply(this, arguments);
296 }
297 
298 Breakpoints.ConditionEditor.prototype = Domplate.domplate(Firebug.Breakpoint.ConditionEditor.prototype,
299 {
300     endEditing: function(target, value, cancel)
301     {
302         if (cancel)
303             return;
304 
305         var cookie = target.repObject;
306         var panel = Firebug.getElementPanel(target);
307         var bp = panel.context.cookies.breakpoints.findBreakpoint(cookie.cookie);
308         if (bp)
309             bp.condition = value;
310     }
311 });
312 
313 // ********************************************************************************************* //
314 
315 /**
316  * @domplate Template for cookie breakpoint displayed in the Breakpoints side
317  * panel.
318  */
319 Breakpoints.Breakpoint = function(cookie)
320 {
321     this.name = cookie.name;
322     this.host = cookie.host;
323     this.path = cookie.path;
324 
325     this.condition = "";
326     this.checked = true;
327 
328     this.onEvaluateFails = Obj.bind(this.onEvaluateFails, this);
329     this.onEvaluateSucceeds =  Obj.bind(this.onEvaluateSucceeds, this);
330 };
331 
332 Breakpoints.Breakpoint.prototype =
333 {
334     evaluateCondition: function(context, cookie)
335     {
336         try
337         {
338             var scope = {};
339             scope["value"] = cookie.value;
340             scope["cookie"] = CookieUtils.makeCookieObject(cookie);
341 
342             // The callbacks will set this if the condition is true or if the eval faults.
343             delete context.breakingCause;
344 
345             // Construct expression to evaluate. Native JSON support is available since
346             // Firefox 3.5 and breakpoints since Firebug 1.5, which supports min Fx 3.5
347             // So, all is good.
348             var expr = "(function (){var scope = " + JSON.stringify(scope) +
349                 "; with (scope) { return " + this.condition + ";}})();"
350 
351             // Evaluate condition using Firebug's command line.
352             var rc = Firebug.CommandLine.evaluate(expr, context, null, context.window,
353                 this.onEvaluateSucceeds, this.onEvaluateFails);
354 
355             if (FBTrace.DBG_COOKIES)
356                 FBTrace.sysout("cookies.evaluateCondition; rc " + rc, {expr: expr, scope: scope});
357 
358             return !!context.breakingCause;
359         }
360         catch (err)
361         {
362             if (FBTrace.DBG_COOKIES)
363                 FBTrace.sysout("cookies.evaluateCondition; EXCEPTION", err);
364         }
365 
366         return false;
367     },
368 
369     onEvaluateSucceeds: function(result, context)
370     {
371         if (FBTrace.DBG_COOKIES)
372             FBTrace.sysout("cookies.onEvaluateSucceeds; " + result, result);
373 
374         // Don't set the breakingCause if the breakpoint condition is evaluated to false.
375         if (!result)
376             return;
377 
378         context.breakingCause = {
379             title: Locale.$STR("cookies.Break On Cookie"),
380             message: Str.cropString(unescape(this.name + "; " + this.condition + "; "), 200)
381         };
382     },
383 
384     onEvaluateFails: function(result, context)
385     {
386         if (FBTrace.DBG_COOKIES)
387             FBTrace.sysout("cookies.onEvaluateFails; " + result, result);
388 
389         context.breakingCause = {
390             title: Locale.$STR("cookies.Break On Cookie"),
391             message: Locale.$STR("cookies.Breakpoint condition evaluation fails"),
392             prevValue: this.condition, newValue:result
393         };
394     }
395 }
396 
397 // ********************************************************************************************* //
398 // Registration
399 
400 Firebug.registerRep(Breakpoints.BreakpointTemplate);
401 
402 return Breakpoints;
403 
404 // ********************************************************************************************* //
405 }});
406