1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/lib/object",
  5     "firebug/lib/events",
  6     "firebug/lib/css",
  7     "firebug/lib/dom",
  8     "firebug/lib/search",
  9     "firebug/lib/xml",
 10     "firebug/lib/string",
 11 ],
 12 function(Obj, Events, Css, Dom, Search, Xml, Str) {
 13 
 14 // ********************************************************************************************* //
 15 // Constants
 16 
 17 const Ci = Components.interfaces;
 18 const SHOW_ALL = Ci.nsIDOMNodeFilter.SHOW_ALL;
 19 
 20 // ********************************************************************************************* //
 21 
 22 /**
 23  * @class Static utility class. Contains utilities used for displaying and
 24  *        searching a HTML tree.
 25  */
 26 var HTMLLib =
 27 {
 28     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 29     // Node Search Utilities
 30 
 31     /**
 32      * Constructs a NodeSearch instance.
 33      *
 34      * @class Class used to search a DOM tree for the given text. Will display
 35      *        the search results in a IO Box.
 36      *
 37      * @constructor
 38      * @param {String} text Text to search for
 39      * @param {Object} root Root of search. This may be an element or a document
 40      * @param {Object} panelNode Panel node containing the IO Box representing the DOM tree.
 41      * @param {Object} ioBox IO Box to display the search results in
 42      * @param {Object} walker Optional walker parameter.
 43      */
 44     NodeSearch: function(text, root, panelNode, ioBox, walker)
 45     {
 46         root = root.documentElement || root;
 47         walker = walker || new HTMLLib.DOMWalker(root);
 48         var re = new Search.ReversibleRegExp(text, "m");
 49         var matchCount = 0;
 50 
 51         /**
 52          * Finds the first match within the document.
 53          *
 54          * @param {boolean} revert true to search backward, false to search forward
 55          * @param {boolean} caseSensitive true to match exact case, false to ignore case
 56          * @return true if no more matches were found, but matches were found previously.
 57          */
 58         this.find = function(reverse, caseSensitive)
 59         {
 60             var match = this.findNextMatch(reverse, caseSensitive);
 61             if (match)
 62             {
 63                 this.lastMatch = match;
 64                 ++matchCount;
 65 
 66                 var node = match.node;
 67                 var nodeBox = this.openToNode(node, match.isValue);
 68 
 69                 this.selectMatched(nodeBox, node, match, reverse);
 70             }
 71             else if (matchCount)
 72             {
 73                 return true;
 74             }
 75             else
 76             {
 77                 this.noMatch = true;
 78                 Events.dispatch([Firebug.A11yModel], "onHTMLSearchNoMatchFound",
 79                     [panelNode.ownerPanel, text]);
 80             }
 81         };
 82 
 83         /**
 84          * Resets the search to the beginning of the document.
 85          */
 86         this.reset = function()
 87         {
 88             delete this.lastMatch;
 89         };
 90 
 91         /**
 92          * Finds the next match in the document.
 93          *
 94          * The return value is an object with the fields
 95          * - node: Node that contains the match
 96          * - isValue: true if the match is a match due to the value of the node, false if it is due to the name
 97          * - match: Regular expression result from the match
 98          *
 99          * @param {boolean} revert true to search backward, false to search forward
100          * @param {boolean} caseSensitive true to match exact case, false to ignore case
101          * @return Match object if found
102          */
103         this.findNextMatch = function(reverse, caseSensitive)
104         {
105             var innerMatch = this.findNextInnerMatch(reverse, caseSensitive);
106             if (innerMatch)
107                 return innerMatch;
108             else
109                 this.reset();
110 
111             function walkNode() { return reverse ? walker.previousNode() : walker.nextNode(); }
112 
113             var node;
114             while (node = walkNode())
115             {
116                 if (node.nodeType == Node.TEXT_NODE && HTMLLib.isSourceElement(node.parentNode))
117                     continue;
118 
119                 var m = this.checkNode(node, reverse, caseSensitive);
120                 if (m)
121                     return m;
122             }
123         };
124 
125         /**
126          * Helper util used to scan the current search result for more results
127          * in the same object.
128          *
129          * @private
130          */
131         this.findNextInnerMatch = function(reverse, caseSensitive)
132         {
133             if (this.lastMatch)
134             {
135                 var lastMatchNode = this.lastMatch.node;
136                 var lastReMatch = this.lastMatch.match;
137                 var m = re.exec(lastReMatch.input, reverse, lastReMatch.caseSensitive, lastReMatch);
138                 if (m)
139                 {
140                     return {
141                         node: lastMatchNode,
142                         isValue: this.lastMatch.isValue,
143                         match: m
144                     };
145                 }
146 
147                 // May need to check the pair for attributes
148                 if (lastMatchNode.nodeType == Node.ATTRIBUTE_NODE &&
149                     this.lastMatch.isValue == !!reverse)
150                 {
151                     return this.checkNode(lastMatchNode, reverse, caseSensitive, 1);
152                 }
153             }
154         };
155 
156         /**
157          * Checks a given node for a search match.
158          *
159          * @private
160          */
161         this.checkNode = function(node, reverse, caseSensitive, firstStep)
162         {
163             var checkOrder;
164             if (node.nodeType != Node.TEXT_NODE)
165             {
166                 var nameCheck = { name: "nodeName", isValue: false, caseSensitive: caseSensitive };
167                 var valueCheck = { name: "nodeValue", isValue: true, caseSensitive: caseSensitive };
168                 checkOrder = reverse ? [ valueCheck, nameCheck ] : [ nameCheck, valueCheck ];
169             }
170             else
171             {
172                 checkOrder = [{name: "nodeValue", isValue: false, caseSensitive: caseSensitive }];
173             }
174 
175             for (var i = firstStep || 0; i < checkOrder.length; i++)
176             {
177                 var m = re.exec(node[checkOrder[i].name], reverse, checkOrder[i].caseSensitive);
178                 if (m) {
179                     return {
180                         node: node,
181                         isValue: checkOrder[i].isValue,
182                         match: m
183                     };
184                 }
185             }
186         };
187 
188         /**
189          * Opens the given node in the associated IO Box.
190          *
191          * @private
192          */
193         this.openToNode = function(node, isValue)
194         {
195             if (node.nodeType == Node.ELEMENT_NODE)
196             {
197                 var nodeBox = ioBox.openToObject(node);
198                 return nodeBox.getElementsByClassName("nodeTag")[0];
199             }
200             else if (node.nodeType == Node.ATTRIBUTE_NODE)
201             {
202                 var nodeBox = ioBox.openToObject(node.ownerElement);
203                 if (nodeBox)
204                 {
205                     var attrNodeBox = HTMLLib.findNodeAttrBox(nodeBox, node.name);
206                     return Dom.getChildByClass(attrNodeBox, isValue ? "nodeValue" : "nodeName");
207                 }
208             }
209             else if (node.nodeType == Node.TEXT_NODE)
210             {
211                 var nodeBox = ioBox.openToObject(node);
212                 if (nodeBox)
213                 {
214                     return nodeBox;
215                 }
216                 else
217                 {
218                     var nodeBox = ioBox.openToObject(node.parentNode);
219                     if (Css.hasClass(nodeBox, "textNodeBox"))
220                         nodeBox = HTMLLib.getTextElementTextBox(nodeBox);
221                     return nodeBox;
222                 }
223             }
224         };
225 
226         /**
227          * Selects the search results.
228          *
229          * @private
230          */
231         this.selectMatched = function(nodeBox, node, match, reverse)
232         {
233             setTimeout(Obj.bindFixed(function()
234             {
235                 var reMatch = match.match;
236                 this.selectNodeText(nodeBox, node, reMatch[0], reMatch.index, reverse,
237                     reMatch.caseSensitive);
238 
239                 Events.dispatch([Firebug.A11yModel], "onHTMLSearchMatchFound",
240                     [panelNode.ownerPanel, match]);
241             }, this));
242         };
243 
244         /**
245          * Select text node search results.
246          *
247          * @private
248          */
249         this.selectNodeText = function(nodeBox, node, text, index, reverse, caseSensitive)
250         {
251             var row;
252 
253             // If we are still inside the same node as the last search, advance the range
254             // to the next substring within that node
255             if (nodeBox == this.lastNodeBox)
256             {
257                 row = this.textSearch.findNext(false, true, reverse, caseSensitive);
258             }
259 
260             if (!row)
261             {
262                 // Search for the first instance of the string inside the node
263                 function findRow(node)
264                 {
265                     return node.nodeType == Node.ELEMENT_NODE ? node : node.parentNode;
266                 }
267 
268                 this.textSearch = new Search.TextSearch(nodeBox, findRow);
269                 row = this.textSearch.find(text, reverse, caseSensitive);
270                 this.lastNodeBox = nodeBox;
271             }
272 
273             if (row)
274             {
275                 var trueNodeBox = Dom.getAncestorByClass(nodeBox, "nodeBox");
276                 Css.setClass(trueNodeBox, "search-selection");
277 
278                 Dom.scrollIntoCenterView(row, panelNode);
279                 var sel = panelNode.ownerDocument.defaultView.getSelection();
280                 sel.removeAllRanges();
281                 sel.addRange(this.textSearch.range);
282 
283                 Css.removeClass(trueNodeBox, "search-selection");
284                 return true;
285             }
286         };
287     },
288 
289     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
290 
291     /**
292      * XXXjjb this code is no longer called and won't be in 1.5; if FireFinder works out we can delete this.
293      * Constructs a SelectorSearch instance.
294      *
295      * @class Class used to search a DOM tree for elements matching the given
296      *        CSS selector.
297      *
298      * @constructor
299      * @param {String} text CSS selector to search for
300      * @param {Document} doc Document to search
301      * @param {Object} panelNode Panel node containing the IO Box representing the DOM tree.
302      * @param {Object} ioBox IO Box to display the search results in
303      */
304     SelectorSearch: function(text, doc, panelNode, ioBox)
305     {
306         this.parent = new HTMLLib.NodeSearch(text, doc, panelNode, ioBox);
307 
308         /**
309          * Finds the first match within the document.
310          *
311          * @param {boolean} revert true to search backward, false to search forward
312          * @param {boolean} caseSensitive true to match exact case, false to ignore case
313          * @return true if no more matches were found, but matches were found previously.
314          */
315         this.find = this.parent.find;
316 
317         /**
318          * Resets the search to the beginning of the document.
319          */
320         this.reset = this.parent.reset;
321 
322         /**
323          * Opens the given node in the associated IO Box.
324          *
325          * @private
326          */
327         this.openToNode = this.parent.openToNode;
328 
329         try
330         {
331             // http://dev.w3.org/2006/webapi/selectors-api/
332             this.matchingNodes = doc.querySelectorAll(text);
333             this.matchIndex = 0;
334         }
335         catch(exc)
336         {
337             FBTrace.sysout("SelectorSearch FAILS "+exc, exc);
338         }
339 
340         /**
341          * Finds the next match in the document.
342          *
343          * The return value is an object with the fields
344          * - node: Node that contains the match
345          * - isValue: true if the match is a match due to the value of the node, false if it is due to the name
346          * - match: Regular expression result from the match
347          *
348          * @param {boolean} revert true to search backward, false to search forward
349          * @param {boolean} caseSensitive true to match exact case, false to ignore case
350          * @return Match object if found
351          */
352         this.findNextMatch = function(reverse, caseSensitive)
353         {
354             if (!this.matchingNodes || !this.matchingNodes.length)
355                 return undefined;
356 
357             if (reverse)
358             {
359                 if (this.matchIndex > 0)
360                     return { node: this.matchingNodes[this.matchIndex--], isValue: false, match: "?XX?"};
361                 else
362                     return undefined;
363             }
364             else
365             {
366                 if (this.matchIndex < this.matchingNodes.length)
367                     return { node: this.matchingNodes[this.matchIndex++], isValue: false, match: "?XX?"};
368                 else
369                     return undefined;
370             }
371         };
372 
373         /**
374          * Selects the search results.
375          *
376          * @private
377          */
378         this.selectMatched = function(nodeBox, node, match, reverse)
379         {
380             setTimeout(Obj.bindFixed(function()
381             {
382                 ioBox.select(node, true, true);
383                 Events.dispatch([Firebug.A11yModel], "onHTMLSearchMatchFound", [panelNode.ownerPanel, match]);
384             }, this));
385         };
386     },
387 
388 
389     /**
390      * Constructs a DOMWalker instance.
391      *
392      * @constructor
393      * @class Implements an ordered traveral of the document, including attributes and
394      *        iframe contents within the results.
395      *
396      *        Note that the order for attributes is not defined. This will follow the
397      *        same order as the Element.attributes accessor.
398      * @param {Element} root Element to traverse
399      */
400     DOMWalker: function(root)
401     {
402         var walker;
403         var currentNode, attrIndex;
404         var pastStart, pastEnd;
405         var doc = root.ownerDocument;
406 
407         function createWalker(docElement)
408         {
409             var walk = doc.createTreeWalker(docElement, SHOW_ALL, null, true);
410             walker.unshift(walk);
411         }
412 
413         function getLastAncestor()
414         {
415             while (walker[0].lastChild()) {}
416             return walker[0].currentNode;
417         }
418 
419         /**
420          * Move to the previous node.
421          *
422          * @return The previous node if one exists, undefined otherwise.
423          */
424         this.previousNode = function()
425         {
426             if (pastStart)
427                 return undefined;
428 
429             if (attrIndex)
430             {
431                 attrIndex--;
432             }
433             else
434             {
435                 var prevNode;
436                 if (currentNode == walker[0].root)
437                 {
438                     if (walker.length > 1)
439                     {
440                         walker.shift();
441                         prevNode = walker[0].currentNode;
442                     }
443                     else
444                     {
445                         prevNode = undefined;
446                     }
447                 }
448                 else
449                 {
450                     prevNode = !currentNode ? getLastAncestor(): walker[0].previousNode();
451 
452                     // Really shouldn't occur, but to be safe
453                     if (!prevNode)
454                         prevNode = walker[0].root;
455 
456                     while ((prevNode.nodeName || "").toUpperCase() == "IFRAME")
457                     {
458                         createWalker(prevNode.contentDocument.documentElement);
459                         prevNode = getLastAncestor();
460                     }
461                 }
462                 currentNode = prevNode;
463                 attrIndex = ((prevNode || {}).attributes || []).length;
464             }
465 
466             if (!currentNode)
467                 pastStart = true;
468             else
469                 pastEnd = false;
470 
471             return this.currentNode();
472         };
473 
474         /**
475          * Move to the next node.
476          *
477          * @return The next node if one exists, otherwise undefined.
478          */
479         this.nextNode = function()
480         {
481             if (pastEnd)
482                 return undefined;
483 
484             if (!currentNode)
485             {
486                 // We are working with a new tree walker
487                 currentNode = walker[0].root;
488                 attrIndex = 0;
489             }
490             else
491             {
492                 // First check attributes
493                 var attrs = currentNode.attributes || [];
494                 if (attrIndex < attrs.length)
495                 {
496                     attrIndex++;
497                 }
498                 else if ((currentNode.nodeName || "").toUpperCase() == "IFRAME")
499                 {
500                     // Attributes have completed, check for iframe contents
501                     createWalker(currentNode.contentDocument.documentElement);
502                     currentNode = walker[0].root;
503                     attrIndex = 0;
504                 }
505                 else
506                 {
507                     // Next node
508                     var nextNode = walker[0].nextNode();
509                     while (!nextNode && walker.length > 1)
510                     {
511                         walker.shift();
512                         nextNode = walker[0].nextNode();
513                     }
514                     currentNode = nextNode;
515                     attrIndex = 0;
516                 }
517             }
518 
519             if (!currentNode)
520                 pastEnd = true;
521             else
522                 pastStart = false;
523 
524             return this.currentNode();
525         };
526 
527         /**
528          * Retrieves the current node.
529          *
530          * @return The current node, if not past the beginning or end of the iteration.
531          */
532         this.currentNode = function()
533         {
534             return !attrIndex ? currentNode : currentNode.attributes[attrIndex-1];
535         };
536 
537         /**
538          * Resets the walker position back to the initial position.
539          */
540         this.reset = function()
541         {
542             pastStart = false;
543             pastEnd = false;
544             walker = [];
545             currentNode = undefined;
546             attrIndex = 0;
547 
548             createWalker(root);
549         };
550 
551         this.reset();
552     },
553 
554     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
555     // Node/Element Utilities
556 
557     /**
558      * Determines if the given element is the source for a non-DOM resource such
559      * as Javascript source or CSS definition.
560      *
561      * @param {Element} element Element to test
562      * @return true if the element is a source element
563      */
564     isSourceElement: function(element)
565     {
566         if (!Xml.isElementHTML(element) && !Xml.isElementXHTML(element))
567             return false;
568 
569         var tag = element.localName ? element.localName.toLowerCase() : "";
570         return tag == "script" || (tag == "link" && element.getAttribute("rel") == "stylesheet") ||
571             tag == "style";
572     },
573 
574     /**
575      * Retrieves the source URL for any external resource associated with a node.
576      *
577      * @param {Element} element Element to examine
578      * @return URL of the external resouce.
579      */
580     getSourceHref: function(element)
581     {
582         var tag = element.localName.toLowerCase();
583         if (tag == "script" && element.src)
584             return element.src;
585         else if (tag == "link")
586             return element.href;
587         else
588             return null;
589     },
590 
591     /**
592      * Retrieves the source text for inline script and style elements.
593      *
594      * @param {Element} element Script or style element
595      * @return Source text
596      */
597     getSourceText: function(element)
598     {
599         var tag = element.localName.toLowerCase();
600         if (tag == "script" && !element.src)
601             return element.textContent;
602         else if (tag == "style")
603             return element.textContent;
604         else
605             return null;
606     },
607 
608     /**
609      * Determines if the given element is a container element.
610      *
611      * @param {Element} element Element to test
612      * @return True if the element is a container element.
613      */
614     isContainerElement: function(element)
615     {
616         var tag = element.localName.toLowerCase();
617         switch (tag)
618         {
619             case "script":
620             case "style":
621             case "iframe":
622             case "frame":
623             case "tabbrowser":
624             case "browser":
625                 return true;
626             case "link":
627                 return element.getAttribute("rel") == "stylesheet";
628             case "embed":
629                 return element.getSVGDocument();
630         }
631         return false;
632     },
633 
634     /**
635      * Determines if the given node has any children which are elements.
636      *
637      * @param {Element} element Element to test.
638      * @return true if immediate children of type Element exist, false otherwise
639      */
640     hasNoElementChildren: function(element)
641     {
642         if (element.childElementCount != 0)  // FF 3.5+
643             return false;
644 
645         // https://developer.mozilla.org/en/XBL/XBL_1.0_Reference/DOM_Interfaces
646         if (element.ownerDocument instanceof Ci.nsIDOMDocumentXBL)
647         {
648             if (FBTrace.DBG_HTML)
649             {
650                 FBTrace.sysout("hasNoElementChildren "+Css.getElementCSSSelector(element)+
651                     " (element.ownerDocument instanceof Ci.nsIDOMDocumentXBL) "+
652                     (element.ownerDocument instanceof Ci.nsIDOMDocumentXBL), element);
653             }
654 
655             var walker = new HTMLLib.ElementWalker();
656             var child = walker.getFirstChild(element);
657 
658             while (child)
659             {
660                 if (child.nodeType === Node.ELEMENT_NODE)
661                     return false;
662                 child = walker.getNextSibling(child);
663             }
664         }
665 
666         if (FBTrace.DBG_HTML)
667             FBTrace.sysout("hasNoElementChildren TRUE "+element.tagName+
668                 " (element.ownerDocument instanceof Ci.nsIDOMDocumentXBL) "+
669                 (element.ownerDocument instanceof Ci.nsIDOMDocumentXBL), element);
670 
671         return true;
672     },
673 
674 
675     /**
676      * Determines if the given node has any children which are comments.
677      *
678      * @param {Element} element Element to test.
679      * @return true if immediate children of type Comment exist, false otherwise
680      */
681     hasCommentChildren: function(element)
682     {
683         if (element.hasChildNodes())
684         {
685             var children = element.childNodes;
686             for (var i = 0; i < children.length; i++)
687             {
688                 if (children[i] instanceof Comment)
689                    return true;
690             }
691         };
692         return false;
693     },
694 
695 
696     /**
697      * Determines if the given node consists solely of whitespace text.
698      *
699      * @param {Node} node Node to test.
700      * @return true if the node is a whitespace text node
701      */
702     isWhitespaceText: function(node)
703     {
704         if (node instanceof window.HTMLAppletElement)
705             return false;
706 
707         return node.nodeType == window.Node.TEXT_NODE && Str.isWhitespace(node.nodeValue);
708     },
709 
710     /**
711      * Determines if a given element is empty. When the
712      * {@link Firebug#showTextNodesWithWhitespace} parameter is true, an element is
713      * considered empty if it has no child elements and is self closing. When
714      * false, an element is considered empty if the only children are whitespace
715      * nodes.
716      *
717      * @param {Element} element Element to test
718      * @return true if the element is empty, false otherwise
719      */
720     isEmptyElement: function(element)
721     {
722         // XXXjjb the commented code causes issues 48, 240, and 244. I think the lines should be deleted.
723         // If the DOM has whitespace children, then the element is not empty even if
724         // we decide not to show the whitespace in the UI.
725 
726         // XXXsroussey reverted above but added a check for self closing tags
727         if (Firebug.showTextNodesWithWhitespace)
728         {
729             return !element.firstChild && Xml.isSelfClosing(element);
730         }
731         else
732         {
733             for (var child = element.firstChild; child; child = child.nextSibling)
734             {
735                 if (!HTMLLib.isWhitespaceText(child))
736                     return false;
737             }
738         }
739         return Xml.isSelfClosing(element);
740     },
741 
742     /**
743      * Finds the next sibling of the given node. If the
744      * {@link Firebug#showTextNodesWithWhitespace} parameter is set to true, the next
745      * sibling may be a whitespace, otherwise the next is the first adjacent
746      * non-whitespace node.
747      *
748      * @param {Node} node Node to analyze.
749      * @return Next sibling node, if one exists
750      */
751     findNextSibling: function(node)
752     {
753         if (Firebug.showTextNodesWithWhitespace)
754             return node.nextSibling;
755         else
756         {
757             // only return a non-whitespace node
758             for (var child = node.nextSibling; child; child = child.nextSibling)
759             {
760                 if (!HTMLLib.isWhitespaceText(child))
761                     return child;
762             }
763         }
764     },
765 
766     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
767     // Domplate Utilities
768 
769     /**
770      * Locates the attribute domplate node for a given element domplate. This method will
771      * only examine notes marked with the "nodeAttr" class that are the direct
772      * children of the given element.
773      *
774      * @param {Object} objectNodeBox The domplate element to look up the attribute for.
775      * @param {String} attrName Attribute name
776      * @return Attribute's domplate node
777      */
778     findNodeAttrBox: function(objectNodeBox, attrName)
779     {
780         var child = objectNodeBox.firstChild.lastChild.firstChild;
781         for (; child; child = child.nextSibling)
782         {
783             if (Css.hasClass(child, "nodeAttr") && child.childNodes[1].firstChild
784                 && child.childNodes[1].firstChild.nodeValue == attrName)
785             {
786                 return child;
787             }
788         }
789     },
790 
791     /**
792      * Locates the text domplate node for a given text element domplate.
793      * @param {Object} nodeBox Text element domplate
794      * @return Element's domplate text node
795      */
796     getTextElementTextBox: function(nodeBox)
797     {
798         var nodeLabelBox = nodeBox.firstChild.lastChild;
799         return Dom.getChildByClass(nodeLabelBox, "nodeText");
800     },
801 
802     // These functions can be copied to add tree walking feature, they allow Chromebug
803     // to reuse the HTML panel
804     ElementWalkerFunctions:
805     {
806         getTreeWalker: function(node)
807         {
808             if (!this.treeWalker || this.treeWalker.currentNode !== node)
809                 this.treeWalker = node.ownerDocument.createTreeWalker(
810                     node, NodeFilter.SHOW_ALL, null, false);
811 
812             return this.treeWalker;
813         },
814 
815         getFirstChild: function(node)
816         {
817             return node.firstChild;
818         },
819 
820         getNextSibling: function(node)
821         {
822             // the Mozilla XBL tree walker fails for nextSibling
823             return node.nextSibling;
824         },
825 
826         getParentNode: function(node)
827         {
828             // the Mozilla XBL tree walker fails for parentNode
829             return node.parentNode;
830         }
831     },
832 
833     ElementWalker: function()  // tree walking via new ElementWalker
834     {
835 
836     }
837 };
838 
839 // ********************************************************************************************* //
840 // Registration
841 
842 HTMLLib.ElementWalker.prototype = HTMLLib.ElementWalkerFunctions;
843 
844 return HTMLLib;
845 
846 // ********************************************************************************************* //
847 });