1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/shared/DOMHelpers.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,156 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +const Ci = Components.interfaces; 1.9 +const Cu = Components.utils; 1.10 +Cu.import("resource://gre/modules/Services.jsm"); 1.11 + 1.12 +this.EXPORTED_SYMBOLS = ["DOMHelpers"]; 1.13 + 1.14 +/** 1.15 + * DOMHelpers 1.16 + * Makes DOM traversal easier. Goes through iframes. 1.17 + * 1.18 + * @constructor 1.19 + * @param nsIDOMWindow aWindow 1.20 + * The content window, owning the document to traverse. 1.21 + */ 1.22 +this.DOMHelpers = function DOMHelpers(aWindow) { 1.23 + if (!aWindow) { 1.24 + throw new Error("window can't be null or undefined"); 1.25 + } 1.26 + this.window = aWindow; 1.27 +}; 1.28 + 1.29 +DOMHelpers.prototype = { 1.30 + getParentObject: function Helpers_getParentObject(node) 1.31 + { 1.32 + let parentNode = node ? node.parentNode : null; 1.33 + 1.34 + if (!parentNode) { 1.35 + // Documents have no parentNode; Attr, Document, DocumentFragment, Entity, 1.36 + // and Notation. top level windows have no parentNode 1.37 + if (node && node == this.window.Node.DOCUMENT_NODE) { 1.38 + // document type 1.39 + if (node.defaultView) { 1.40 + let embeddingFrame = node.defaultView.frameElement; 1.41 + if (embeddingFrame) 1.42 + return embeddingFrame.parentNode; 1.43 + } 1.44 + } 1.45 + // a Document object without a parentNode or window 1.46 + return null; // top level has no parent 1.47 + } 1.48 + 1.49 + if (parentNode.nodeType == this.window.Node.DOCUMENT_NODE) { 1.50 + if (parentNode.defaultView) { 1.51 + return parentNode.defaultView.frameElement; 1.52 + } 1.53 + // parent is document element, but no window at defaultView. 1.54 + return null; 1.55 + } 1.56 + 1.57 + if (!parentNode.localName) 1.58 + return null; 1.59 + 1.60 + return parentNode; 1.61 + }, 1.62 + 1.63 + getChildObject: function Helpers_getChildObject(node, index, previousSibling, 1.64 + showTextNodesWithWhitespace) 1.65 + { 1.66 + if (!node) 1.67 + return null; 1.68 + 1.69 + if (node.contentDocument) { 1.70 + // then the node is a frame 1.71 + if (index == 0) { 1.72 + return node.contentDocument.documentElement; // the node's HTMLElement 1.73 + } 1.74 + return null; 1.75 + } 1.76 + 1.77 + if (node.getSVGDocument) { 1.78 + let svgDocument = node.getSVGDocument(); 1.79 + if (svgDocument) { 1.80 + // then the node is a frame 1.81 + if (index == 0) { 1.82 + return svgDocument.documentElement; // the node's SVGElement 1.83 + } 1.84 + return null; 1.85 + } 1.86 + } 1.87 + 1.88 + let child = null; 1.89 + if (previousSibling) // then we are walking 1.90 + child = this.getNextSibling(previousSibling); 1.91 + else 1.92 + child = this.getFirstChild(node); 1.93 + 1.94 + if (showTextNodesWithWhitespace) 1.95 + return child; 1.96 + 1.97 + for (; child; child = this.getNextSibling(child)) { 1.98 + if (!this.isWhitespaceText(child)) 1.99 + return child; 1.100 + } 1.101 + 1.102 + return null; // we have no children worth showing. 1.103 + }, 1.104 + 1.105 + getFirstChild: function Helpers_getFirstChild(node) 1.106 + { 1.107 + let SHOW_ALL = Components.interfaces.nsIDOMNodeFilter.SHOW_ALL; 1.108 + this.treeWalker = node.ownerDocument.createTreeWalker(node, 1.109 + SHOW_ALL, null); 1.110 + return this.treeWalker.firstChild(); 1.111 + }, 1.112 + 1.113 + getNextSibling: function Helpers_getNextSibling(node) 1.114 + { 1.115 + let next = this.treeWalker.nextSibling(); 1.116 + 1.117 + if (!next) 1.118 + delete this.treeWalker; 1.119 + 1.120 + return next; 1.121 + }, 1.122 + 1.123 + isWhitespaceText: function Helpers_isWhitespaceText(node) 1.124 + { 1.125 + return node.nodeType == this.window.Node.TEXT_NODE && 1.126 + !/[^\s]/.exec(node.nodeValue); 1.127 + }, 1.128 + 1.129 + destroy: function Helpers_destroy() 1.130 + { 1.131 + delete this.window; 1.132 + delete this.treeWalker; 1.133 + }, 1.134 + 1.135 + /** 1.136 + * A simple way to be notified (once) when a window becomes 1.137 + * interactive (DOMContentLoaded). 1.138 + * 1.139 + * It is based on the chromeEventHandler. This is useful when 1.140 + * chrome iframes are loaded in content docshells (in Firefox 1.141 + * tabs for example). 1.142 + */ 1.143 + onceDOMReady: function Helpers_onLocationChange(callback) { 1.144 + let window = this.window; 1.145 + let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor) 1.146 + .getInterface(Ci.nsIWebNavigation) 1.147 + .QueryInterface(Ci.nsIDocShell); 1.148 + let onReady = function(event) { 1.149 + if (event.target == window.document) { 1.150 + docShell.chromeEventHandler.removeEventListener("DOMContentLoaded", onReady, false); 1.151 + // If in `callback` the URL of the window is changed and a listener to DOMContentLoaded 1.152 + // is attached, the event we just received will be also be caught by the new listener. 1.153 + // We want to avoid that so we execute the callback in the next queue. 1.154 + Services.tm.mainThread.dispatch(callback, 0); 1.155 + } 1.156 + } 1.157 + docShell.chromeEventHandler.addEventListener("DOMContentLoaded", onReady, false); 1.158 + } 1.159 +};