1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/lib/object",
  5     "firebug/firebug",
  6     "firebug/lib/domplate",
  7     "firebug/chrome/reps",
  8     "firebug/lib/locale",
  9     "firebug/lib/events",
 10     "firebug/lib/wrapper",
 11     "firebug/js/sourceLink",
 12     "firebug/js/stackFrame",
 13     "firebug/lib/dom",
 14     "firebug/lib/css",
 15     "firebug/lib/search",
 16     "firebug/lib/string",
 17     "firebug/lib/array",
 18     "firebug/lib/persist",
 19     "firebug/dom/toggleBranch",
 20     "firebug/lib/system",
 21     "firebug/chrome/menu",
 22     "firebug/editor/editor",
 23     "firebug/js/breakpoint",
 24     "firebug/chrome/searchBox",
 25     "firebug/dom/domModule",
 26     "firebug/console/autoCompleter"
 27 ],
 28 function(Obj, Firebug, Domplate, FirebugReps, Locale, Events, Wrapper,
 29     SourceLink, StackFrame, Dom, Css, Search, Str, Arr, Persist, ToggleBranch, System, Menu) {
 30 
 31 with (Domplate) {
 32 
 33 // ********************************************************************************************* //
 34 // Constants
 35 
 36 const Cc = Components.classes;
 37 const Ci = Components.interfaces;
 38 const jsdIStackFrame = Ci.jsdIStackFrame;
 39 
 40 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 41 
 42 const insertSliceSize = 18;
 43 const insertInterval = 40;
 44 
 45 const rxIdentifier = /^[$_A-Za-z][$_A-Za-z0-9]*$/
 46 
 47 // ********************************************************************************************* //
 48 
 49 const WatchRowTag =
 50     TR({"class": "watchNewRow", level: 0},
 51         TD({"class": "watchEditCell", colspan: 3},
 52             DIV({"class": "watchEditBox a11yFocusNoTab", role: "button", tabindex: "0",
 53                 "aria-label": Locale.$STR("a11y.labels.press enter to add new watch expression")},
 54                     Locale.$STR("NewWatch")
 55             )
 56         )
 57     );
 58 
 59 const SizerRow =
 60     TR({role: "presentation"},
 61         TD(),
 62         TD({width: "30%"}),
 63         TD({width: "70%"})
 64     );
 65 
 66 const DirTablePlate = domplate(Firebug.Rep,
 67 {
 68     memberRowTag:
 69         TR({"class": "memberRow $member.open $member.type\\Row", _domObject: "$member",
 70             $hasChildren: "$member.hasChildren",
 71             role: "presentation",
 72             level: "$member.level",
 73             breakable: "$member.breakable",
 74             breakpoint: "$member.breakpoint",
 75             disabledBreakpoint: "$member.disabledBreakpoint"},
 76             TD({"class": "memberHeaderCell"},
 77                DIV({"class": "sourceLine memberRowHeader", onclick: "$onClickRowHeader"},
 78                     " "
 79                )
 80             ),
 81             TD({"class": "memberLabelCell", style: "padding-left: $member.indent\\px",
 82                 role: "presentation"},
 83                 DIV({"class": "memberLabel $member.type\\Label"},
 84                     SPAN({"class": "memberLabelPrefix"}, "$member.prefix"),
 85                     SPAN("$member.name")
 86                 )
 87             ),
 88             TD({"class": "memberValueCell", $readOnly: "$member.readOnly",
 89                 role: "presentation"},
 90                 TAG("$member.tag", {object: "$member.value"})
 91             )
 92         ),
 93 
 94     tag:
 95         TABLE({"class": "domTable", cellpadding: 0, cellspacing: 0, onclick: "$onClick",
 96             _repObject: "$object", role: "tree",
 97             "aria-label": Locale.$STR("aria.labels.dom properties")},
 98             TBODY({role: "presentation"},
 99                 SizerRow,
100                 FOR("member", "$object|memberIterator",
101                     TAG("$memberRowTag", {member: "$member"})
102                 )
103             )
104         ),
105 
106     watchTag:
107         TABLE({"class": "domTable", cellpadding: 0, cellspacing: 0,
108                _toggles: "$toggles", _domPanel: "$domPanel", onclick: "$onClick", role: "tree"},
109             TBODY({role: "presentation"},
110                 SizerRow,
111                 WatchRowTag
112             )
113         ),
114 
115     tableTag:
116         TABLE({"class": "domTable", cellpadding: 0, cellspacing: 0,
117             _toggles: "$toggles", _domPanel: "$domPanel", onclick: "$onClick",
118             role: "tree", "aria-label": Locale.$STR("a11y.labels.dom_properties")},
119             TBODY({role: "presentation"},
120                 SizerRow
121             )
122         ),
123 
124     rowTag:
125         FOR("member", "$members",
126             TAG("$memberRowTag", {member: "$member"})
127         ),
128 
129     memberIterator: function(object)
130     {
131         var members = Firebug.DOMBasePanel.prototype.getMembers(object, 0, null);
132         if (members.length)
133             return members;
134 
135         return [{
136             name: Locale.$STR("firebug.dom.noChildren2"),
137             type: "string",
138             rowClass: "memberRow-string",
139             tag: Firebug.Rep.tag,
140             prefix: ""
141         }];
142     },
143 
144     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
145 
146     onClick: function(event)
147     {
148         if (!Events.isLeftClick(event))
149             return;
150 
151         var row = Dom.getAncestorByClass(event.target, "memberRow");
152         var label = Dom.getAncestorByClass(event.target, "memberLabel");
153         var valueCell = row.getElementsByClassName("memberValueCell").item(0);
154         var object = Firebug.getRepObject(event.target);
155         var target = row.lastChild.firstChild;
156         var isString = Css.hasClass(target,"objectBox-string");
157         var inValueCell = event.target == valueCell || event.target == target;
158 
159         if (label && Css.hasClass(row, "hasChildren") && !(isString && inValueCell))
160         {
161             var row = label.parentNode.parentNode;
162             this.toggleRow(row);
163             Events.cancelEvent(event);
164         }
165         else
166         {
167             if (typeof(object) == "function")
168             {
169                 Firebug.chrome.select(object, "script");
170                 Events.cancelEvent(event);
171             }
172             else if (Events.isDoubleClick(event) && !object)
173             {
174                 var panel = row.parentNode.parentNode.domPanel;
175                 if (panel)
176                 {
177                     var rowValue = panel.getRowPropertyValue(row);
178                     if (typeof(rowValue) == "boolean")
179                         panel.setPropertyValue(row, !rowValue);
180                     else
181                         panel.editProperty(row);
182                     Events.cancelEvent(event);
183                 }
184             }
185         }
186     },
187 
188     toggleRow: function(row)
189     {
190         var level = parseInt(row.getAttribute("level"));
191         var table = Dom.getAncestorByClass(row, "domTable");
192         var toggles = table.toggles;
193         if (!toggles)
194             toggles = table.repObject.toggles;
195 
196         var domPanel = table.domPanel;
197         if (!domPanel)
198         {
199             var panel = Firebug.getElementPanel(row);
200             domPanel = panel.context.getPanel("dom");
201         }
202 
203         if (!domPanel)
204             return;
205 
206         var context = domPanel.context;
207         var target = row.lastChild.firstChild;
208         var isString = Css.hasClass(target, "objectBox-string");
209 
210         if (Css.hasClass(row, "opened"))
211         {
212             Css.removeClass(row, "opened");
213 
214             if (isString)
215             {
216                 var rowValue = row.domObject.value;
217                 row.lastChild.firstChild.textContent = '"' + Str.cropMultipleLines(rowValue) + '"';
218             }
219             else
220             {
221                 if (toggles)
222                 {
223                     var path = getPath(row);
224 
225                     // Remove the path from the toggle tree
226                     for (var i = 0; i < path.length; ++i)
227                     {
228                         if (i == path.length-1)
229                             toggles.remove(path[i]);
230                         else
231                             toggles = toggles.get(path[i]);
232                     }
233                 }
234 
235                 var rowTag = this.rowTag;
236                 var tbody = row.parentNode;
237 
238                 setTimeout(function()
239                 {
240                     for (var firstRow = row.nextSibling; firstRow; firstRow = row.nextSibling)
241                     {
242                         if (parseInt(firstRow.getAttribute("level")) <= level)
243                             break;
244 
245                         tbody.removeChild(firstRow);
246                     }
247                 }, row.insertTimeout ? row.insertTimeout : 0);
248             }
249         }
250         else
251         {
252             Css.setClass(row, "opened");
253             if (isString)
254             {
255                 var rowValue = row.domObject.value
256                 row.lastChild.firstChild.textContent = '"' + rowValue + '"';
257             }
258             else
259             {
260 
261                 if (toggles)
262                 {
263                     var path = getPath(row);
264 
265                     // Mark the path in the toggle tree
266                     for (var i = 0; i < path.length; ++i)
267                     {
268                         var name = path[i];
269                         if (toggles.get(name))
270                             toggles = toggles.get(name);
271                         else
272                             toggles = toggles.set(name, new ToggleBranch.ToggleBranch());
273                     }
274                     if (FBTrace.DBG_DOMPLATE)
275                         FBTrace.sysout("toggleRow mark path "+toggles);
276                 }
277 
278                 var members = domPanel.getMembers(target.repObject, level+1, context);
279 
280                 var rowTag = this.rowTag;
281                 var lastRow = row;
282 
283                 var delay = 0;
284                 var setSize = members.length;
285                 var rowCount = 1;
286                 while (members.length)
287                 {
288                     with ({slice: members.splice(0, insertSliceSize), isLast: !members.length})
289                     {
290                         setTimeout(function()
291                         {
292                             if (lastRow.parentNode)
293                             {
294                                 var result = rowTag.insertRows({members: slice}, lastRow);
295                                 lastRow = result[1];
296                                 Events.dispatch(Firebug.DOMModule.fbListeners,
297                                     "onMemberRowSliceAdded", [null, result, rowCount, setSize]);
298                                 rowCount += insertSliceSize;
299                             }
300 
301                             if (isLast)
302                                 delete row.insertTimeout;
303                         }, delay);
304                     }
305 
306                     delay += insertInterval;
307                 }
308 
309                 row.insertTimeout = delay;
310             }
311         }
312     },
313 
314     onClickRowHeader: function(event)
315     {
316         Events.cancelEvent(event);
317 
318         var rowHeader = event.target;
319         if (!Css.hasClass(rowHeader, "memberRowHeader"))
320             return;
321 
322         var row = Dom.getAncestorByClass(event.target, "memberRow");
323         if (!row)
324             return;
325 
326         var panel = row.parentNode.parentNode.domPanel;
327         if (panel)
328         {
329             var scriptPanel = panel.context.getPanel("script", true);
330             if (!scriptPanel || !scriptPanel.isEnabled())
331                 return;     // set the breakpoint only if the script panel will respond.
332             panel.breakOnProperty(row);
333         }
334     }
335 });
336 
337 const ToolboxPlate = domplate(
338 {
339     tag:
340         DIV({"class": "watchToolbox", _domPanel: "$domPanel", onclick: "$onClick"},
341             IMG({"class": "watchDeleteButton closeButton", src: "blank.gif"})
342         ),
343 
344     onClick: function(event)
345     {
346         var toolbox = event.currentTarget;
347         toolbox.domPanel.deleteWatch(toolbox.watchRow);
348     }
349 });
350 
351 // ********************************************************************************************* //
352 
353 Firebug.DOMBasePanel = function() {}
354 
355 Firebug.DOMBasePanel.ToolboxPlate = ToolboxPlate;
356 
357 Firebug.DOMBasePanel.prototype = Obj.extend(Firebug.Panel,
358 {
359     tag: DirTablePlate.tableTag,
360     dirTablePlate: DirTablePlate,
361 
362     getObjectView: function(object)
363     {
364         if (!Firebug.viewChrome)
365         {
366             var contentView = Wrapper.getContentView(object);
367             if (!contentView && FBTrace.DBG_DOM)
368                 FBTrace.sysout("getObjectView: no contentView for " + object);
369             return contentView || object;
370         }
371         return object;
372     },
373 
374     rebuild: function(update, scrollTop)
375     {
376         Events.dispatch(this.fbListeners, "onBeforeDomUpdateSelection", [this]);
377 
378         var members = this.getMembers(this.selection, 0, this.context);
379         this.expandMembers(members, this.toggles, 0, 0, this.context);
380         this.showMembers(members, update, scrollTop);
381     },
382 
383     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
384     // Object properties
385 
386     /**
387      * Returns a list of properties available on an object, filtered on enumerability and prototype
388      * chain position. Due to prototype traversal, some property names may appear several times.
389      *
390      * @param {Object} object The object we want to get the list of properties for.
391      * @param {Boolean} enumerableOnly If set to true, only enumerable properties are returned.
392      * @param {Boolean} ownOnly If set to true, only own properties (not those from the
393      *      prototype chain) are returned.
394      */
395     getObjectProperties: function(object, enumerableOnly, ownOnly)
396     {
397         var props = [];
398 
399         // Get all enumerable-only or all-properties of the object (but not inherited).
400         if (enumerableOnly)
401             props = Object.keys(object);
402         else
403             props = Object.getOwnPropertyNames(object);
404 
405         // Not interested in inherited properties, bail out.
406         if (ownOnly)
407             return props;
408 
409         // Climb the prototype chain.
410         var inheritedProps = [];
411         var parent = Object.getPrototypeOf(object);
412         if (parent)
413             inheritedProps = this.getObjectProperties(parent, enumerableOnly, ownOnly);
414 
415         // Push everything onto the returned array, to avoid O(nm) runtime behavior.
416         inheritedProps.push.apply(inheritedProps, props);
417         return inheritedProps;
418     },
419 
420     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
421 
422     /**
423      * @param object a user-level object wrapped in security blanket
424      * @param level for a.b.c, level is 2
425      * @param optional context
426      */
427     getMembers: function(object, level, context)
428     {
429         if (!level)
430             level = 0;
431 
432         var ordinals = [];
433         var userProps = [];
434         var userClasses = [];
435         var userFuncs = [];
436         var domProps = [];
437         var domClasses = [];
438         var domFuncs = [];
439         var domConstants = [];
440         var proto = [];
441         var domHandlers = [];
442 
443         try
444         {
445             // Special case for "arguments", which is not enumerable by for...in statement.
446             if (isArguments(object))
447                 object = Arr.cloneArray(object);
448 
449             try
450             {
451                 var contentView = this.getObjectView(object);
452                 var properties = this.getObjectProperties(contentView,
453                     Firebug.showEnumerableProperties, Firebug.showOwnProperties);
454                 properties = Arr.sortUnique(properties);
455 
456                 if (contentView.hasOwnProperty("constructor") &&
457                     properties.indexOf("constructor") == -1)
458                 {
459                     properties.push("constructor");
460                 }
461 
462                 if (contentView.hasOwnProperty("prototype") &&
463                     properties.indexOf("prototype") == -1)
464                 {
465                     properties.push("prototype");
466                 }
467 
468                 // If showOwnProperties is false the __proto__ can be already in.
469                 // If showOwnProperties is true the __proto__ should not be in.
470                 if (contentView.__proto__ && Obj.hasProperties(contentView.__proto__) &&
471                     properties.indexOf("__proto__") == -1 && !Firebug.showOwnProperties)
472                 {
473                     properties.push("__proto__");
474                 }
475             }
476             catch (exc)
477             {
478                  // workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=648560
479                 if (contentView.wrappedJSObject)
480                 {
481                     if (FBTrace.DBG_ERRORS || FBTrace.DBG_DOM)
482                     {
483                         FBTrace.sysout("dom DOM bz:" + (XPCNativeWrapper.unwrap(contentView) !==
484                             contentView) + " contentView(" + contentView + ").wrappedJSObject " +
485                             contentView.wrappedJSObject);
486                     }
487 
488                     var wrapperToString = contentView+"";
489                     contentView =
490                     {
491                         wrappedJSObject: XPCNativeWrapper.unwrap(contentView),
492                         toString: function() { return wrapperToString; },
493                         isXPCNativeWrapper: (XPCNativeWrapper.unwrap(contentView) !== contentView),
494                     }
495 
496                     object = contentView;
497                 }
498             }
499 
500             if (contentView.wrappedJSObject)
501                 properties.push("wrappedJSObject");
502 
503             var domMembers = Dom.getDOMMembers(object);
504             for (var i = 0; i < properties.length; i++)
505             {
506                 var name = properties[i];
507 
508                 // Ignore only global variables (properties of the |window| object).
509                 if (Wrapper.shouldIgnore(name) && (object instanceof Window))
510                 {
511                     if (FBTrace.DBG_DOM)
512                     {
513                         FBTrace.sysout("dom.getMembers: Wrapper.ignoreVars: " + name + ", " +
514                             level, object);
515                     }
516                     continue;
517                 }
518 
519                 var val;
520                 try
521                 {
522                     val = contentView[name];  // getter is safe
523                 }
524                 catch (exc)
525                 {
526                     // Sometimes we get exceptions trying to access certain members
527                     if (FBTrace.DBG_ERRORS && FBTrace.DBG_DOM)
528                         FBTrace.sysout("dom.getMembers cannot access "+name, exc);
529 
530                     val = undefined;
531                 }
532 
533                 var ordinal = parseInt(name);
534                 if (ordinal || ordinal == 0)
535                 {
536                     this.addMember(object, "ordinal", ordinals, name, val, level, 0, context);
537                 }
538                 else if (typeof(val) == "function")
539                 {
540                     if (isClassFunction(val))
541                     {
542                         if (Dom.isDOMMember(object, name))
543                             this.addMember(object, "domClass", domClasses, name, val, level, domMembers[name], context);
544                         else
545                             this.addMember(object, "userClass", userClasses, name, val, level, 0, context);
546                     }
547                     else if (Dom.isDOMMember(object, name))
548                     {
549                         this.addMember(object, "domFunction", domFuncs, name, val, level, domMembers[name], context);
550                     }
551                     else if (!Firebug.showUserFuncs && Firebug.showInlineEventHandlers)
552                     {
553                         this.addMember(object, "userFunction", domHandlers, name, val, level, 0, context);
554                     }
555                     else
556                     {
557                         this.addMember(object, "userFunction", userFuncs, name, val, level, 0, context);
558                     }
559                 }
560                 else
561                 {
562                     if (isPrototype(name))
563                         this.addMember(object, "proto", proto, name, val, level, 0, context);
564                     else if (Dom.isDOMMember(object, name))
565                         this.addMember(object, "dom", domProps, name, val, level, domMembers[name], context);
566                     else if (Dom.isDOMConstant(object, name))
567                         this.addMember(object, "dom", domConstants, name, val, level, 0, context);
568                     else if (Dom.isInlineEventHandler(name))
569                         this.addMember(object, "user", domHandlers, name, val, level, 0, context);
570                     else
571                         this.addMember(object, "user", userProps, name, val, level, 0, context);
572                 }
573             }
574         }
575         catch (exc)
576         {
577             // Sometimes we get exceptions just from trying to iterate the members
578             // of certain objects, like StorageList, but don't let that gum up the works
579             //throw exc;
580             if (FBTrace.DBG_ERRORS && FBTrace.DBG_DOM)
581                 FBTrace.sysout("dom.getMembers FAILS: ", exc);
582         }
583 
584         function sortName(a, b) { return a.name > b.name ? 1 : -1; }
585         function sortOrdinal(a, b) { return parseInt(a.name) > parseInt(b.name) ? 1 : -1; }
586 
587         var members = [];
588 
589         ordinals.sort(sortOrdinal);
590         members.push.apply(members, ordinals);
591 
592         if (Firebug.showUserProps)
593         {
594             userProps.sort(sortName);
595             members.push.apply(members, userProps);
596         }
597 
598         if (Firebug.showUserFuncs)
599         {
600             userClasses.sort(sortName);
601             members.push.apply(members, userClasses);
602 
603             userFuncs.sort(sortName);
604             members.push.apply(members, userFuncs);
605         }
606 
607         if (Firebug.showDOMProps)
608         {
609             domProps.sort(sortName);
610             members.push.apply(members, domProps);
611         }
612 
613         if (Firebug.showDOMFuncs)
614         {
615             domClasses.sort(sortName);
616             members.push.apply(members, domClasses);
617 
618             domFuncs.sort(sortName);
619             members.push.apply(members, domFuncs);
620         }
621 
622         if (Firebug.showDOMConstants)
623             members.push.apply(members, domConstants);
624 
625         // The prototype is always displayed at the end.
626         members.push.apply(members, proto);
627 
628         if (Firebug.showInlineEventHandlers)
629         {
630             domHandlers.sort(sortName);
631             members.push.apply(members, domHandlers);
632         }
633 
634         if (FBTrace.DBG_DOM)
635         {
636             var showEnum = Firebug.showEnumerableProperties;
637             var showOwn = Firebug.showOwnProperties;
638             FBTrace.sysout("dom.getMembers; Report: enum-only: " + showEnum +
639                 ", own-only: " + showOwn,
640             {
641                 object: object,
642                 ordinals: ordinals,
643                 userProps: userProps,
644                 userFuncs: userFuncs,
645                 userClasses: userClasses,
646                 domProps: domProps,
647                 domFuncs: domFuncs,
648                 domConstants: domConstants,
649                 domHandlers: domHandlers,
650                 proto: proto,
651             });
652         }
653 
654         return members;
655     },
656 
657     addMember: function(object, type, props, name, value, level, order, context)
658     {
659         try
660         {
661             return this.addMemberInternal.apply(this, arguments);
662         }
663         catch (err)
664         {
665             if (FBTrace.DBG_ERRORS)
666                 FBTrace.sysout("domPanel.addMember; EXCEPTION " + err, err);
667         }
668     },
669 
670     addMemberInternal: function(object, type, props, name, value, level, order, context)
671     {
672         // do this first in case a call to instanceof reveals contents
673         var rep = Firebug.getRep(value);
674         var tag = rep.shortTag ? rep.shortTag : rep.tag;
675 
676         var hasProperties = Obj.hasProperties(value, !Firebug.showEnumerableProperties,
677             Firebug.showOwnProperties);
678 
679         var valueType = typeof(value);
680         var hasChildren = hasProperties && !(value instanceof FirebugReps.ErrorCopy) &&
681             (valueType == "function" || (valueType == "object" && value != null)
682             || (valueType == "string" && value.length > Firebug.stringCropLength));
683 
684         // Special case for "arguments", which is not enumerable by for...in statement
685         // and so, Obj.hasProperties always returns false.
686         if (!hasChildren && value) // arguments will never be falsy if the arguments exist
687             hasChildren = isArguments(value);
688 
689         if (value)
690         {
691             var proto = Obj.getPrototype(value);
692             // Special case for functions with a prototype that has values
693             if (valueType === "function" && proto)
694             {
695                 hasChildren = hasChildren || Obj.hasProperties(proto,
696                     !Firebug.showEnumerableProperties, Firebug.showOwnProperties);
697             }
698         }
699 
700         var member = {
701             object: object,
702             name: name,
703             value: value,
704             type: type,
705             rowClass: "memberRow-"+type,
706             open: "",
707             order: order,
708             level: level,
709             indent: level*16,
710             hasChildren: hasChildren,
711             tag: tag,
712             prefix: "",
713             readOnly: false
714         };
715 
716         // The context doesn't have to be specified (e.g. in case of Watch panel that is based
717         // on the same template as the DOM panel, but doesn't show any breakpoints).
718         if (context)
719         {
720             // xxxHonza: Support for object change not implemented yet.
721             member.breakable = !hasChildren;
722 
723             var breakpoints = context.dom.breakpoints;
724             var bp = breakpoints.findBreakpoint(object, name);
725             if (bp)
726             {
727                 member.breakpoint = true;
728                 member.disabledBreakpoint = !bp.checked;
729             }
730         }
731 
732         // Set prefix for user defined properties. This prefix help the user to distinguish
733         // among simple properties and those defined using getter and/or (only a) setter.
734         var o = this.getObjectView(object);
735         if (o && !Dom.isDOMMember(object, name) && (XPCNativeWrapper.unwrap(object) !== object))
736         {
737             var getter = o.__lookupGetter__(name);
738             var setter = o.__lookupSetter__(name);
739 
740             // both, getter and setter
741             if (getter && setter)
742                 member.type = "userFunction";
743 
744             // only getter
745             if (getter && !setter)
746             {
747                 member.readOnly = true;
748                 member.prefix = "get";
749             }
750 
751             // only setter
752             if (!getter && setter)
753             {
754                 member.readOnly = true;
755                 member.prefix = "set";
756             }
757         }
758 
759         var readOnly = isReadOnly(object, name);
760         if (typeof(readOnly) != "undefined")
761             member.readOnly = readOnly;
762 
763         props.push(member);
764         return member;
765     },
766 
767     // recursion starts with offset=0, level=0
768     expandMembers: function (members, toggles, offset, level, context)
769     {
770         var expanded = 0;
771         for (var i = offset; i < members.length; ++i)
772         {
773             var member = members[i];
774             if (member.level > level)
775                 break;
776 
777             if (toggles.get(member.name))
778             {
779                 // member.level <= level && member.name in toggles.
780                 member.open = "opened";
781 
782                 // Don't expand if the member doesn't have children any more.
783                 if (!member.hasChildren)
784                     continue;
785 
786                 // sets newMembers.level to level+1
787                 var newMembers = this.getMembers(member.value, level+1, context);
788 
789                 var args = [i+1, 0];
790                 args.push.apply(args, newMembers);
791                 members.splice.apply(members, args);
792                 if (FBTrace.DBG_DOM)
793                 {
794                     FBTrace.sysout("expandMembers member.name "+member.name+" member "+member);
795                     FBTrace.sysout("expandMembers toggles "+toggles, toggles);
796                     FBTrace.sysout("expandMembers toggles.get(member.name) " +
797                         toggles.get(member.name), toggles.get(member.name));
798                     FBTrace.sysout("dom.expandedMembers level: "+level+" member.level " +
799                         member.level, member);
800                 }
801 
802                 expanded += newMembers.length;
803 
804                 i += newMembers.length + this.expandMembers(members, toggles.get(member.name),
805                     i+1, level+1, context);
806             }
807         }
808 
809         return expanded;
810     },
811 
812     showMembers: function(members, update, scrollTop)
813     {
814         // If we are still in the midst of inserting rows, cancel all pending
815         // insertions here - this is a big speedup when stepping in the debugger
816         if (this.timeouts)
817         {
818             for (var i = 0; i < this.timeouts.length; ++i)
819                 this.context.clearTimeout(this.timeouts[i]);
820             delete this.timeouts;
821         }
822 
823         if (!members.length)
824             return this.showEmptyMembers();
825 
826         var panelNode = this.panelNode;
827         var priorScrollTop = scrollTop == undefined ? panelNode.scrollTop : scrollTop;
828 
829         // If we are asked to "update" the current view, then build the new table
830         // offscreen and swap it in when it's done
831         var offscreen = update && panelNode.firstChild;
832         var dest = offscreen ? this.document : panelNode;
833 
834         var table = this.tag.replace({domPanel: this, toggles: this.toggles}, dest);
835         var tbody = table.lastChild;
836         var rowTag = this.dirTablePlate.rowTag;
837 
838         // Insert the first slice immediately
839         var setSize = members.length;
840         var slice = members.splice(0, insertSliceSize);
841         var result = rowTag.insertRows({members: slice}, tbody.lastChild);
842         var rowCount = 1;
843         var panel = this;
844 
845         Events.dispatch(this.fbListeners, "onMemberRowSliceAdded",
846             [panel, result, rowCount, setSize]);
847 
848         var timeouts = [];
849 
850         var delay = 0;
851         while (members.length)
852         {
853             with({slice: members.splice(0, insertSliceSize)})
854             {
855                 timeouts.push(this.context.setTimeout(function addMemberRowSlice()
856                 {
857                     result = rowTag.insertRows({members: slice}, tbody.lastChild);
858                     rowCount += insertSliceSize;
859 
860                     Events.dispatch(Firebug.DOMModule.fbListeners, "onMemberRowSliceAdded",
861                         [panel, result, rowCount, setSize]);
862 
863                     if ((panelNode.scrollHeight+panelNode.offsetHeight) >= priorScrollTop)
864                         panelNode.scrollTop = priorScrollTop;
865 
866                 }, delay));
867             }
868 
869             delay += insertInterval;
870         }
871 
872         if (offscreen)
873         {
874             timeouts.push(this.context.setTimeout(function()
875             {
876                 if (panelNode.firstChild)
877                     panelNode.replaceChild(table, panelNode.firstChild);
878                 else
879                     panelNode.appendChild(table);
880 
881                 // Scroll back to where we were before
882                 panelNode.scrollTop = priorScrollTop;
883             }, delay));
884         }
885         else
886         {
887             timeouts.push(this.context.setTimeout(function()
888             {
889                 panelNode.scrollTop = scrollTop == undefined ? 0 : scrollTop;
890             }, delay));
891         }
892         this.timeouts = timeouts;
893     },
894 
895     showEmptyMembers: function()
896     {
897         FirebugReps.Warning.tag.replace({object: "NoMembersWarning"}, this.panelNode);
898     },
899 
900     findPathIndex: function(object)
901     {
902         var pathIndex = -1;
903         for (var i = 0; i < this.objectPath.length; ++i)
904         {
905             if (this.getPathObject(i) == object)
906                 return i;
907         }
908 
909         return -1;
910     },
911 
912     getPathObject: function(index)
913     {
914         var object = this.objectPath[index];
915         if (object instanceof FirebugReps.PropertyObj)
916             return object.getObject();
917         else
918             return object;
919     },
920 
921     getRowObject: function(row)
922     {
923         var object = getRowOwnerObject(row);
924         return object ? object : this.selection;
925     },
926 
927     getRealRowObject: function(row)
928     {
929         var object = this.getRowObject(row);
930         return this.getObjectView(object);
931     },
932 
933     getRowPropertyValue: function(row)
934     {
935         var object = this.getRealRowObject(row);
936         return this.getObjectPropertyValue(object, row.domObject.name);
937     },
938 
939     getObjectPropertyValue: function(object, propName)
940     {
941         if (!object)
942             return;
943 
944         // Get the value with try-catch statement. This method is used also within
945         // getContextMenuItems where the exception would break the context menu.
946         // 1) The Firebug.Debugger.evaluate can throw
947         // 2) object[propName] can also throws in case of e.g. non existing "abc.abc" prop name.
948         try
949         {
950             if (object instanceof StackFrame.StackFrame)
951                 return Firebug.Debugger.evaluate(propName, this.context);
952             else
953                 return object[propName];
954         }
955         catch (err)
956         {
957             if(FBTrace.DBG_DOM || FBTrace.DBG_ERRORS)
958                 FBTrace.sysout("dom.getObjectPropertyValue; EXCEPTION " + propName, object);
959         }
960     },
961 
962     getRowPathName: function(row)
963     {
964         var name = row.domObject.name;
965 
966         if(name.match(/^[\d]+$/))//ordinal
967             return ["", "["+name+"]"];
968         else if(name.match(rxIdentifier))//identifier
969             return [".", name];
970         else//map keys
971             return ["", "[\""+name.replace(/\\/g, "\\\\").replace(/"/g,"\\\"") + "\"]"];
972     },
973 
974     copyName: function(row)
975     {
976         var value = this.getRowPathName(row);
977         value = value[1]; //don't want the separator
978         System.copyToClipboard(value);
979     },
980 
981     copyPath: function(row)
982     {
983         var path = this.getPropertyPath(row);
984         System.copyToClipboard(path.join(""));
985     },
986 
987     /**
988      * Walk from the current row up to the most ancient parent, building an array.
989      * @return array of property names and separators, eg ['foo','.','bar'].
990      */
991     getPropertyPath: function(row)
992     {
993         var path = [];
994         for(var current = row; current ; current = getParentRow(current))
995             path = this.getRowPathName(current).concat(path);
996         path.shift(); //don't want the first separator
997         return path;
998     },
999 
1000     copyProperty: function(row)
1001     {
1002         var value = this.getRowPropertyValue(row);
1003         System.copyToClipboard(value);
1004     },
1005 
1006     editProperty: function(row, editValue)
1007     {
1008         var member = row.domObject;
1009         if (member && member.readOnly)
1010             return;
1011 
1012         if (Css.hasClass(row, "watchNewRow"))
1013         {
1014             Firebug.Editor.startEditing(row, "");
1015         }
1016         else if (Css.hasClass(row, "watchRow"))
1017         {
1018             Firebug.Editor.startEditing(row, getRowName(row));
1019         }
1020         else
1021         {
1022             var object = this.getRowObject(row);
1023             this.context.thisValue = object;
1024 
1025             if (!editValue)
1026             {
1027                 var propValue = this.getRowPropertyValue(row);
1028 
1029                 var type = typeof(propValue);
1030                 if (type == "undefined" || type == "number" || type == "boolean")
1031                     editValue = propValue;
1032                 else if (type == "string")
1033                     editValue = "\"" + Str.escapeJS(propValue) + "\"";
1034                 else if (propValue == null)
1035                     editValue = "null";
1036                 else if (object instanceof window.Window || object instanceof StackFrame.StackFrame)
1037                     editValue = getRowName(row);
1038                 else
1039                     editValue = "this." + getRowName(row);
1040             }
1041 
1042             Firebug.Editor.startEditing(row, editValue);
1043         }
1044     },
1045 
1046     deleteProperty: function(row)
1047     {
1048         if (Css.hasClass(row, "watchRow"))
1049         {
1050             this.deleteWatch(row);
1051         }
1052         else
1053         {
1054             var object = getRowOwnerObject(row);
1055             if (!object)
1056                 object = this.selection;
1057             object = this.getObjectView(object);
1058 
1059             if (object)
1060             {
1061                 var name = getRowName(row);
1062                 try
1063                 {
1064                     delete object[name];
1065                 }
1066                 catch (exc)
1067                 {
1068                     return;
1069                 }
1070 
1071                 this.rebuild(true);
1072                 this.markChange();
1073             }
1074         }
1075     },
1076 
1077     setPropertyValue: function(row, value)  // value must be string
1078     {
1079         if (FBTrace.DBG_DOM)
1080         {
1081             FBTrace.sysout("row: " + row);
1082             FBTrace.sysout("value: " + value + " type " + typeof(value), value);
1083         }
1084 
1085         var name = getRowName(row);
1086         if (name == "this")
1087             return;
1088 
1089         var object = this.getRealRowObject(row);
1090         if (object && !(object instanceof StackFrame.StackFrame))
1091         {
1092             Firebug.CommandLine.evaluate(value, this.context, object, this.context.getGlobalScope(),
1093                 function success(result, context)
1094                 {
1095                     if (FBTrace.DBG_DOM)
1096                     {
1097                         FBTrace.sysout("setPropertyValue evaluate success object[" + name + "]=" +
1098                             result + " type " + typeof(result), result);
1099                     }
1100                     object[name] = result;
1101                 },
1102                 function failed(exc, context)
1103                 {
1104                     try
1105                     {
1106                         if (FBTrace.DBG_DOM)
1107                         {
1108                             FBTrace.sysout("setPropertyValue evaluate failed with exc:" + exc +
1109                                 " object[" + name + "]=" + value + " type " + typeof(value), exc);
1110                         }
1111 
1112                         // If the value doesn't parse, then just store it as a string.
1113                         // Some users will not realize they're supposed to enter a JavaScript
1114                         // expression and just type literal text
1115                         object[name] = String(value);  // unwrappedJSobject.property = string
1116                     }
1117                     catch (exc)
1118                     {
1119                         return;
1120                     }
1121                 }
1122             );
1123         }
1124         else if (this.context.stopped)
1125         {
1126             try
1127             {
1128                 Firebug.CommandLine.evaluate(name + "=" + value, this.context);
1129             }
1130             catch (exc)
1131             {
1132                 try
1133                 {
1134                     // See catch block above...
1135                     object[name] = String(value); // unwrappedJSobject.property = string
1136                 }
1137                 catch (exc)
1138                 {
1139                     return;
1140                 }
1141             }
1142 
1143             // Clear cached scope chain (it'll be regenerated the next time the getScopes
1144             // is executed). This forces the watch window to update in case a closer scope
1145             // variables have been changed during a debugging session.
1146             if (object instanceof StackFrame.StackFrame)
1147                 object.clearScopes();
1148         }
1149 
1150         this.rebuild(true);
1151         this.markChange();
1152     },
1153 
1154     breakOnProperty: function(row)
1155     {
1156         var member = row.domObject;
1157         if (!member)
1158             return;
1159 
1160         // Bail out if this property is not breakable.
1161         if (!member.breakable)
1162             return;
1163 
1164         //xxxHonza: don't use getRowName to get the prop name. From some reason
1165         // unwatch doesn't work if row.firstChild.textContent is used.
1166         // It works only from within the watch handler method if the passed param
1167         // name is used.
1168         var name = member.name;
1169         if (name == "this")
1170             return;
1171 
1172         var object = this.getRowObject(row);
1173         object = this.getObjectView(object);
1174         if (!object)
1175             return;
1176 
1177         // Create new or remove an existing breakpoint.
1178         var breakpoints = this.context.dom.breakpoints;
1179         var bp = breakpoints.findBreakpoint(object, name);
1180         if (bp)
1181         {
1182             row.removeAttribute("breakpoint");
1183             breakpoints.removeBreakpoint(object, name);
1184         }
1185         else
1186         {
1187             breakpoints.addBreakpoint(object, name, this, row);
1188             row.setAttribute("breakpoint", "true");
1189         }
1190     },
1191 
1192     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1193     // extends Panel
1194 
1195     initialize: function()
1196     {
1197         this.objectPath = [];
1198         this.propertyPath = [];
1199         this.viewPath = [];
1200         this.pathIndex = -1;
1201         this.toggles = new ToggleBranch.ToggleBranch();
1202 
1203         Firebug.Panel.initialize.apply(this, arguments);
1204     },
1205 
1206     initializeNode: function(node)
1207     {
1208         Firebug.Panel.initializeNode.apply(this, arguments);
1209     },
1210 
1211     destroyNode: function()
1212     {
1213         Firebug.Panel.destroyNode.apply(this, arguments);
1214     },
1215 
1216     destroy: function(state)
1217     {
1218         var view = this.viewPath[this.pathIndex];
1219         if (view && this.panelNode.scrollTop)
1220             view.scrollTop = this.panelNode.scrollTop;
1221 
1222         if (this.pathIndex > -1)
1223             state.pathIndex = this.pathIndex;
1224         if (this.viewPath)
1225             state.viewPath = this.viewPath;
1226         if (this.propertyPath)
1227             state.propertyPath = this.propertyPath;
1228 
1229         if (this.propertyPath.length > 0 && !this.propertyPath[1])
1230             state.firstSelection = Persist.persistObject(this.getPathObject(1), this.context);
1231 
1232         if (FBTrace.DBG_DOM)
1233             FBTrace.sysout("dom.destroy; state:", state);
1234 
1235         Firebug.Panel.destroy.apply(this, arguments);
1236     },
1237 
1238     show: function(state)
1239     {
1240         this.showToolbarButtons("fbStatusButtons", true);
1241 
1242         if (!this.selection)
1243         {
1244             if (!state)
1245             {
1246                 this.select(null);
1247                 return;
1248             }
1249             if (state.pathIndex > -1)
1250                 this.pathIndex = state.pathIndex;
1251             if (state.viewPath)
1252                 this.viewPath = state.viewPath;
1253             if (state.propertyPath)
1254                 this.propertyPath = state.propertyPath;
1255 
1256             var defaultObject = this.getDefaultSelection();
1257             var selectObject = defaultObject;
1258 
1259             if (state.firstSelection)
1260             {
1261                 var restored = state.firstSelection(this.context);
1262                 if (restored)
1263                 {
1264                     selectObject = restored;
1265                     this.objectPath = [defaultObject, restored];
1266                 }
1267                 else
1268                     this.objectPath = [defaultObject];
1269             }
1270             else
1271             {
1272                 this.objectPath = [defaultObject];
1273             }
1274 
1275             if (this.propertyPath.length > 1)
1276             {
1277                 selectObject = this.resetPaths(selectObject);
1278             }
1279             else
1280             {
1281                 // Sync with objectPath always containing a default object.
1282                 this.propertyPath.push(null);
1283             }
1284 
1285             var selection = state.pathIndex < this.objectPath.length
1286                 ? this.getPathObject(state.pathIndex)
1287                 : this.getPathObject(this.objectPath.length-1);
1288 
1289             if (FBTrace.DBG_DOM)
1290                 FBTrace.sysout("dom.show; selection:", selection);
1291 
1292             this.select(selection);
1293         }
1294     },
1295 
1296     resetPaths: function(selectObject)
1297     {
1298         for (var i = 1; i < this.propertyPath.length; ++i)
1299         {
1300             var name = this.propertyPath[i];
1301             if (!name)
1302                 continue;
1303 
1304             var object = selectObject;
1305             try
1306             {
1307                 selectObject = object[name];
1308             }
1309             catch (exc)
1310             {
1311                 selectObject = null;
1312             }
1313 
1314             if (selectObject)
1315             {
1316                 this.objectPath.push(new FirebugReps.PropertyObj(object, name));
1317             }
1318             else
1319             {
1320                 // If we can't access a property, just stop
1321                 this.viewPath.splice(i);
1322                 this.propertyPath.splice(i);
1323                 this.objectPath.splice(i);
1324                 selectObject = this.getPathObject(this.objectPath.length-1);
1325                 break;
1326             }
1327         }
1328     },
1329 
1330     hide: function()
1331     {
1332         var view = this.viewPath[this.pathIndex];
1333         if (view && this.panelNode.scrollTop)
1334             view.scrollTop = this.panelNode.scrollTop;
1335     },
1336 
1337     getBreakOnNextTooltip: function(enabled)
1338     {
1339         return (enabled ? Locale.$STR("dom.disableBreakOnPropertyChange") :
1340             Locale.$STR("dom.label.breakOnPropertyChange"));
1341     },
1342 
1343     supportsObject: function(object, type)
1344     {
1345         if (object == null)
1346             return 1000;
1347 
1348         if (typeof(object) == "undefined")
1349             return 1000;
1350         else if (object instanceof SourceLink.SourceLink)
1351             return 0;
1352         else
1353             return 1; // just agree to support everything but not aggressively.
1354     },
1355 
1356     refresh: function()
1357     {
1358         this.rebuild(true);
1359     },
1360 
1361     updateSelection: function(object)
1362     {
1363         if (FBTrace.DBG_DOM)
1364             FBTrace.sysout("dom.updateSelection; object=" + object, object);
1365 
1366         var previousIndex = this.pathIndex;
1367         var previousView = previousIndex == -1 ? null : this.viewPath[previousIndex];
1368 
1369         var newPath = this.pathToAppend;
1370         delete this.pathToAppend;
1371 
1372         var pathIndex = this.findPathIndex(object);
1373         if (newPath || pathIndex == -1)
1374         {
1375             this.toggles = new ToggleBranch.ToggleBranch();
1376 
1377             if (newPath)
1378             {
1379                 // Remove everything after the point where we are inserting, so we
1380                 // essentially replace it with the new path
1381                 if (previousView)
1382                 {
1383                     if (this.panelNode.scrollTop)
1384                         previousView.scrollTop = this.panelNode.scrollTop;
1385 
1386                     this.objectPath.splice(previousIndex+1);
1387                     this.propertyPath.splice(previousIndex+1);
1388                     this.viewPath.splice(previousIndex+1);
1389                 }
1390 
1391                 var value = this.getPathObject(previousIndex);
1392                 if (!value)
1393                 {
1394                     if (FBTrace.DBG_ERRORS)
1395                         FBTrace.sysout("dom.updateSelection no pathObject for " + previousIndex);
1396                     return;
1397                 }
1398 
1399                 for (var i = 0; i < newPath.length; ++i)
1400                 {
1401                     var name = newPath[i];
1402                     var object = value;
1403                     try
1404                     {
1405                         value = value[name];
1406                     }
1407                     catch(exc)
1408                     {
1409                         if (FBTrace.DBG_ERRORS)
1410                         {
1411                             FBTrace.sysout("dom.updateSelection FAILS at path_i=" + i +
1412                                 " for name:" + name);
1413                         }
1414                         return;
1415                     }
1416 
1417                     ++this.pathIndex;
1418                     this.objectPath.push(new FirebugReps.PropertyObj(object, name));
1419                     this.propertyPath.push(name);
1420                     this.viewPath.push({toggles: this.toggles, scrollTop: 0});
1421                 }
1422             }
1423             else
1424             {
1425                 this.toggles = new ToggleBranch.ToggleBranch();
1426 
1427                 var win = this.getDefaultSelection();
1428                 if (object == win)
1429                 {
1430                     this.pathIndex = 0;
1431                     this.objectPath = [win];
1432                     this.propertyPath = [null];
1433                     this.viewPath = [{toggles: this.toggles, scrollTop: 0}];
1434                 }
1435                 else
1436                 {
1437                     this.pathIndex = 1;
1438                     this.objectPath = [win, object];
1439                     this.propertyPath = [null, null];
1440                     this.viewPath = [
1441                         {toggles: new ToggleBranch.ToggleBranch(), scrollTop: 0},
1442                         {toggles: this.toggles, scrollTop: 0}
1443                     ];
1444                 }
1445             }
1446 
1447             this.panelNode.scrollTop = 0;
1448             this.rebuild();
1449         }
1450         else
1451         {
1452             this.pathIndex = pathIndex;
1453 
1454             var view = this.viewPath[pathIndex];
1455             this.toggles = view ? view.toggles : new ToggleBranch.ToggleBranch();
1456 
1457             // Persist the current scroll location
1458             if (previousView && this.panelNode.scrollTop)
1459                 previousView.scrollTop = this.panelNode.scrollTop;
1460 
1461             this.rebuild(false, view ? view.scrollTop : 0);
1462         }
1463 
1464     },
1465 
1466     getObjectPath: function(object)
1467     {
1468         return this.objectPath;
1469     },
1470 
1471     getDefaultSelection: function()
1472     {
1473         return this.getObjectView(this.context.getGlobalScope());
1474     },
1475 
1476     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1477     // Options
1478 
1479     updateOption: function(name, value)
1480     {
1481         var options = new Set();
1482         options.add("showUserProps");
1483         options.add("showUserFuncs");
1484         options.add("showDOMProps");
1485         options.add("showDOMFuncs");
1486         options.add("showDOMConstants");
1487         options.add("showInlineEventHandlers");
1488         options.add("showOwnProperties");
1489         options.add("showEnumerableProperties");
1490 
1491         if (options.has(name))
1492             this.rebuild(true);
1493     },
1494 
1495     getOptionsMenuItems: function()
1496     {
1497         return [
1498             Menu.optionMenu("ShowUserProps", "showUserProps",
1499                 "dom.option.tip.Show User Props"),
1500             Menu.optionMenu("ShowUserFuncs", "showUserFuncs",
1501                 "dom.option.tip.Show_User_Funcs"),
1502             Menu.optionMenu("ShowDOMProps", "showDOMProps",
1503                 "dom.option.tip.Show_DOM_Props"),
1504             Menu.optionMenu("ShowDOMFuncs", "showDOMFuncs",
1505                 "dom.option.tip.Show_DOM_Funcs"),
1506             Menu.optionMenu("ShowDOMConstants", "showDOMConstants",
1507                 "dom.option.tip.Show_DOM_Constants"),
1508             Menu.optionMenu("ShowInlineEventHandlers", "showInlineEventHandlers",
1509                 "ShowInlineEventHandlersTooltip"),
1510             "-",
1511             Menu.optionMenu("ShowOwnProperties", "showOwnProperties",
1512                 "ShowOwnPropertiesTooltip"),
1513             Menu.optionMenu("ShowEnumerableProperties",
1514                 "showEnumerableProperties", "ShowEnumerablePropertiesTooltip"),
1515             "-",
1516             {label: "Refresh", command: Obj.bindFixed(this.rebuild, this, true),
1517                 tooltiptext: "panel.tip.Refresh"}
1518         ];
1519     },
1520 
1521     getContextMenuItems: function(object, target)
1522     {
1523         if (FBTrace.DBG_DOM)
1524             FBTrace.sysout("dom.getContextMenuItems;", object);
1525 
1526         var row = Dom.getAncestorByClass(target, "memberRow");
1527 
1528         var items = [];
1529 
1530         if (row)
1531         {
1532             var rowName = getRowName(row);
1533             var rowObject = this.getRowObject(row);
1534             var rowValue = this.getRowPropertyValue(row);
1535             var member = row.domObject;
1536 
1537             var isWatch = Css.hasClass(row, "watchRow");
1538             var isStackFrame = rowObject instanceof StackFrame.StackFrame;
1539             var label, tooltiptext;
1540 
1541             items.push(
1542                 "-",
1543                 {
1544                     label: "Copy_Name",
1545                     tooltiptext: "dom.tip.Copy_Name",
1546                     command: Obj.bindFixed(this.copyName, this, row)
1547                 },
1548                 {
1549                     label: "Copy_Path",
1550                     tooltiptext: "dom.tip.Copy_Path",
1551                     command: Obj.bindFixed(this.copyPath, this, row)
1552                 }
1553             );
1554 
1555             if (typeof(rowValue) == "string" || typeof(rowValue) == "number")
1556             {
1557                 // Functions already have a copy item in their context menu
1558                 items.push(
1559                     {
1560                         label: "CopyValue",
1561                         tooltiptext: "dom.tip.Copy_Value",
1562                         command: Obj.bindFixed(this.copyProperty, this, row)
1563                     }
1564                 );
1565             }
1566 
1567             if (isWatch)
1568             {
1569                 label = "EditWatch";
1570                 tooltiptext = "watch.tip.Edit_Watch";
1571             }
1572             else if (isStackFrame)
1573             {
1574                 label = "EditVariable";
1575                 tooltiptext = "stack.tip.Edit_Variable";
1576             }
1577             else
1578             {
1579                 label = "EditProperty";
1580                 tooltiptext = "dom.tip.Edit_Property";
1581             }
1582 
1583             var readOnly = (!isWatch && !isStackFrame && member && member.readOnly);
1584             if (!readOnly)
1585             {
1586                 items.push(
1587                     "-",
1588                     {
1589                         label: label,
1590                         tooltiptext: tooltiptext,
1591                         command: Obj.bindFixed(this.editProperty, this, row)
1592                     }
1593                 );
1594             }
1595 
1596             if (isWatch || (!isStackFrame && !Dom.isDOMMember(rowObject, rowName)))
1597             {
1598                 items.push(
1599                     {
1600                         label: isWatch ? "DeleteWatch" : "DeleteProperty",
1601                         id: "DeleteProperty",
1602                         tooltiptext: isWatch ? "watch.tip.Delete_Watch" :
1603                             "dom.tip.Delete_Property",
1604                         command: Obj.bindFixed(this.deleteProperty, this, row)
1605                     }
1606                 );
1607             }
1608 
1609             if (!Dom.isDOMMember(rowObject, rowName) && member && member.breakable)
1610             {
1611                 items.push(
1612                     "-",
1613                     {
1614                         label: "dom.label.breakOnPropertyChange",
1615                         tooltiptext: "dom.tip.Break_On_Property_Change",
1616                         type: "checkbox",
1617                         checked: this.context.dom.breakpoints.findBreakpoint(rowObject, rowName),
1618                         command: Obj.bindFixed(this.breakOnProperty, this, row)
1619                     }
1620                 );
1621             }
1622         }
1623 
1624         items.push(
1625             "-",
1626             {
1627                 label: "Refresh",
1628                 tooltiptext: "panel.tip.Refresh",
1629                 command: Obj.bindFixed(this.rebuild, this, true)
1630             }
1631         );
1632 
1633         return items;
1634     },
1635 
1636     getEditor: function(target, value)
1637     {
1638         if (!this.editor)
1639             this.editor = new DOMEditor(this.document);
1640 
1641         return this.editor;
1642     }
1643 });
1644 
1645 // ********************************************************************************************* //
1646 
1647 var DOMMainPanel = Firebug.DOMPanel = function () {};
1648 
1649 Firebug.DOMPanel.DirTable = DirTablePlate;
1650 
1651 DOMMainPanel.prototype = Obj.extend(Firebug.DOMBasePanel.prototype,
1652 {
1653     selectRow: function(row, target)
1654     {
1655         if (!target)
1656             target = row.lastChild.firstChild;
1657 
1658         if (!target || !target.repObject)
1659             return;
1660 
1661         this.pathToAppend = getPath(row);
1662 
1663         // If the object is inside an array, look up its index
1664         var valueBox = row.lastChild.firstChild;
1665         if (Css.hasClass(valueBox, "objectBox-array"))
1666         {
1667             var arrayIndex = FirebugReps.Arr.getItemIndex(target);
1668             this.pathToAppend.push(arrayIndex);
1669         }
1670 
1671         // Make sure we get a fresh status path for the object, since otherwise
1672         // it might find the object in the existing path and not refresh it
1673         Firebug.chrome.clearStatusPath();
1674 
1675         this.select(target.repObject, true);
1676     },
1677 
1678     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1679 
1680     onClick: function(event)
1681     {
1682         var repNode = Firebug.getRepNode(event.target);
1683         if (repNode)
1684         {
1685             var row = Dom.getAncestorByClass(event.target, "memberRow");
1686             if (row)
1687             {
1688                 this.selectRow(row, repNode);
1689                 Events.cancelEvent(event);
1690             }
1691         }
1692     },
1693 
1694     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1695     // extends Panel
1696 
1697     name: "dom",
1698     searchable: true,
1699     statusSeparator: ">",
1700     enableA11y: true,
1701     deriveA11yFrom: "console",
1702     searchType : "dom",
1703     order: 50,
1704     inspectable: true,
1705 
1706     initialize: function()
1707     {
1708         this.onClick = Obj.bind(this.onClick, this);
1709 
1710         Firebug.DOMBasePanel.prototype.initialize.apply(this, arguments);
1711     },
1712 
1713     initializeNode: function(oldPanelNode)
1714     {
1715         Events.addEventListener(this.panelNode, "click", this.onClick, false);
1716 
1717         Firebug.DOMBasePanel.prototype.initializeNode.apply(this, arguments);
1718     },
1719 
1720     destroyNode: function()
1721     {
1722         Events.removeEventListener(this.panelNode, "click", this.onClick, false);
1723 
1724         Firebug.DOMBasePanel.prototype.destroyNode.apply(this, arguments);
1725     },
1726 
1727     search: function(text, reverse)
1728     {
1729         if (!text)
1730         {
1731             delete this.currentSearch;
1732             this.highlightNode(null);
1733             this.document.defaultView.getSelection().removeAllRanges();
1734             return false;
1735         }
1736 
1737         var row;
1738         if (this.currentSearch && text == this.currentSearch.text)
1739         {
1740             row = this.currentSearch.findNext(true, undefined, reverse,
1741                 Firebug.Search.isCaseSensitive(text));
1742         }
1743         else
1744         {
1745             function findRow(node) { return Dom.getAncestorByClass(node, "memberRow"); }
1746             this.currentSearch = new Search.TextSearch(this.panelNode, findRow);
1747             row = this.currentSearch.find(text, reverse, Firebug.Search.isCaseSensitive(text));
1748         }
1749 
1750         if (row)
1751         {
1752             var sel = this.document.defaultView.getSelection();
1753             sel.removeAllRanges();
1754             sel.addRange(this.currentSearch.range);
1755 
1756             Dom.scrollIntoCenterView(row, this.panelNode);
1757 
1758             this.highlightNode(row);
1759             Events.dispatch(this.fbListeners, 'onDomSearchMatchFound', [this, text, row]);
1760             return true;
1761         }
1762         else
1763         {
1764             this.document.defaultView.getSelection().removeAllRanges();
1765             Events.dispatch(this.fbListeners, 'onDomSearchMatchFound', [this, text, null]);
1766             return false;
1767         }
1768     }
1769 });
1770 
1771 // ********************************************************************************************* //
1772 
1773 function DOMSidePanel() {}
1774 
1775 DOMSidePanel.prototype = Obj.extend(Firebug.DOMBasePanel.prototype,
1776 {
1777     name: "domSide",
1778     parentPanel: "html",
1779     order: 3,
1780     enableA11y: true,
1781     deriveA11yFrom: "console",
1782 });
1783 
1784 // ********************************************************************************************* //
1785 // Local Helpers
1786 
1787 function DOMEditor(doc)
1788 {
1789     this.box = this.tag.replace({}, doc, this);
1790     this.input = this.box.childNodes[1];
1791 
1792     var completionBox = this.box.childNodes[0];
1793     var options = {
1794         includeCurrentScope: true
1795     };
1796     this.setupCompleter(completionBox, options);
1797 }
1798 
1799 DOMEditor.prototype = domplate(Firebug.JSEditor.prototype,
1800 {
1801     tag:
1802         DIV({style: "position: absolute;"},
1803             INPUT({"class": "fixedWidthEditor completionBox", type: "text",
1804                 tabindex: "-1"}),
1805             INPUT({"class": "fixedWidthEditor completionInput", type: "text",
1806                 oninput: "$onInput", onkeypress: "$onKeyPress"})),
1807 
1808     endEditing: function(target, value, cancel)
1809     {
1810         // XXXjoe Kind of hackish - fix me
1811         delete this.panel.context.thisValue;
1812 
1813         if (cancel || value == "")
1814             return;
1815 
1816         var row = Dom.getAncestorByClass(target, "memberRow");
1817 
1818         Events.dispatch(this.panel.fbListeners, "onWatchEndEditing", [this.panel]);
1819 
1820         if (!row)
1821             this.panel.addWatch(value);
1822         else if (Css.hasClass(row, "watchRow"))
1823             this.panel.setWatchValue(row, value);
1824         else
1825             this.panel.setPropertyValue(row, value);
1826     }
1827 });
1828 
1829 // ********************************************************************************************* //
1830 // Local Helpers
1831 
1832 function isClassFunction(fn)
1833 {
1834     try
1835     {
1836         for (var name in fn.prototype)
1837             return true;
1838     } catch (exc) {}
1839     return false;
1840 }
1841 
1842 function isArguments(obj)
1843 {
1844     try
1845     {
1846         return isFinite(obj.length) && obj.length > 0 && typeof obj.callee === "function";
1847     } catch (exc) {}
1848     return false;
1849 }
1850 
1851 function isPrototype(name)
1852 {
1853     return (name == "prototype" || name == "__proto__");
1854 }
1855 
1856 function isReadOnly(object, propName)
1857 {
1858     try
1859     {
1860         var desc;
1861         while (object)
1862         {
1863             desc = Object.getOwnPropertyDescriptor(object, propName);
1864             if (desc)
1865                 break;
1866             object = Object.getPrototypeOf(object);
1867         }
1868         return (desc && !desc.writable && !desc.set);
1869     }
1870     catch (e)
1871     {
1872     }
1873 }
1874 
1875 function getRowName(row)
1876 {
1877     var labelNode = row.getElementsByClassName("memberLabelCell").item(0);
1878     return labelNode.textContent;
1879 }
1880 
1881 function getRowValue(row)
1882 {
1883     var valueNode = row.getElementsByClassName("memberValueCell").item(0);
1884     return valueNode.firstChild.repObject;
1885 }
1886 
1887 function getRowOwnerObject(row)
1888 {
1889     var parentRow = getParentRow(row);
1890     if (parentRow)
1891         return getRowValue(parentRow);
1892 }
1893 
1894 function getParentRow(row)
1895 {
1896     var level = parseInt(row.getAttribute("level"))-1;
1897     // If it's top level object the level is now set to -1, is that a problem?
1898     for (row = row.previousSibling; row; row = row.previousSibling)
1899     {
1900         if (parseInt(row.getAttribute("level")) == level)
1901             return row;
1902     }
1903 }
1904 
1905 function getPath(row)
1906 {
1907     var name = getRowName(row);
1908     var path = [name];
1909 
1910     var level = parseInt(row.getAttribute("level"))-1;
1911     for (row = row.previousSibling; row; row = row.previousSibling)
1912     {
1913         if (parseInt(row.getAttribute("level")) == level)
1914         {
1915             var name = getRowName(row);
1916             path.splice(0, 0, name);
1917 
1918             --level;
1919         }
1920     }
1921 
1922     return path;
1923 }
1924 
1925 // ********************************************************************************************* //
1926 // Registration
1927 
1928 // xxxHonza: Every panel should have its own module.
1929 Firebug.registerPanel(DOMMainPanel);
1930 Firebug.registerPanel(DOMSidePanel);
1931 
1932 return Firebug.DOMModule;
1933 
1934 // ********************************************************************************************* //
1935 }});
1936 
1937