selection.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. "use strict";
  6. const nodeConstants = require("devtools/shared/dom-node-constants");
  7. var EventEmitter = require("devtools/shared/event-emitter");
  8. /**
  9. * API
  10. *
  11. * new Selection(walker=null)
  12. * destroy()
  13. * node (readonly)
  14. * setNode(node, origin="unknown")
  15. *
  16. * Helpers:
  17. *
  18. * window
  19. * document
  20. * isRoot()
  21. * isNode()
  22. * isHTMLNode()
  23. *
  24. * Check the nature of the node:
  25. *
  26. * isElementNode()
  27. * isAttributeNode()
  28. * isTextNode()
  29. * isCDATANode()
  30. * isEntityRefNode()
  31. * isEntityNode()
  32. * isProcessingInstructionNode()
  33. * isCommentNode()
  34. * isDocumentNode()
  35. * isDocumentTypeNode()
  36. * isDocumentFragmentNode()
  37. * isNotationNode()
  38. *
  39. * Events:
  40. * "new-node-front" when the inner node changed
  41. * "attribute-changed" when an attribute is changed
  42. * "detached-front" when the node (or one of its parents) is removed from
  43. * the document
  44. * "reparented" when the node (or one of its parents) is moved under
  45. * a different node
  46. */
  47. /**
  48. * A Selection object. Hold a reference to a node.
  49. * Includes some helpers, fire some helpful events.
  50. */
  51. function Selection(walker) {
  52. EventEmitter.decorate(this);
  53. this._onMutations = this._onMutations.bind(this);
  54. this.setWalker(walker);
  55. }
  56. exports.Selection = Selection;
  57. Selection.prototype = {
  58. _walker: null,
  59. _onMutations: function (mutations) {
  60. let attributeChange = false;
  61. let pseudoChange = false;
  62. let detached = false;
  63. let parentNode = null;
  64. for (let m of mutations) {
  65. if (!attributeChange && m.type == "attributes") {
  66. attributeChange = true;
  67. }
  68. if (m.type == "childList") {
  69. if (!detached && !this.isConnected()) {
  70. if (this.isNode()) {
  71. parentNode = m.target;
  72. }
  73. detached = true;
  74. }
  75. }
  76. if (m.type == "pseudoClassLock") {
  77. pseudoChange = true;
  78. }
  79. }
  80. // Fire our events depending on what changed in the mutations array
  81. if (attributeChange) {
  82. this.emit("attribute-changed");
  83. }
  84. if (pseudoChange) {
  85. this.emit("pseudoclass");
  86. }
  87. if (detached) {
  88. this.emit("detached-front", parentNode);
  89. }
  90. },
  91. destroy: function () {
  92. this.setWalker(null);
  93. },
  94. setWalker: function (walker) {
  95. if (this._walker) {
  96. this._walker.off("mutations", this._onMutations);
  97. }
  98. this._walker = walker;
  99. if (this._walker) {
  100. this._walker.on("mutations", this._onMutations);
  101. }
  102. },
  103. setNodeFront: function (value, reason = "unknown") {
  104. this.reason = reason;
  105. // If an inlineTextChild text node is being set, then set it's parent instead.
  106. let parentNode = value && value.parentNode();
  107. if (value && parentNode && parentNode.inlineTextChild === value) {
  108. value = parentNode;
  109. }
  110. this._nodeFront = value;
  111. this.emit("new-node-front", value, this.reason);
  112. },
  113. get documentFront() {
  114. return this._walker.document(this._nodeFront);
  115. },
  116. get nodeFront() {
  117. return this._nodeFront;
  118. },
  119. isRoot: function () {
  120. return this.isNode() &&
  121. this.isConnected() &&
  122. this._nodeFront.isDocumentElement;
  123. },
  124. isNode: function () {
  125. return !!this._nodeFront;
  126. },
  127. isConnected: function () {
  128. let node = this._nodeFront;
  129. if (!node || !node.actorID) {
  130. return false;
  131. }
  132. while (node) {
  133. if (node === this._walker.rootNode) {
  134. return true;
  135. }
  136. node = node.parentNode();
  137. }
  138. return false;
  139. },
  140. isHTMLNode: function () {
  141. let xhtmlNs = "http://www.w3.org/1999/xhtml";
  142. return this.isNode() && this.nodeFront.namespaceURI == xhtmlNs;
  143. },
  144. // Node type
  145. isElementNode: function () {
  146. return this.isNode() && this.nodeFront.nodeType == nodeConstants.ELEMENT_NODE;
  147. },
  148. isPseudoElementNode: function () {
  149. return this.isNode() && this.nodeFront.isPseudoElement;
  150. },
  151. isAnonymousNode: function () {
  152. return this.isNode() && this.nodeFront.isAnonymous;
  153. },
  154. isAttributeNode: function () {
  155. return this.isNode() && this.nodeFront.nodeType == nodeConstants.ATTRIBUTE_NODE;
  156. },
  157. isTextNode: function () {
  158. return this.isNode() && this.nodeFront.nodeType == nodeConstants.TEXT_NODE;
  159. },
  160. isCDATANode: function () {
  161. return this.isNode() && this.nodeFront.nodeType == nodeConstants.CDATA_SECTION_NODE;
  162. },
  163. isEntityRefNode: function () {
  164. return this.isNode() &&
  165. this.nodeFront.nodeType == nodeConstants.ENTITY_REFERENCE_NODE;
  166. },
  167. isEntityNode: function () {
  168. return this.isNode() && this.nodeFront.nodeType == nodeConstants.ENTITY_NODE;
  169. },
  170. isProcessingInstructionNode: function () {
  171. return this.isNode() &&
  172. this.nodeFront.nodeType == nodeConstants.PROCESSING_INSTRUCTION_NODE;
  173. },
  174. isCommentNode: function () {
  175. return this.isNode() &&
  176. this.nodeFront.nodeType == nodeConstants.PROCESSING_INSTRUCTION_NODE;
  177. },
  178. isDocumentNode: function () {
  179. return this.isNode() && this.nodeFront.nodeType == nodeConstants.DOCUMENT_NODE;
  180. },
  181. /**
  182. * @returns true if the selection is the <body> HTML element.
  183. */
  184. isBodyNode: function () {
  185. return this.isHTMLNode() &&
  186. this.isConnected() &&
  187. this.nodeFront.nodeName === "BODY";
  188. },
  189. /**
  190. * @returns true if the selection is the <head> HTML element.
  191. */
  192. isHeadNode: function () {
  193. return this.isHTMLNode() &&
  194. this.isConnected() &&
  195. this.nodeFront.nodeName === "HEAD";
  196. },
  197. isDocumentTypeNode: function () {
  198. return this.isNode() && this.nodeFront.nodeType == nodeConstants.DOCUMENT_TYPE_NODE;
  199. },
  200. isDocumentFragmentNode: function () {
  201. return this.isNode() &&
  202. this.nodeFront.nodeType == nodeConstants.DOCUMENT_FRAGMENT_NODE;
  203. },
  204. isNotationNode: function () {
  205. return this.isNode() && this.nodeFront.nodeType == nodeConstants.NOTATION_NODE;
  206. },
  207. };