2012-10-03 17:41:00 +00:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
2016-06-03 22:20:45 +00:00
|
|
|
"use strict";
|
|
|
|
|
2018-02-07 09:37:36 +00:00
|
|
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
|
|
|
|
const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
|
2016-07-11 18:28:00 +00:00
|
|
|
const nodeFilterConstants = require("devtools/shared/dom-node-filter-constants");
|
2013-11-16 02:47:00 +00:00
|
|
|
|
2012-10-31 16:13:28 +00:00
|
|
|
this.EXPORTED_SYMBOLS = ["DOMHelpers"];
|
2012-10-03 17:41:00 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* DOMHelpers
|
|
|
|
* Makes DOM traversal easier. Goes through iframes.
|
|
|
|
*
|
|
|
|
* @constructor
|
|
|
|
* @param nsIDOMWindow aWindow
|
|
|
|
* The content window, owning the document to traverse.
|
|
|
|
*/
|
2012-10-31 16:13:28 +00:00
|
|
|
this.DOMHelpers = function DOMHelpers(aWindow) {
|
2013-11-16 02:47:00 +00:00
|
|
|
if (!aWindow) {
|
|
|
|
throw new Error("window can't be null or undefined");
|
|
|
|
}
|
2012-10-03 17:41:00 +00:00
|
|
|
this.window = aWindow;
|
|
|
|
};
|
|
|
|
|
|
|
|
DOMHelpers.prototype = {
|
2018-04-09 09:44:03 +00:00
|
|
|
getParentObject: function Helpers_getParentObject(node) {
|
2018-06-01 10:36:09 +00:00
|
|
|
const parentNode = node ? node.parentNode : null;
|
2012-10-03 17:41:00 +00:00
|
|
|
|
|
|
|
if (!parentNode) {
|
|
|
|
// Documents have no parentNode; Attr, Document, DocumentFragment, Entity,
|
|
|
|
// and Notation. top level windows have no parentNode
|
|
|
|
if (node && node == this.window.Node.DOCUMENT_NODE) {
|
|
|
|
// document type
|
|
|
|
if (node.defaultView) {
|
2018-06-01 10:36:09 +00:00
|
|
|
const embeddingFrame = node.defaultView.frameElement;
|
2018-04-09 09:44:03 +00:00
|
|
|
if (embeddingFrame) {
|
2012-10-03 17:41:00 +00:00
|
|
|
return embeddingFrame.parentNode;
|
2018-04-09 09:44:03 +00:00
|
|
|
}
|
2012-10-03 17:41:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// a Document object without a parentNode or window
|
2018-04-09 09:44:03 +00:00
|
|
|
return null; // top level has no parent
|
2012-10-03 17:41:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (parentNode.nodeType == this.window.Node.DOCUMENT_NODE) {
|
|
|
|
if (parentNode.defaultView) {
|
|
|
|
return parentNode.defaultView.frameElement;
|
|
|
|
}
|
|
|
|
// parent is document element, but no window at defaultView.
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-04-09 09:44:03 +00:00
|
|
|
if (!parentNode.localName) {
|
2012-10-03 17:41:00 +00:00
|
|
|
return null;
|
2018-04-09 09:44:03 +00:00
|
|
|
}
|
2012-10-03 17:41:00 +00:00
|
|
|
|
|
|
|
return parentNode;
|
|
|
|
},
|
|
|
|
|
|
|
|
getChildObject: function Helpers_getChildObject(node, index, previousSibling,
|
2018-04-09 09:44:03 +00:00
|
|
|
showTextNodesWithWhitespace) {
|
|
|
|
if (!node) {
|
2012-10-03 17:41:00 +00:00
|
|
|
return null;
|
2018-04-09 09:44:03 +00:00
|
|
|
}
|
2012-10-03 17:41:00 +00:00
|
|
|
|
|
|
|
if (node.contentDocument) {
|
|
|
|
// then the node is a frame
|
|
|
|
if (index == 0) {
|
2018-04-09 09:44:03 +00:00
|
|
|
return node.contentDocument.documentElement; // the node's HTMLElement
|
2012-10-03 17:41:00 +00:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2013-06-12 04:56:19 +00:00
|
|
|
if (node.getSVGDocument) {
|
2018-06-01 10:36:09 +00:00
|
|
|
const svgDocument = node.getSVGDocument();
|
2012-10-03 17:41:00 +00:00
|
|
|
if (svgDocument) {
|
|
|
|
// then the node is a frame
|
|
|
|
if (index == 0) {
|
2018-04-09 09:44:03 +00:00
|
|
|
return svgDocument.documentElement; // the node's SVGElement
|
2012-10-03 17:41:00 +00:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let child = null;
|
2018-04-09 10:14:01 +00:00
|
|
|
if (previousSibling) {
|
|
|
|
// then we are walking
|
2012-10-03 17:41:00 +00:00
|
|
|
child = this.getNextSibling(previousSibling);
|
2018-04-09 09:44:03 +00:00
|
|
|
} else {
|
2012-10-03 17:41:00 +00:00
|
|
|
child = this.getFirstChild(node);
|
2018-04-09 09:44:03 +00:00
|
|
|
}
|
2012-10-03 17:41:00 +00:00
|
|
|
|
2018-04-09 09:44:03 +00:00
|
|
|
if (showTextNodesWithWhitespace) {
|
2012-10-03 17:41:00 +00:00
|
|
|
return child;
|
2018-04-09 09:44:03 +00:00
|
|
|
}
|
2012-10-03 17:41:00 +00:00
|
|
|
|
|
|
|
for (; child; child = this.getNextSibling(child)) {
|
2018-04-09 09:44:03 +00:00
|
|
|
if (!this.isWhitespaceText(child)) {
|
2012-10-03 17:41:00 +00:00
|
|
|
return child;
|
2018-04-09 09:44:03 +00:00
|
|
|
}
|
2012-10-03 17:41:00 +00:00
|
|
|
}
|
|
|
|
|
2018-04-09 09:44:03 +00:00
|
|
|
return null; // we have no children worth showing.
|
2012-10-03 17:41:00 +00:00
|
|
|
},
|
|
|
|
|
2018-04-09 09:44:03 +00:00
|
|
|
getFirstChild: function Helpers_getFirstChild(node) {
|
2018-06-01 10:36:09 +00:00
|
|
|
const SHOW_ALL = nodeFilterConstants.SHOW_ALL;
|
2012-10-03 17:41:00 +00:00
|
|
|
this.treeWalker = node.ownerDocument.createTreeWalker(node,
|
2013-02-06 14:22:33 +00:00
|
|
|
SHOW_ALL, null);
|
2012-10-03 17:41:00 +00:00
|
|
|
return this.treeWalker.firstChild();
|
|
|
|
},
|
|
|
|
|
2018-04-09 09:44:03 +00:00
|
|
|
getNextSibling: function Helpers_getNextSibling(node) {
|
2018-06-01 10:36:09 +00:00
|
|
|
const next = this.treeWalker.nextSibling();
|
2012-10-03 17:41:00 +00:00
|
|
|
|
2018-04-09 09:44:03 +00:00
|
|
|
if (!next) {
|
2012-10-03 17:41:00 +00:00
|
|
|
delete this.treeWalker;
|
2018-04-09 09:44:03 +00:00
|
|
|
}
|
2012-10-03 17:41:00 +00:00
|
|
|
|
|
|
|
return next;
|
|
|
|
},
|
|
|
|
|
2018-04-09 09:44:03 +00:00
|
|
|
isWhitespaceText: function Helpers_isWhitespaceText(node) {
|
2012-10-03 17:41:00 +00:00
|
|
|
return node.nodeType == this.window.Node.TEXT_NODE &&
|
|
|
|
!/[^\s]/.exec(node.nodeValue);
|
|
|
|
},
|
|
|
|
|
2018-04-09 09:44:03 +00:00
|
|
|
destroy: function Helpers_destroy() {
|
2012-10-03 17:41:00 +00:00
|
|
|
delete this.window;
|
|
|
|
delete this.treeWalker;
|
2013-11-16 02:47:00 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A simple way to be notified (once) when a window becomes
|
|
|
|
* interactive (DOMContentLoaded).
|
|
|
|
*
|
|
|
|
* It is based on the chromeEventHandler. This is useful when
|
|
|
|
* chrome iframes are loaded in content docshells (in Firefox
|
|
|
|
* tabs for example).
|
|
|
|
*/
|
2016-01-13 10:55:32 +00:00
|
|
|
onceDOMReady: function Helpers_onLocationChange(callback, targetURL) {
|
2018-06-01 10:36:09 +00:00
|
|
|
const window = this.window;
|
2018-08-01 17:07:10 +00:00
|
|
|
const docShell = window.docShell;
|
2018-06-01 10:36:09 +00:00
|
|
|
const onReady = function(event) {
|
2013-11-16 02:47:00 +00:00
|
|
|
if (event.target == window.document) {
|
2017-01-17 10:50:25 +00:00
|
|
|
docShell.chromeEventHandler.removeEventListener("DOMContentLoaded", onReady);
|
2013-11-16 02:47:00 +00:00
|
|
|
// If in `callback` the URL of the window is changed and a listener to DOMContentLoaded
|
|
|
|
// is attached, the event we just received will be also be caught by the new listener.
|
|
|
|
// We want to avoid that so we execute the callback in the next queue.
|
2017-04-14 16:29:12 +00:00
|
|
|
Services.tm.dispatchToMainThread(callback);
|
2013-11-16 02:47:00 +00:00
|
|
|
}
|
2016-05-17 18:25:54 +00:00
|
|
|
};
|
2016-01-13 10:55:32 +00:00
|
|
|
if ((window.document.readyState == "complete" ||
|
|
|
|
window.document.readyState == "interactive") &&
|
|
|
|
window.location.href == targetURL) {
|
2017-04-14 16:29:12 +00:00
|
|
|
Services.tm.dispatchToMainThread(callback);
|
2016-01-13 10:55:32 +00:00
|
|
|
} else {
|
2017-01-17 10:50:25 +00:00
|
|
|
docShell.chromeEventHandler.addEventListener("DOMContentLoaded", onReady);
|
2016-01-13 10:55:32 +00:00
|
|
|
}
|
2012-10-03 17:41:00 +00:00
|
|
|
}
|
|
|
|
};
|