browser/devtools/framework/selection.js

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

mercurial