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