DOMHelpers.jsm 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. const Ci = Components.interfaces;
  6. const Cu = Components.utils;
  7. const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
  8. const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
  9. const nodeFilterConstants = require("devtools/shared/dom-node-filter-constants");
  10. this.EXPORTED_SYMBOLS = ["DOMHelpers"];
  11. /**
  12. * DOMHelpers
  13. * Makes DOM traversal easier. Goes through iframes.
  14. *
  15. * @constructor
  16. * @param nsIDOMWindow aWindow
  17. * The content window, owning the document to traverse.
  18. */
  19. this.DOMHelpers = function DOMHelpers(aWindow) {
  20. if (!aWindow) {
  21. throw new Error("window can't be null or undefined");
  22. }
  23. this.window = aWindow;
  24. };
  25. DOMHelpers.prototype = {
  26. getParentObject: function Helpers_getParentObject(node)
  27. {
  28. let parentNode = node ? node.parentNode : null;
  29. if (!parentNode) {
  30. // Documents have no parentNode; Attr, Document, DocumentFragment, Entity,
  31. // and Notation. top level windows have no parentNode
  32. if (node && node == this.window.Node.DOCUMENT_NODE) {
  33. // document type
  34. if (node.defaultView) {
  35. let embeddingFrame = node.defaultView.frameElement;
  36. if (embeddingFrame)
  37. return embeddingFrame.parentNode;
  38. }
  39. }
  40. // a Document object without a parentNode or window
  41. return null; // top level has no parent
  42. }
  43. if (parentNode.nodeType == this.window.Node.DOCUMENT_NODE) {
  44. if (parentNode.defaultView) {
  45. return parentNode.defaultView.frameElement;
  46. }
  47. // parent is document element, but no window at defaultView.
  48. return null;
  49. }
  50. if (!parentNode.localName)
  51. return null;
  52. return parentNode;
  53. },
  54. getChildObject: function Helpers_getChildObject(node, index, previousSibling,
  55. showTextNodesWithWhitespace)
  56. {
  57. if (!node)
  58. return null;
  59. if (node.contentDocument) {
  60. // then the node is a frame
  61. if (index == 0) {
  62. return node.contentDocument.documentElement; // the node's HTMLElement
  63. }
  64. return null;
  65. }
  66. if (node.getSVGDocument) {
  67. let svgDocument = node.getSVGDocument();
  68. if (svgDocument) {
  69. // then the node is a frame
  70. if (index == 0) {
  71. return svgDocument.documentElement; // the node's SVGElement
  72. }
  73. return null;
  74. }
  75. }
  76. let child = null;
  77. if (previousSibling) // then we are walking
  78. child = this.getNextSibling(previousSibling);
  79. else
  80. child = this.getFirstChild(node);
  81. if (showTextNodesWithWhitespace)
  82. return child;
  83. for (; child; child = this.getNextSibling(child)) {
  84. if (!this.isWhitespaceText(child))
  85. return child;
  86. }
  87. return null; // we have no children worth showing.
  88. },
  89. getFirstChild: function Helpers_getFirstChild(node)
  90. {
  91. let SHOW_ALL = nodeFilterConstants.SHOW_ALL;
  92. this.treeWalker = node.ownerDocument.createTreeWalker(node,
  93. SHOW_ALL, null);
  94. return this.treeWalker.firstChild();
  95. },
  96. getNextSibling: function Helpers_getNextSibling(node)
  97. {
  98. let next = this.treeWalker.nextSibling();
  99. if (!next)
  100. delete this.treeWalker;
  101. return next;
  102. },
  103. isWhitespaceText: function Helpers_isWhitespaceText(node)
  104. {
  105. return node.nodeType == this.window.Node.TEXT_NODE &&
  106. !/[^\s]/.exec(node.nodeValue);
  107. },
  108. destroy: function Helpers_destroy()
  109. {
  110. delete this.window;
  111. delete this.treeWalker;
  112. },
  113. /**
  114. * A simple way to be notified (once) when a window becomes
  115. * interactive (DOMContentLoaded).
  116. *
  117. * It is based on the chromeEventHandler. This is useful when
  118. * chrome iframes are loaded in content docshells (in Firefox
  119. * tabs for example).
  120. */
  121. onceDOMReady: function Helpers_onLocationChange(callback, targetURL) {
  122. let window = this.window;
  123. let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
  124. .getInterface(Ci.nsIWebNavigation)
  125. .QueryInterface(Ci.nsIDocShell);
  126. let onReady = function (event) {
  127. if (event.target == window.document) {
  128. docShell.chromeEventHandler.removeEventListener("DOMContentLoaded", onReady, false);
  129. // If in `callback` the URL of the window is changed and a listener to DOMContentLoaded
  130. // is attached, the event we just received will be also be caught by the new listener.
  131. // We want to avoid that so we execute the callback in the next queue.
  132. Services.tm.mainThread.dispatch(callback, 0);
  133. }
  134. };
  135. if ((window.document.readyState == "complete" ||
  136. window.document.readyState == "interactive") &&
  137. window.location.href == targetURL) {
  138. Services.tm.mainThread.dispatch(callback, 0);
  139. } else {
  140. docShell.chromeEventHandler.addEventListener("DOMContentLoaded", onReady, false);
  141. }
  142. }
  143. };