browser/devtools/framework/selection.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 "use strict";
michael@0 8
michael@0 9 const {Cu, Ci} = require("chrome");
michael@0 10 let EventEmitter = require("devtools/toolkit/event-emitter");
michael@0 11
michael@0 12 /**
michael@0 13 * API
michael@0 14 *
michael@0 15 * new Selection(walker=null, node=null, track={attributes,detached});
michael@0 16 * destroy()
michael@0 17 * node (readonly)
michael@0 18 * setNode(node, origin="unknown")
michael@0 19 *
michael@0 20 * Helpers:
michael@0 21 *
michael@0 22 * window
michael@0 23 * document
michael@0 24 * isRoot()
michael@0 25 * isNode()
michael@0 26 * isHTMLNode()
michael@0 27 *
michael@0 28 * Check the nature of the node:
michael@0 29 *
michael@0 30 * isElementNode()
michael@0 31 * isAttributeNode()
michael@0 32 * isTextNode()
michael@0 33 * isCDATANode()
michael@0 34 * isEntityRefNode()
michael@0 35 * isEntityNode()
michael@0 36 * isProcessingInstructionNode()
michael@0 37 * isCommentNode()
michael@0 38 * isDocumentNode()
michael@0 39 * isDocumentTypeNode()
michael@0 40 * isDocumentFragmentNode()
michael@0 41 * isNotationNode()
michael@0 42 *
michael@0 43 * Events:
michael@0 44 * "new-node" when the inner node changed
michael@0 45 * "before-new-node" when the inner node is set to change
michael@0 46 * "attribute-changed" when an attribute is changed (only if tracked)
michael@0 47 * "detached" when the node (or one of its parents) is removed from the document (only if tracked)
michael@0 48 * "reparented" when the node (or one of its parents) is moved under a different node (only if tracked)
michael@0 49 */
michael@0 50
michael@0 51 /**
michael@0 52 * A Selection object. Hold a reference to a node.
michael@0 53 * Includes some helpers, fire some helpful events.
michael@0 54 *
michael@0 55 * @param node Inner node.
michael@0 56 * Can be null. Can be (un)set in the future via the "node" property;
michael@0 57 * @param trackAttribute Tell if events should be fired when the attributes of
michael@0 58 * the node change.
michael@0 59 *
michael@0 60 */
michael@0 61 function Selection(walker, node=null, track={attributes:true,detached:true}) {
michael@0 62 EventEmitter.decorate(this);
michael@0 63
michael@0 64 this._onMutations = this._onMutations.bind(this);
michael@0 65 this.track = track;
michael@0 66 this.setWalker(walker);
michael@0 67 this.setNode(node);
michael@0 68 }
michael@0 69
michael@0 70 exports.Selection = Selection;
michael@0 71
michael@0 72 Selection.prototype = {
michael@0 73 _walker: null,
michael@0 74 _node: null,
michael@0 75
michael@0 76 _onMutations: function(mutations) {
michael@0 77 let attributeChange = false;
michael@0 78 let pseudoChange = false;
michael@0 79 let detached = false;
michael@0 80 let parentNode = null;
michael@0 81
michael@0 82 for (let m of mutations) {
michael@0 83 if (!attributeChange && m.type == "attributes") {
michael@0 84 attributeChange = true;
michael@0 85 }
michael@0 86 if (m.type == "childList") {
michael@0 87 if (!detached && !this.isConnected()) {
michael@0 88 if (this.isNode()) {
michael@0 89 parentNode = m.target;
michael@0 90 }
michael@0 91 detached = true;
michael@0 92 }
michael@0 93 }
michael@0 94 if (m.type == "pseudoClassLock") {
michael@0 95 pseudoChange = true;
michael@0 96 }
michael@0 97 }
michael@0 98
michael@0 99 // Fire our events depending on what changed in the mutations array
michael@0 100 if (attributeChange) {
michael@0 101 this.emit("attribute-changed");
michael@0 102 }
michael@0 103 if (pseudoChange) {
michael@0 104 this.emit("pseudoclass");
michael@0 105 }
michael@0 106 if (detached) {
michael@0 107 let rawNode = null;
michael@0 108 if (parentNode && parentNode.isLocal_toBeDeprecated()) {
michael@0 109 rawNode = parentNode.rawNode();
michael@0 110 }
michael@0 111
michael@0 112 this.emit("detached", rawNode, null);
michael@0 113 this.emit("detached-front", parentNode);
michael@0 114 }
michael@0 115 },
michael@0 116
michael@0 117 destroy: function() {
michael@0 118 this.setNode(null);
michael@0 119 this.setWalker(null);
michael@0 120 },
michael@0 121
michael@0 122 setWalker: function(walker) {
michael@0 123 if (this._walker) {
michael@0 124 this._walker.off("mutations", this._onMutations);
michael@0 125 }
michael@0 126 this._walker = walker;
michael@0 127 if (this._walker) {
michael@0 128 this._walker.on("mutations", this._onMutations);
michael@0 129 }
michael@0 130 },
michael@0 131
michael@0 132 // Not remote-safe
michael@0 133 setNode: function(value, reason="unknown") {
michael@0 134 if (value) {
michael@0 135 value = this._walker.frontForRawNode(value);
michael@0 136 }
michael@0 137 this.setNodeFront(value, reason);
michael@0 138 },
michael@0 139
michael@0 140 // Not remote-safe
michael@0 141 get node() {
michael@0 142 return this._node;
michael@0 143 },
michael@0 144
michael@0 145 // Not remote-safe
michael@0 146 get window() {
michael@0 147 if (this.isNode()) {
michael@0 148 return this.node.ownerDocument.defaultView;
michael@0 149 }
michael@0 150 return null;
michael@0 151 },
michael@0 152
michael@0 153 // Not remote-safe
michael@0 154 get document() {
michael@0 155 if (this.isNode()) {
michael@0 156 return this.node.ownerDocument;
michael@0 157 }
michael@0 158 return null;
michael@0 159 },
michael@0 160
michael@0 161 setNodeFront: function(value, reason="unknown") {
michael@0 162 this.reason = reason;
michael@0 163
michael@0 164 // We used to return here if the node had not changed but we now need to
michael@0 165 // set the node even if it is already set otherwise it is not possible to
michael@0 166 // e.g. highlight the same node twice.
michael@0 167 let rawValue = null;
michael@0 168 if (value && value.isLocal_toBeDeprecated()) {
michael@0 169 rawValue = value.rawNode();
michael@0 170 }
michael@0 171 this.emit("before-new-node", rawValue, reason);
michael@0 172 this.emit("before-new-node-front", value, reason);
michael@0 173 let previousNode = this._node;
michael@0 174 let previousFront = this._nodeFront;
michael@0 175 this._node = rawValue;
michael@0 176 this._nodeFront = value;
michael@0 177 this.emit("new-node", previousNode, this.reason);
michael@0 178 this.emit("new-node-front", value, this.reason);
michael@0 179 },
michael@0 180
michael@0 181 get documentFront() {
michael@0 182 return this._walker.document(this._nodeFront);
michael@0 183 },
michael@0 184
michael@0 185 get nodeFront() {
michael@0 186 return this._nodeFront;
michael@0 187 },
michael@0 188
michael@0 189 isRoot: function() {
michael@0 190 return this.isNode() &&
michael@0 191 this.isConnected() &&
michael@0 192 this._nodeFront.isDocumentElement;
michael@0 193 },
michael@0 194
michael@0 195 isNode: function() {
michael@0 196 if (!this._nodeFront) {
michael@0 197 return false;
michael@0 198 }
michael@0 199
michael@0 200 // As long as tools are still accessing node.rawNode(),
michael@0 201 // this needs to stay here.
michael@0 202 if (this._node && Cu.isDeadWrapper(this._node)) {
michael@0 203 return false;
michael@0 204 }
michael@0 205
michael@0 206 return true;
michael@0 207 },
michael@0 208
michael@0 209 isLocal: function() {
michael@0 210 return !!this._node;
michael@0 211 },
michael@0 212
michael@0 213 isConnected: function() {
michael@0 214 let node = this._nodeFront;
michael@0 215 if (!node || !node.actorID) {
michael@0 216 return false;
michael@0 217 }
michael@0 218
michael@0 219 // As long as there are still tools going around
michael@0 220 // accessing node.rawNode, this needs to stay.
michael@0 221 let rawNode = null;
michael@0 222 if (node.isLocal_toBeDeprecated()) {
michael@0 223 rawNode = node.rawNode();
michael@0 224 }
michael@0 225 if (rawNode) {
michael@0 226 try {
michael@0 227 let doc = this.document;
michael@0 228 return (doc && doc.defaultView && doc.documentElement.contains(rawNode));
michael@0 229 } catch (e) {
michael@0 230 // "can't access dead object" error
michael@0 231 return false;
michael@0 232 }
michael@0 233 }
michael@0 234
michael@0 235 while(node) {
michael@0 236 if (node === this._walker.rootNode) {
michael@0 237 return true;
michael@0 238 }
michael@0 239 node = node.parentNode();
michael@0 240 };
michael@0 241 return false;
michael@0 242 },
michael@0 243
michael@0 244 isHTMLNode: function() {
michael@0 245 let xhtml_ns = "http://www.w3.org/1999/xhtml";
michael@0 246 return this.isNode() && this.node.namespaceURI == xhtml_ns;
michael@0 247 },
michael@0 248
michael@0 249 // Node type
michael@0 250
michael@0 251 isElementNode: function() {
michael@0 252 return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ELEMENT_NODE;
michael@0 253 },
michael@0 254
michael@0 255 isAttributeNode: function() {
michael@0 256 return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ATTRIBUTE_NODE;
michael@0 257 },
michael@0 258
michael@0 259 isTextNode: function() {
michael@0 260 return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.TEXT_NODE;
michael@0 261 },
michael@0 262
michael@0 263 isCDATANode: function() {
michael@0 264 return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.CDATA_SECTION_NODE;
michael@0 265 },
michael@0 266
michael@0 267 isEntityRefNode: function() {
michael@0 268 return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ENTITY_REFERENCE_NODE;
michael@0 269 },
michael@0 270
michael@0 271 isEntityNode: function() {
michael@0 272 return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ENTITY_NODE;
michael@0 273 },
michael@0 274
michael@0 275 isProcessingInstructionNode: function() {
michael@0 276 return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.PROCESSING_INSTRUCTION_NODE;
michael@0 277 },
michael@0 278
michael@0 279 isCommentNode: function() {
michael@0 280 return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.PROCESSING_INSTRUCTION_NODE;
michael@0 281 },
michael@0 282
michael@0 283 isDocumentNode: function() {
michael@0 284 return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE;
michael@0 285 },
michael@0 286
michael@0 287 isDocumentTypeNode: function() {
michael@0 288 return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE;
michael@0 289 },
michael@0 290
michael@0 291 isDocumentFragmentNode: function() {
michael@0 292 return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_FRAGMENT_NODE;
michael@0 293 },
michael@0 294
michael@0 295 isNotationNode: function() {
michael@0 296 return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.NOTATION_NODE;
michael@0 297 },
michael@0 298 };

mercurial