1 /* See license.txt for terms of usage */ 2 3 define([ 4 "firebug/lib/object", 5 "firebug/chrome/firefox", 6 "firebug/firebug", 7 "firebug/dom/toggleBranch", 8 "firebug/lib/events", 9 "firebug/lib/dom", 10 "firebug/lib/css", 11 "firebug/js/stackFrame", 12 "firebug/lib/locale", 13 "firebug/lib/string", 14 "firebug/dom/domPanel", // Firebug.DOMBasePanel, Firebug.DOMPanel.DirTable 15 ], 16 function(Obj, Firefox, Firebug, ToggleBranch, Events, Dom, Css, StackFrame, Locale, Str) { 17 18 // ********************************************************************************************* // 19 // Watch Panel 20 21 Firebug.WatchPanel = function() 22 { 23 } 24 25 /** 26 * Represents the Watch side panel available in the Script panel. 27 */ 28 Firebug.WatchPanel.prototype = Obj.extend(Firebug.DOMBasePanel.prototype, 29 /** @lends Firebug.WatchPanel */ 30 { 31 tag: Firebug.DOMPanel.DirTable.watchTag, 32 33 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 34 // extends Panel 35 36 name: "watches", 37 order: 0, 38 parentPanel: "script", 39 enableA11y: true, 40 deriveA11yFrom: "console", 41 42 initialize: function() 43 { 44 this.onMouseDown = Obj.bind(this.onMouseDown, this); 45 this.onMouseOver = Obj.bind(this.onMouseOver, this); 46 this.onMouseOut = Obj.bind(this.onMouseOut, this); 47 48 Firebug.registerUIListener(this); 49 50 Firebug.DOMBasePanel.prototype.initialize.apply(this, arguments); 51 }, 52 53 destroy: function(state) 54 { 55 state.watches = this.watches; 56 57 Firebug.unregisterUIListener(this); 58 59 Firebug.DOMBasePanel.prototype.destroy.apply(this, arguments); 60 }, 61 62 show: function(state) 63 { 64 if (state && state.watches) 65 this.watches = state.watches; 66 }, 67 68 initializeNode: function(oldPanelNode) 69 { 70 Events.addEventListener(this.panelNode, "mousedown", this.onMouseDown, false); 71 Events.addEventListener(this.panelNode, "mouseover", this.onMouseOver, false); 72 Events.addEventListener(this.panelNode, "mouseout", this.onMouseOut, false); 73 74 Firebug.DOMBasePanel.prototype.initializeNode.apply(this, arguments); 75 }, 76 77 destroyNode: function() 78 { 79 Events.removeEventListener(this.panelNode, "mousedown", this.onMouseDown, false); 80 Events.removeEventListener(this.panelNode, "mouseover", this.onMouseOver, false); 81 Events.removeEventListener(this.panelNode, "mouseout", this.onMouseOut, false); 82 83 Firebug.DOMBasePanel.prototype.destroyNode.apply(this, arguments); 84 }, 85 86 refresh: function() 87 { 88 this.rebuild(true); 89 }, 90 91 updateSelection: function(frame) 92 { 93 // this method is called while the debugger has halted JS, 94 // so failures don't show up in FBS_ERRORS 95 try 96 { 97 this.doUpdateSelection(frame); 98 } 99 catch (exc) 100 { 101 if (FBTrace.DBG_ERRORS && FBTrace.DBG_STACK) 102 FBTrace.sysout("updateSelection FAILS " + exc, exc); 103 } 104 }, 105 106 doUpdateSelection: function(frame) 107 { 108 if (FBTrace.DBG_STACK) 109 FBTrace.sysout("dom watch panel updateSelection frame " + frame, frame); 110 111 Events.dispatch(this.fbListeners, "onBeforeDomUpdateSelection", [this]); 112 113 var newFrame = frame && ("signature" in frame) && 114 (frame.signature() != this.frameSignature); 115 116 if (newFrame) 117 { 118 this.toggles = new ToggleBranch.ToggleBranch(); 119 this.frameSignature = frame.signature(); 120 } 121 122 var scopes; 123 if (frame instanceof StackFrame.StackFrame) 124 scopes = frame.getScopes(Firebug.viewChrome); 125 else 126 scopes = [this.context.getGlobalScope()]; 127 128 if (FBTrace.DBG_STACK) 129 FBTrace.sysout("dom watch frame isStackFrame " + 130 (frame instanceof StackFrame.StackFrame) + 131 " updateSelection scopes " + scopes.length, scopes); 132 133 var members = []; 134 135 if (this.watches) 136 { 137 for (var i = 0; i < this.watches.length; ++i) 138 { 139 var expr = this.watches[i]; 140 var value = null; 141 142 Firebug.CommandLine.evaluate(expr, this.context, null, this.context.getGlobalScope(), 143 function success(result, context) 144 { 145 value = result; 146 }, 147 function failed(result, context) 148 { 149 var exc = result; 150 value = new FirebugReps.ErrorCopy(exc+""); 151 } 152 ); 153 154 this.addMember(scopes[0], "watch", members, expr, value, 0); 155 156 if (FBTrace.DBG_DOM) 157 FBTrace.sysout("watch.updateSelection " + expr + " = " + value, 158 {expr: expr, value: value, members: members}) 159 } 160 } 161 162 if (frame && frame instanceof StackFrame.StackFrame) 163 { 164 var thisVar = frame.getThisValue(); 165 if (thisVar) 166 this.addMember(scopes[0], "user", members, "this", thisVar, 0); 167 168 // locals, pre-expanded 169 members.push.apply(members, this.getMembers(scopes[0], 0, this.context)); 170 171 for (var i=1; i<scopes.length; i++) 172 this.addMember(scopes[i], "scopes", members, scopes[i].toString(), scopes[i], 0); 173 } 174 175 this.expandMembers(members, this.toggles, 0, 0, this.context); 176 this.showMembers(members, false); 177 178 if (FBTrace.DBG_STACK) 179 FBTrace.sysout("dom watch panel updateSelection members " + members.length, members); 180 }, 181 182 rebuild: function() 183 { 184 if (FBTrace.DBG_WATCH) 185 FBTrace.sysout("Firebug.WatchPanel.rebuild", this.selection); 186 187 this.updateSelection(this.selection); 188 }, 189 190 showEmptyMembers: function() 191 { 192 var domTable = this.tag.replace({domPanel: this, toggles: new ToggleBranch.ToggleBranch()}, 193 this.panelNode); 194 195 // The direction needs to be adjusted according to the direction 196 // of the user agent. See issue 5073. 197 // TODO: Set the direction at the <body> to allow correct formatting of all relevant parts. 198 // This requires more adjustments related for rtl user agents. 199 var mainFrame = Firefox.getElementById("fbMainFrame"); 200 var cs = mainFrame.ownerDocument.defaultView.getComputedStyle(mainFrame); 201 var watchRow = domTable.getElementsByClassName("watchNewRow").item(0); 202 watchRow.style.direction = cs.direction; 203 }, 204 205 addWatch: function(expression) 206 { 207 expression = Str.trim(expression); 208 209 if (FBTrace.DBG_WATCH) 210 FBTrace.sysout("Firebug.WatchPanel.addWatch; expression: "+expression); 211 212 if (!this.watches) 213 this.watches = []; 214 215 for (var i=0; i<this.watches.length; i++) 216 { 217 if (expression == this.watches[i]) 218 return; 219 } 220 221 this.watches.splice(0, 0, expression); 222 this.rebuild(true); 223 }, 224 225 removeWatch: function(expression) 226 { 227 if (FBTrace.DBG_WATCH) 228 FBTrace.sysout("Firebug.WatchPanel.removeWatch; expression: " + expression); 229 230 if (!this.watches) 231 return; 232 233 var index = this.watches.indexOf(expression); 234 if (index != -1) 235 this.watches.splice(index, 1); 236 }, 237 238 editNewWatch: function(value) 239 { 240 if (FBTrace.DBG_WATCH) 241 FBTrace.sysout("Firebug.WatchPanel.editNewWatch; value: " + value); 242 243 var watchNewRow = this.panelNode.getElementsByClassName("watchNewRow").item(0); 244 if (watchNewRow) 245 this.editProperty(watchNewRow, value); 246 }, 247 248 setWatchValue: function(row, value) 249 { 250 if (FBTrace.DBG_WATCH) 251 FBTrace.sysout("Firebug.WatchPanel.setWatchValue", {row: row, value: value}); 252 253 var rowIndex = getWatchRowIndex(row); 254 this.watches[rowIndex] = value; 255 this.rebuild(true); 256 }, 257 258 deleteWatch: function(row) 259 { 260 if (FBTrace.DBG_WATCH) 261 FBTrace.sysout("Firebug.WatchPanel.deleteWatch", row); 262 263 var rowIndex = getWatchRowIndex(row); 264 this.watches.splice(rowIndex, 1); 265 this.rebuild(true); 266 267 this.context.setTimeout(Obj.bindFixed(function() 268 { 269 this.showToolbox(null); 270 }, this)); 271 }, 272 273 // deletes all the watches 274 deleteAllWatches: function() 275 { 276 if (FBTrace.DBG_WATCH) 277 FBTrace.sysout("Firebug.WatchPanel.deleteAllWatches"); 278 this.watches = []; 279 this.rebuild(true); 280 this.context.setTimeout(Obj.bindFixed(function() 281 { 282 this.showToolbox(null); 283 }, this)); 284 }, 285 286 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 287 288 showToolbox: function(row) 289 { 290 var toolbox = this.getToolbox(); 291 if (row) 292 { 293 if (Css.hasClass(row, "editing")) 294 return; 295 296 toolbox.watchRow = row; 297 298 var offset = Dom.getClientOffset(row); 299 toolbox.style.top = offset.y + "px"; 300 this.panelNode.appendChild(toolbox); 301 } 302 else 303 { 304 delete toolbox.watchRow; 305 306 if (toolbox.parentNode) 307 toolbox.parentNode.removeChild(toolbox); 308 } 309 }, 310 311 getToolbox: function() 312 { 313 if (!this.toolbox) 314 { 315 this.toolbox = Firebug.DOMBasePanel.ToolboxPlate.tag.replace( 316 {domPanel: this}, this.document); 317 } 318 319 return this.toolbox; 320 }, 321 322 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 323 324 onMouseDown: function(event) 325 { 326 var watchNewRow = Dom.getAncestorByClass(event.target, "watchNewRow"); 327 if (watchNewRow) 328 { 329 this.editProperty(watchNewRow); 330 Events.cancelEvent(event); 331 } 332 }, 333 334 onMouseOver: function(event) 335 { 336 var watchRow = Dom.getAncestorByClass(event.target, "watchRow"); 337 if (watchRow) 338 this.showToolbox(watchRow); 339 }, 340 341 onMouseOut: function(event) 342 { 343 if (Dom.isAncestor(event.relatedTarget, this.getToolbox())) 344 return; 345 346 var watchRow = Dom.getAncestorByClass(event.relatedTarget, "watchRow"); 347 if (!watchRow) 348 this.showToolbox(null); 349 }, 350 351 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 352 // Context Menu 353 354 /** 355 * Creates "Add Watch" menu item within DOM and Watch panel context menus. 356 */ 357 onContextMenu: function(items, object, target, context, panel, popup) 358 { 359 // Ignore events from other contexts. 360 if (this.context != context) 361 return; 362 363 if (panel.name != "dom" && panel.name != "watches") 364 return; 365 366 var row = Dom.getAncestorByClass(target, "memberRow"); 367 if (!row) 368 return; 369 370 var path = this.getPropertyPath(row); 371 if (!path || !path.length) 372 return; 373 374 375 // Ignore top level variables in the Watch panel. 376 if (panel.name == "watches" && path.length == 1) 377 return; 378 379 items.push({ 380 id: "fbAddWatch", 381 label: "AddWatch", 382 tooltiptext: "watch.tip.Add_Watch", 383 command: Obj.bindFixed(this.addWatch, this, path.join("")) 384 }); 385 }, 386 387 getContextMenuItems: function(object, target) 388 { 389 var items = Firebug.DOMBasePanel.prototype.getContextMenuItems.apply(this, arguments); 390 391 if (!this.watches || this.watches.length == 0) 392 return items; 393 394 // find the index of "DeletePropery" in the items: 395 var deleteWatchIndex = items.map(function(item) 396 { 397 return item.id; 398 }).indexOf("DeleteProperty"); 399 400 // if DeleteWatch was found, we insert DeleteAllWatches after it 401 // otherwise, we insert the item at the beginning of the menu 402 var deleteAllWatchesIndex = (deleteWatchIndex >= 0) ? deleteWatchIndex + 1 : 0; 403 404 if (FBTrace.DBG_WATCH) 405 FBTrace.sysout("insert DeleteAllWatches at: "+ deleteAllWatchesIndex); 406 407 // insert DeleteAllWatches after DeleteWatch 408 items.splice(deleteAllWatchesIndex, 0, { 409 id: "fbDeleteAllWatches", 410 label: "DeleteAllWatches", 411 tooltiptext: "watch.tip.Delete_All_Watches", 412 command: Obj.bindFixed(this.deleteAllWatches, this) 413 }); 414 415 return items; 416 } 417 }); 418 419 // ********************************************************************************************* // 420 // Local Helpers 421 422 function getWatchRowIndex(row) 423 { 424 var index = -1; 425 for (; row; row = row.previousSibling) 426 { 427 if (Css.hasClass(row, "watchRow")) 428 ++index; 429 } 430 return index; 431 } 432 433 // ********************************************************************************************* // 434 // Registration 435 436 Firebug.registerPanel(Firebug.WatchPanel); 437 438 return Firebug.WatchPanel; 439 440 // ********************************************************************************************* // 441 }); 442