michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: this.EXPORTED_SYMBOLS = ["DOMHelpers"]; michael@0: michael@0: /** michael@0: * DOMHelpers michael@0: * Makes DOM traversal easier. Goes through iframes. michael@0: * michael@0: * @constructor michael@0: * @param nsIDOMWindow aWindow michael@0: * The content window, owning the document to traverse. michael@0: */ michael@0: this.DOMHelpers = function DOMHelpers(aWindow) { michael@0: if (!aWindow) { michael@0: throw new Error("window can't be null or undefined"); michael@0: } michael@0: this.window = aWindow; michael@0: }; michael@0: michael@0: DOMHelpers.prototype = { michael@0: getParentObject: function Helpers_getParentObject(node) michael@0: { michael@0: let parentNode = node ? node.parentNode : null; michael@0: michael@0: if (!parentNode) { michael@0: // Documents have no parentNode; Attr, Document, DocumentFragment, Entity, michael@0: // and Notation. top level windows have no parentNode michael@0: if (node && node == this.window.Node.DOCUMENT_NODE) { michael@0: // document type michael@0: if (node.defaultView) { michael@0: let embeddingFrame = node.defaultView.frameElement; michael@0: if (embeddingFrame) michael@0: return embeddingFrame.parentNode; michael@0: } michael@0: } michael@0: // a Document object without a parentNode or window michael@0: return null; // top level has no parent michael@0: } michael@0: michael@0: if (parentNode.nodeType == this.window.Node.DOCUMENT_NODE) { michael@0: if (parentNode.defaultView) { michael@0: return parentNode.defaultView.frameElement; michael@0: } michael@0: // parent is document element, but no window at defaultView. michael@0: return null; michael@0: } michael@0: michael@0: if (!parentNode.localName) michael@0: return null; michael@0: michael@0: return parentNode; michael@0: }, michael@0: michael@0: getChildObject: function Helpers_getChildObject(node, index, previousSibling, michael@0: showTextNodesWithWhitespace) michael@0: { michael@0: if (!node) michael@0: return null; michael@0: michael@0: if (node.contentDocument) { michael@0: // then the node is a frame michael@0: if (index == 0) { michael@0: return node.contentDocument.documentElement; // the node's HTMLElement michael@0: } michael@0: return null; michael@0: } michael@0: michael@0: if (node.getSVGDocument) { michael@0: let svgDocument = node.getSVGDocument(); michael@0: if (svgDocument) { michael@0: // then the node is a frame michael@0: if (index == 0) { michael@0: return svgDocument.documentElement; // the node's SVGElement michael@0: } michael@0: return null; michael@0: } michael@0: } michael@0: michael@0: let child = null; michael@0: if (previousSibling) // then we are walking michael@0: child = this.getNextSibling(previousSibling); michael@0: else michael@0: child = this.getFirstChild(node); michael@0: michael@0: if (showTextNodesWithWhitespace) michael@0: return child; michael@0: michael@0: for (; child; child = this.getNextSibling(child)) { michael@0: if (!this.isWhitespaceText(child)) michael@0: return child; michael@0: } michael@0: michael@0: return null; // we have no children worth showing. michael@0: }, michael@0: michael@0: getFirstChild: function Helpers_getFirstChild(node) michael@0: { michael@0: let SHOW_ALL = Components.interfaces.nsIDOMNodeFilter.SHOW_ALL; michael@0: this.treeWalker = node.ownerDocument.createTreeWalker(node, michael@0: SHOW_ALL, null); michael@0: return this.treeWalker.firstChild(); michael@0: }, michael@0: michael@0: getNextSibling: function Helpers_getNextSibling(node) michael@0: { michael@0: let next = this.treeWalker.nextSibling(); michael@0: michael@0: if (!next) michael@0: delete this.treeWalker; michael@0: michael@0: return next; michael@0: }, michael@0: michael@0: isWhitespaceText: function Helpers_isWhitespaceText(node) michael@0: { michael@0: return node.nodeType == this.window.Node.TEXT_NODE && michael@0: !/[^\s]/.exec(node.nodeValue); michael@0: }, michael@0: michael@0: destroy: function Helpers_destroy() michael@0: { michael@0: delete this.window; michael@0: delete this.treeWalker; michael@0: }, michael@0: michael@0: /** michael@0: * A simple way to be notified (once) when a window becomes michael@0: * interactive (DOMContentLoaded). michael@0: * michael@0: * It is based on the chromeEventHandler. This is useful when michael@0: * chrome iframes are loaded in content docshells (in Firefox michael@0: * tabs for example). michael@0: */ michael@0: onceDOMReady: function Helpers_onLocationChange(callback) { michael@0: let window = this.window; michael@0: let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShell); michael@0: let onReady = function(event) { michael@0: if (event.target == window.document) { michael@0: docShell.chromeEventHandler.removeEventListener("DOMContentLoaded", onReady, false); michael@0: // If in `callback` the URL of the window is changed and a listener to DOMContentLoaded michael@0: // is attached, the event we just received will be also be caught by the new listener. michael@0: // We want to avoid that so we execute the callback in the next queue. michael@0: Services.tm.mainThread.dispatch(callback, 0); michael@0: } michael@0: } michael@0: docShell.chromeEventHandler.addEventListener("DOMContentLoaded", onReady, false); michael@0: } michael@0: };