1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/lib/object",
  5     "firebug/firebug",
  6     "firebug/lib/domplate",
  7     "firebug/lib/locale",
  8     "firebug/lib/events",
  9     "firebug/lib/wrapper",
 10     "firebug/lib/dom",
 11     "firebug/lib/css",
 12     "firebug/lib/string",
 13     "firebug/lib/array",
 14     "firebug/lib/persist",
 15 ],
 16 function(Obj, Firebug, Domplate, Locale, Events, Wrapper, Dom, Css, Str, Arr, Persist) {
 17 
 18 with (Domplate) {
 19 
 20 // ********************************************************************************************* //
 21 // Constants
 22 
 23 const Cc = Components.classes;
 24 const Ci = Components.interfaces;
 25 
 26 // ********************************************************************************************* //
 27 // Breakpoint Group
 28 
 29 function DOMBreakpointGroup()
 30 {
 31     this.breakpoints = [];
 32 }
 33 
 34 DOMBreakpointGroup.prototype = Obj.extend(new Firebug.Breakpoint.BreakpointGroup(),
 35 {
 36     name: "domBreakpoints",
 37     title: Locale.$STR("dom.label.DOM Breakpoints"),
 38 
 39     addBreakpoint: function(object, propName, panel, row)
 40     {
 41         var path = panel.getPropertyPath(row);
 42         path.pop();
 43 
 44         // We don't want the last dot.
 45         if (path.length > 0 && path[path.length-1] == ".")
 46             path.pop();
 47 
 48         var objectPath = path.join("");
 49         if (FBTrace.DBG_DOM)
 50             FBTrace.sysout("dom.addBreakpoint; " + objectPath, path);
 51 
 52         var bp = new Breakpoint(object, propName, objectPath, panel.context);
 53         if (bp.watchProperty());
 54             this.breakpoints.push(bp);
 55     },
 56 
 57     removeBreakpoint: function(object, propName)
 58     {
 59         var bp = this.findBreakpoint(object, propName);
 60         if (bp)
 61         {
 62             bp.unwatchProperty();
 63             Arr.remove(this.breakpoints, bp);
 64         }
 65     },
 66 
 67     matchBreakpoint: function(bp, args)
 68     {
 69         var object = args[0];
 70         var propName = args[1];
 71         return bp.object == object && bp.propName == propName;
 72     },
 73 
 74     // Persistence
 75     load: function(context)
 76     {
 77         var panelState = Persist.getPersistedState(context, "dom");
 78         if (panelState.breakpoints)
 79             this.breakpoints = panelState.breakpoints;
 80 
 81         this.enumerateBreakpoints(function(bp)
 82         {
 83             try
 84             {
 85                 var contentView = Wrapper.getContentView(context.window);
 86                 bp.object = contentView[bp.objectPath];
 87                 bp.context = context;
 88                 bp.watchProperty();
 89 
 90                 if (FBTrace.DBG_DOM)
 91                     FBTrace.sysout("dom.DOMBreakpointGroup.load; " + bp.objectPath, bp);
 92             }
 93             catch (err)
 94             {
 95                 if (FBTrace.DBG_ERROR || FBTrace.DBG_DOM)
 96                     FBTrace.sysout("dom.DOMBreakpointGroup.load; ERROR " + bp.objectPath, err);
 97             }
 98         });
 99     },
100 
101     store: function(context)
102     {
103         this.enumerateBreakpoints(function(bp)
104         {
105             bp.object = null;
106         });
107 
108         var panelState = Persist.getPersistedState(context, "dom");
109         panelState.breakpoints = this.breakpoints;
110     },
111 });
112 
113 // ********************************************************************************************* //
114 
115 function Breakpoint(object, propName, objectPath, context)
116 {
117     this.context = context;
118     this.propName = propName;
119     this.objectPath = objectPath;
120     this.object = object;
121     this.checked = true;
122 }
123 
124 Breakpoint.prototype =
125 {
126     watchProperty: function()
127     {
128         if (FBTrace.DBG_DOM)
129             FBTrace.sysout("dom.watch; property: " + this.propName);
130 
131         if (!this.object)
132             return;
133 
134         try
135         {
136             var self = this;
137             this.object.watch(this.propName, function handler(prop, oldval, newval)
138             {
139                 // XXXjjb Beware: in playing with this feature I hit too much recursion
140                 // multiple times with console.log
141                 // TODO Do something cute in the UI with the error bubble thing
142                 if (self.checked)
143                 {
144                     self.context.breakingCause = {
145                         title: Locale.$STR("dom.Break On Property"),
146                         message: Str.cropString(prop, 200),
147                         prevValue: oldval,
148                         newValue: newval
149                     };
150 
151                     Firebug.Breakpoint.breakNow(self.context.getPanel("dom"));
152                 }
153                 return newval;
154             });
155         }
156         catch (exc)
157         {
158             if (FBTrace.DBG_ERRORS)
159                 FBTrace.sysout("dom.watch; object FAILS " + exc, exc);
160             return false;
161         }
162 
163         return true;
164     },
165 
166     unwatchProperty: function()
167     {
168         if (FBTrace.DBG_DOM)
169             FBTrace.sysout("dom.unwatch; property: " + this.propName, this.object);
170 
171         if (!this.object)
172             return;
173 
174         try
175         {
176             this.object.unwatch(this.propName);
177         }
178         catch (exc)
179         {
180             if (FBTrace.DBG_ERRORS)
181                 FBTrace.sysout("dom.unwatch; object FAILS " + exc, exc);
182         }
183     }
184 }
185 
186 // ********************************************************************************************* //
187 
188 var BreakpointRep = domplate(Firebug.Rep,
189 {
190     inspectable: false,
191 
192     tag:
193         DIV({"class": "breakpointRow focusRow", $disabled: "$bp|isDisabled", _repObject: "$bp",
194             role: "option", "aria-checked": "$bp.checked"},
195             DIV({"class": "breakpointBlockHead"},
196                 INPUT({"class": "breakpointCheckbox", type: "checkbox",
197                     _checked: "$bp.checked", tabindex: "-1", onclick: "$onEnable"}),
198                 SPAN({"class": "breakpointName"}, "$bp.propName"),
199                 IMG({"class": "closeButton", src: "blank.gif", onclick: "$onRemove"})
200             ),
201             DIV({"class": "breakpointCode"},
202                 TAG("$bp.object|getObjectTag", {object: "$bp.object"})
203             )
204         ),
205 
206     getObjectTag: function(object)
207     {
208         // I am uncertain about the Firebug.currentContext but I think we are
209         // only here in panel code.
210         var rep = Firebug.getRep(object, Firebug.currentContext);
211         return rep.shortTag ? rep.shortTag : rep.tag;
212     },
213 
214     isDisabled: function(bp)
215     {
216         return !bp.checked;
217     },
218 
219     onRemove: function(event)
220     {
221         Events.cancelEvent(event);
222 
223         if (!Css.hasClass(event.target, "closeButton"))
224             return;
225 
226         var bpPanel = Firebug.getElementPanel(event.target);
227         var context = bpPanel.context;
228 
229         // Remove from list of breakpoints.
230         var row = Dom.getAncestorByClass(event.target, "breakpointRow");
231         var bp = row.repObject;
232         context.dom.breakpoints.removeBreakpoint(bp.object, bp.propName);
233 
234         bpPanel.refresh();
235 
236         var domPanel = context.getPanel("dom", true);
237         if (domPanel)
238         {
239             var domRow = findRow(domPanel.panelNode, bp.object, bp.propName);
240             if (domRow)
241             {
242                 domRow.removeAttribute("breakpoint");
243                 domRow.removeAttribute("disabledBreakpoint");
244             }
245         }
246     },
247 
248     onEnable: function(event)
249     {
250         var checkBox = event.target;
251         var bpRow = Dom.getAncestorByClass(checkBox, "breakpointRow");
252 
253         if (checkBox.checked)
254         {
255             Css.removeClass(bpRow, "disabled");
256             bpRow.setAttribute("aria-checked", "true");
257         }
258         else
259         {
260             Css.setClass(bpRow, "disabled");
261             bpRow.setAttribute("aria-checked", "false");
262         }
263 
264         var bp = bpRow.repObject;
265         bp.checked = checkBox.checked;
266 
267         var bpPanel = Firebug.getElementPanel(event.target);
268         var context = bpPanel.context;
269 
270         var domPanel = context.getPanel("dom", true);
271         if (domPanel)
272         {
273             var row = findRow(domPanel.panelNode, bp.object, bp.propName);
274             if (row)
275                 row.setAttribute("disabledBreakpoint", bp.checked ? "false" : "true");
276         }
277     },
278 
279     supportsObject: function(object, type)
280     {
281         return object instanceof Breakpoint;
282     }
283 });
284 
285 // ********************************************************************************************* //
286 // Helpers
287 
288 function findRow(parentNode, object, propName)
289 {
290     var rows = parentNode.getElementsByClassName("memberRow");
291     for (var i=0; i<rows.length; i++)
292     {
293         var row = rows[i];
294         if (object == row.domObject.object && propName == row.domObject.name)
295             return row;
296     }
297 
298     return row;
299 }
300 
301 // ********************************************************************************************* //
302 // Registration
303 
304 Firebug.registerRep(BreakpointRep);
305 
306 return DOMBreakpointGroup;
307 
308 // ********************************************************************************************* //
309 }});
310 
311