OLD | NEW |
| (Empty) |
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 /** | |
6 * @fileoverview Dom and DomNode are used to represent remote DOM in the | |
7 * web inspector. | |
8 */ | |
9 goog.provide('devtools.DomAgent'); | |
10 goog.provide('devtools.DomDocument'); | |
11 goog.provide('devtools.DomNode'); | |
12 | |
13 goog.require('devtools.Callback'); | |
14 | |
15 | |
16 /** | |
17 * Defines indexes for the node payload properties. | |
18 */ | |
19 devtools.PayloadIndex = { | |
20 ID : 0, | |
21 TYPE : 1, | |
22 NAME : 2, | |
23 VALUE : 3, | |
24 ATTRS : 4, | |
25 HAS_CHILDREN : 5, | |
26 CHILD_NODES : 6 | |
27 }; | |
28 | |
29 | |
30 /** | |
31 * Creates document node in a given document based on a given payload data. | |
32 * @param {devtools.Doc} doc Document to create node in. | |
33 * @param {Array.<Object>} payload Data to build node based upon. | |
34 * @constructor | |
35 */ | |
36 devtools.DomNode = function(doc, payload) { | |
37 this.ownerDocument = doc; | |
38 | |
39 this.id_ = payload[devtools.PayloadIndex.ID]; | |
40 this.nodeType = payload[devtools.PayloadIndex.TYPE]; | |
41 this.nodeName = payload[devtools.PayloadIndex.NAME]; | |
42 this.nodeValue_ = payload[devtools.PayloadIndex.VALUE]; | |
43 this.textContent = this.nodeValue; | |
44 | |
45 this.attributes = []; | |
46 this.attributesMap_ = {}; | |
47 if (payload.length > devtools.PayloadIndex.ATTRS) { | |
48 this.setAttributesPayload_(payload[devtools.PayloadIndex.ATTRS]); | |
49 } | |
50 | |
51 this.childNodesCount_ = payload[devtools.PayloadIndex.HAS_CHILDREN]; | |
52 this.children = null; | |
53 | |
54 this.nextSibling = null; | |
55 this.prevSibling = null; | |
56 this.firstChild = null; | |
57 this.parentNode = null; | |
58 | |
59 this.disabledStyleProperties_ = {}; | |
60 | |
61 if (payload.length > devtools.PayloadIndex.CHILD_NODES) { | |
62 // Has children payloads | |
63 this.setChildrenPayload_( | |
64 payload[devtools.PayloadIndex.CHILD_NODES]); | |
65 } | |
66 | |
67 this.computedStyle_ = null; | |
68 this.style = null; | |
69 this.matchedCSSRules_ = []; | |
70 }; | |
71 | |
72 | |
73 /** | |
74 * Overrides for getters and setters. | |
75 */ | |
76 devtools.DomNode.prototype = { | |
77 get nodeValue() { | |
78 return this.nodeValue_; | |
79 }, | |
80 | |
81 set nodeValue(value) { | |
82 if (this.nodeType != Node.TEXT_NODE) { | |
83 return; | |
84 } | |
85 var self = this; | |
86 this.ownerDocument.domAgent_.setTextNodeValueAsync(this, value, | |
87 function() { | |
88 self.nodeValue_ = value; | |
89 self.textContent = value; | |
90 }); | |
91 } | |
92 }; | |
93 | |
94 | |
95 /** | |
96 * Sets attributes for a given node based on a given attrs payload. | |
97 * @param {Array.<string>} attrs Attribute key-value pairs to set. | |
98 * @private | |
99 */ | |
100 devtools.DomNode.prototype.setAttributesPayload_ = function(attrs) { | |
101 for (var i = 0; i < attrs.length; i += 2) { | |
102 this.addAttribute_(attrs[i], attrs[i + 1]); | |
103 } | |
104 }; | |
105 | |
106 | |
107 /** | |
108 * @return True iff node has attributes. | |
109 */ | |
110 devtools.DomNode.prototype.hasAttributes = function() { | |
111 return this.attributes.length > 0; | |
112 }; | |
113 | |
114 | |
115 /** | |
116 * @return True iff node has child nodes. | |
117 */ | |
118 devtools.DomNode.prototype.hasChildNodes = function() { | |
119 return this.childNodesCount_ > 0; | |
120 }; | |
121 | |
122 | |
123 /** | |
124 * Inserts child node into this node after a given anchor. | |
125 * @param {devtools.DomNode} prev Node to insert child after. | |
126 * @param {Array.<Object>} payload Child node data. | |
127 * @private | |
128 */ | |
129 devtools.DomNode.prototype.insertChild_ = function(prev, payload) { | |
130 var node = new devtools.DomNode(this.ownerDocument, payload); | |
131 if (!prev) { | |
132 // First node | |
133 this.children = [ node ]; | |
134 } else { | |
135 this.children.splice(this.children.indexOf(prev) + 1, 0, node); | |
136 } | |
137 this.renumber_(); | |
138 return node; | |
139 }; | |
140 | |
141 | |
142 /** | |
143 * Removes child node from this node. | |
144 * @param {devtools.DomNode} node Node to remove. | |
145 * @private | |
146 */ | |
147 devtools.DomNode.prototype.removeChild_ = function(node) { | |
148 this.children.splice(this.children.indexOf(node), 1); | |
149 node.parentNode = null; | |
150 this.renumber_(); | |
151 }; | |
152 | |
153 | |
154 /** | |
155 * Sets children for this node based on the given payload. | |
156 * @param {Array.<Object>} payload Data for children. | |
157 * @private | |
158 */ | |
159 devtools.DomNode.prototype.setChildrenPayload_ = function(payloads) { | |
160 this.children = []; | |
161 for (var i = 0; i < payloads.length; ++i) { | |
162 var payload = payloads[i]; | |
163 var node = new devtools.DomNode(this.ownerDocument, payload); | |
164 this.children.push(node); | |
165 } | |
166 this.renumber_(); | |
167 }; | |
168 | |
169 | |
170 /** | |
171 * Normalizes prev/next/parent/firstChild links for this node's children. | |
172 * @private | |
173 */ | |
174 devtools.DomNode.prototype.renumber_ = function() { | |
175 this.childNodesCount_ = this.children.length; | |
176 if (this.childNodesCount_ == 0) { | |
177 this.firstChild = null; | |
178 return; | |
179 } | |
180 this.firstChild = this.children[0]; | |
181 for (var i = 0; i < this.childNodesCount_; ++i) { | |
182 var child = this.children[i]; | |
183 child.nextSibling = i + 1 < this.childNodesCount_ ? | |
184 this.children[i + 1] : null; | |
185 child.prevSibling = i - 1 >= 0 ? this.children[i - 1] : null; | |
186 child.parentNode = this; | |
187 } | |
188 }; | |
189 | |
190 | |
191 /** | |
192 * Returns attribute value. | |
193 * @param {string} name Attribute name to get value for. | |
194 * @return {string} Attribute value. | |
195 */ | |
196 devtools.DomNode.prototype.getAttribute = function(name) { | |
197 var attr = this.attributesMap_[name]; | |
198 return attr ? attr.value : undefined; | |
199 }; | |
200 | |
201 | |
202 /** | |
203 * Sends 'set attribute' command to the remote agent. | |
204 * @param {string} name Attribute name to set value for. | |
205 * @param {string} value Attribute value to set. | |
206 */ | |
207 devtools.DomNode.prototype.setAttribute = function(name, value) { | |
208 var self = this; | |
209 this.ownerDocument.domAgent_.setAttributeAsync(this, name, value, | |
210 function() { | |
211 var attr = self.attributesMap_[name]; | |
212 if (attr) { | |
213 attr.value = value; | |
214 } else { | |
215 attr = self.addAttribute_(name, value); | |
216 } | |
217 }); | |
218 }; | |
219 | |
220 | |
221 /** | |
222 * Creates an attribute-like object and adds it to the object. | |
223 * @param {string} name Attribute name to set value for. | |
224 * @param {string} value Attribute value to set. | |
225 */ | |
226 devtools.DomNode.prototype.addAttribute_ = function(name, value) { | |
227 var attr = { | |
228 'name': name, | |
229 'value': value, | |
230 node_: this | |
231 }; | |
232 | |
233 this.attributesMap_[name] = attr; | |
234 this.attributes.push(attr); | |
235 }; | |
236 | |
237 | |
238 /** | |
239 * Sends 'remove attribute' command to the remote agent. | |
240 * @param {string} name Attribute name to set value for. | |
241 */ | |
242 devtools.DomNode.prototype.removeAttribute = function(name) { | |
243 var self = this; | |
244 this.ownerDocument.domAgent_.removeAttributeAsync(this, name, function() { | |
245 delete self.attributesMap_[name]; | |
246 for (var i = 0; i < self.attributes.length; ++i) { | |
247 if (self.attributes[i].name == name) { | |
248 self.attributes.splice(i, 1); | |
249 break; | |
250 } | |
251 } | |
252 }); | |
253 }; | |
254 | |
255 | |
256 /** | |
257 * Makes available the following methods and properties: | |
258 * - node.style property | |
259 * - node.document.defaultView.getComputedStyles(node) | |
260 * - node.document.defaultView.getMatchedCSSRules(node, ...) | |
261 * - style attribute of node's attributes | |
262 * @param {string} computedStyle is a cssText of result of getComputedStyle(). | |
263 * @param {string} inlineStyle is a style.cssText (defined in the STYLE | |
264 * attribute). | |
265 * @param {Object} styleAttributes represents 'style' property | |
266 * of attributes. | |
267 * @param {Array.<object>} matchedCSSRules represents result of the | |
268 * getMatchedCSSRules(node, '', authorOnly). Each elemet consists of: | |
269 * selector, rule.style.cssText[, rule.parentStyleSheet.href | |
270 * [, rule.parentStyleSheet.ownerNode.nodeName]]. | |
271 */ | |
272 devtools.DomNode.prototype.setStyles = function(computedStyle, inlineStyle, | |
273 styleAttributes, matchedCSSRules) { | |
274 this.computedStyle_ = this.makeStyle_(computedStyle); | |
275 this.style = this.makeStyle_(inlineStyle); | |
276 | |
277 for (var name in styleAttributes) { | |
278 if (this.attributesMap_[name]) { | |
279 this.attributesMap_[name].style = | |
280 this.makeStyle_(styleAttributes[name]); | |
281 } | |
282 } | |
283 | |
284 this.matchedCSSRules_ = []; | |
285 for (var i = 0; i < matchedCSSRules.length; i++) { | |
286 var descr = matchedCSSRules[i]; | |
287 | |
288 var rule = {}; | |
289 rule.selectorText = descr['selector']; | |
290 rule.style = this.makeStyle_(descr['style']); | |
291 | |
292 if (descr['parentStyleSheet']) { | |
293 var parentStyleMock = {}; | |
294 parentStyleMock.href = descr['parentStyleSheet']['href']; | |
295 var nodeName = descr['parentStyleSheet']['ownerNodeName']; | |
296 if (nodeName) { | |
297 parentStyleMock.ownerNode = { | |
298 'nodeName': nodeName | |
299 }; | |
300 } | |
301 rule.parentStyleSheet = parentStyleMock; | |
302 } | |
303 this.matchedCSSRules_.push(rule); | |
304 } | |
305 }; | |
306 | |
307 | |
308 /** | |
309 * Creates a style declaration. | |
310 * @param {payload} payload | |
311 * @return {devtools.CSSStyleDeclaration:undefined} | |
312 * @see devtools.CSSStyleDeclaration | |
313 */ | |
314 devtools.DomNode.prototype.makeStyle_ = function(payload) { | |
315 var style = new devtools.CSSStyleDeclaration(payload); | |
316 style.nodeId_ = this.id_; | |
317 return style; | |
318 }; | |
319 | |
320 | |
321 /** | |
322 * Remove references to the style information to release | |
323 * resources when styles are not going to be used. | |
324 * @see setStyles. | |
325 */ | |
326 devtools.DomNode.prototype.clearStyles = function() { | |
327 this.computedStyle = null; | |
328 this.style = null; | |
329 for (var name in this.attributesMap_) { | |
330 this.attributesMap_[name].style = null; | |
331 } | |
332 this.matchedCSSRules_ = null; | |
333 }; | |
334 | |
335 | |
336 /** | |
337 * Remote Dom document abstraction. | |
338 * @param {devtools.DomAgent} domAgent owner agent. | |
339 * @param {devtools.DomWindow} defaultView owner window. | |
340 * @constructor. | |
341 */ | |
342 devtools.DomDocument = function(domAgent, defaultView) { | |
343 devtools.DomNode.call(this, null, | |
344 [ | |
345 0, // id | |
346 9, // type = Node.DOCUMENT_NODE, | |
347 '', // nodeName | |
348 '', // nodeValue | |
349 [], // attributes | |
350 0, // childNodeCount | |
351 ]); | |
352 this.listeners_ = {}; | |
353 this.domAgent_ = domAgent; | |
354 this.defaultView = defaultView; | |
355 }; | |
356 goog.inherits(devtools.DomDocument, devtools.DomNode); | |
357 | |
358 | |
359 /** | |
360 * Adds event listener to the Dom. | |
361 * @param {string} name Event name. | |
362 * @param {function(Event):undefined} callback Listener callback. | |
363 * @param {bool} useCapture Listener's useCapture settings. | |
364 */ | |
365 devtools.DomDocument.prototype.addEventListener = | |
366 function(name, callback, useCapture) { | |
367 var listeners = this.listeners_[name]; | |
368 if (!listeners) { | |
369 listeners = []; | |
370 this.listeners_[name] = listeners; | |
371 } | |
372 listeners.push(callback); | |
373 }; | |
374 | |
375 | |
376 /** | |
377 * Removes event listener from the Dom. | |
378 * @param {string} name Event name. | |
379 * @param {function(Event):undefined} callback Listener callback. | |
380 * @param {bool} useCapture Listener's useCapture settings. | |
381 */ | |
382 devtools.DomDocument.prototype.removeEventListener = | |
383 function(name, callback, useCapture) { | |
384 var listeners = this.listeners_[name]; | |
385 if (!listeners) { | |
386 return; | |
387 } | |
388 var index = listeners.indexOf(callback); | |
389 if (index != -1) { | |
390 listeners.splice(index, 1); | |
391 } | |
392 }; | |
393 | |
394 | |
395 /** | |
396 * Fires Dom event to the listeners for given event type. | |
397 * @param {string} name Event type. | |
398 * @param {Event} event Event to fire. | |
399 * @private | |
400 */ | |
401 devtools.DomDocument.prototype.fireDomEvent_ = function(name, event) { | |
402 var listeners = this.listeners_[name]; | |
403 if (!listeners) { | |
404 return; | |
405 } | |
406 for (var i = 0; i < listeners.length; ++i) { | |
407 listeners[i](event); | |
408 } | |
409 }; | |
410 | |
411 | |
412 | |
413 /** | |
414 * Simulation of inspected DOMWindow. | |
415 * @param {devtools.DomAgent} domAgent owner agent. | |
416 * @constructor | |
417 */ | |
418 devtools.DomWindow = function(domAgent) { | |
419 this.document = new devtools.DomDocument(domAgent, this); | |
420 }; | |
421 | |
422 /** | |
423 * Represents DOM Node class. | |
424 */ | |
425 devtools.DomWindow.prototype.__defineGetter__('Node', function() { | |
426 return devtools.DomNode; | |
427 }); | |
428 | |
429 /** | |
430 * Represents DOM Element class. | |
431 * @constructor | |
432 */ | |
433 devtools.DomWindow.prototype.__defineGetter__('Element', function() { | |
434 return devtools.DomNode; | |
435 }); | |
436 | |
437 | |
438 /** | |
439 * See usages in ScopeChainSidebarPane.js where it's called as | |
440 * constructor. | |
441 */ | |
442 devtools.DomWindow.prototype.Object = function() { | |
443 }; | |
444 | |
445 | |
446 /** | |
447 * Simulates the DOM interface for styles. | |
448 * @param {devtools.DomNode} node | |
449 * @return {CSSStyleDescription} | |
450 */ | |
451 devtools.DomWindow.prototype.getComputedStyle = function(node) { | |
452 return node.computedStyle_; | |
453 }; | |
454 | |
455 | |
456 /** | |
457 * Simulates the DOM interface for styles. | |
458 * @param {devtools.DomNode} nodeStyles | |
459 * @param {string} pseudoElement assumed to be empty string. | |
460 * @param {boolean} authorOnly assumed to be equal to authorOnly argument of | |
461 * getNodeStylesAsync. | |
462 * @return {CSSStyleDescription} | |
463 */ | |
464 devtools.DomWindow.prototype.getMatchedCSSRules = function(node, | |
465 pseudoElement, authorOnly) { | |
466 return node.matchedCSSRules_; | |
467 }; | |
468 | |
469 | |
470 /** | |
471 * Creates DomAgent Js representation. | |
472 * @constructor | |
473 */ | |
474 devtools.DomAgent = function() { | |
475 RemoteDomAgent.DidGetChildNodes = | |
476 devtools.Callback.processCallback; | |
477 RemoteDomAgent.DidPerformSearch = | |
478 devtools.Callback.processCallback; | |
479 RemoteDomAgent.DidApplyDomChange = | |
480 devtools.Callback.processCallback; | |
481 RemoteDomAgent.DidRemoveAttribute = | |
482 devtools.Callback.processCallback; | |
483 RemoteDomAgent.DidSetTextNodeValue = | |
484 devtools.Callback.processCallback; | |
485 RemoteDomAgent.AttributesUpdated = | |
486 goog.bind(this.attributesUpdated, this); | |
487 RemoteDomAgent.SetDocumentElement = | |
488 goog.bind(this.setDocumentElement, this); | |
489 RemoteDomAgent.SetChildNodes = | |
490 goog.bind(this.setChildNodes, this); | |
491 RemoteDomAgent.HasChildrenUpdated = | |
492 goog.bind(this.hasChildrenUpdated, this); | |
493 RemoteDomAgent.ChildNodeInserted = | |
494 goog.bind(this.childNodeInserted, this); | |
495 RemoteDomAgent.ChildNodeRemoved = | |
496 goog.bind(this.childNodeRemoved, this); | |
497 | |
498 /** | |
499 * Top-level (and the only) document. | |
500 * @type {devtools.DomWindow} | |
501 * @private | |
502 */ | |
503 this.window_ = null; | |
504 | |
505 /** | |
506 * Id to node mapping. | |
507 * @type {Object} | |
508 * @private | |
509 */ | |
510 this.idToDomNode_ = null; | |
511 | |
512 /** | |
513 * @type {Array.<number>} Node ids for search results. | |
514 * @private | |
515 */ | |
516 this.searchResults_ = null; | |
517 }; | |
518 | |
519 | |
520 /** | |
521 * Resets dom agent to its initial state. | |
522 */ | |
523 devtools.DomAgent.prototype.reset = function() { | |
524 this.window_ = new devtools.DomWindow(this); | |
525 this.idToDomNode_ = { 0 : this.getDocument() }; | |
526 this.searchResults_ = []; | |
527 }; | |
528 | |
529 | |
530 /** | |
531 * @return {devtools.DomWindow} Window for the top level (and the only) document
. | |
532 */ | |
533 devtools.DomAgent.prototype.getWindow = function() { | |
534 return this.window_; | |
535 }; | |
536 | |
537 | |
538 /** | |
539 * @return {devtools.DomDocument} A document of the top level window. | |
540 */ | |
541 devtools.DomAgent.prototype.getDocument = function() { | |
542 return this.window_.document; | |
543 }; | |
544 | |
545 | |
546 /** | |
547 * Requests that the document element is sent from the agent. | |
548 */ | |
549 devtools.DomAgent.prototype.getDocumentElementAsync = function() { | |
550 if (this.getDocument().documentElement) { | |
551 return; | |
552 } | |
553 RemoteDomAgent.GetDocumentElement(); | |
554 }; | |
555 | |
556 | |
557 /** | |
558 * Asynchronously fetches children from the element with given id. | |
559 * @param {devtools.DomNode} parent Element to get children for. | |
560 * @param {function(devtools.DomNode):undefined} opt_callback Callback with | |
561 * the result. | |
562 */ | |
563 devtools.DomAgent.prototype.getChildNodesAsync = function(parent, | |
564 opt_callback) { | |
565 var children = parent.children; | |
566 if (children && opt_callback) { | |
567 opt_callback(children); | |
568 return; | |
569 } | |
570 var mycallback = function() { | |
571 if (opt_callback) { | |
572 opt_callback(parent.children); | |
573 } | |
574 }; | |
575 var callId = devtools.Callback.wrap(mycallback); | |
576 RemoteDomAgent.GetChildNodes(callId, parent.id_); | |
577 }; | |
578 | |
579 | |
580 /** | |
581 * Sends 'set attribute' command to the remote agent. | |
582 * @param {devtools.DomNode} node Node to change. | |
583 * @param {string} name Attribute name to set value for. | |
584 * @param {string} value Attribute value to set. | |
585 * @param {function():undefined} opt_callback Callback on success. | |
586 */ | |
587 devtools.DomAgent.prototype.setAttributeAsync = function(node, name, value, | |
588 callback) { | |
589 var mycallback = goog.bind(this.didApplyDomChange_, this, node, callback); | |
590 RemoteDomAgent.SetAttribute(devtools.Callback.wrap(mycallback), | |
591 node.id_, name, value); | |
592 }; | |
593 | |
594 | |
595 /** | |
596 * Sends 'remove attribute' command to the remote agent. | |
597 * @param {devtools.DomNode} node Node to change. | |
598 * @param {string} name Attribute name to set value for. | |
599 * @param {function():undefined} opt_callback Callback on success. | |
600 */ | |
601 devtools.DomAgent.prototype.removeAttributeAsync = function(node, name, | |
602 callback) { | |
603 var mycallback = goog.bind(this.didApplyDomChange_, this, node, callback); | |
604 RemoteDomAgent.RemoveAttribute(devtools.Callback.wrap(mycallback), | |
605 node.id_, name); | |
606 }; | |
607 | |
608 | |
609 /** | |
610 * Sends 'set text node value' command to the remote agent. | |
611 * @param {devtools.DomNode} node Node to change. | |
612 * @param {string} text Text value to set. | |
613 * @param {function():undefined} opt_callback Callback on success. | |
614 */ | |
615 devtools.DomAgent.prototype.setTextNodeValueAsync = function(node, text, | |
616 callback) { | |
617 var mycallback = goog.bind(this.didApplyDomChange_, this, node, callback); | |
618 RemoteDomAgent.SetTextNodeValue(devtools.Callback.wrap(mycallback), | |
619 node.id_, text); | |
620 }; | |
621 | |
622 | |
623 /** | |
624 * Universal callback wrapper for edit dom operations. | |
625 * @param {devtools.DomNode} node Node to apply local changes on. | |
626 * @param {Function} callback Post-operation call. | |
627 * @param {boolean} success True iff operation has completed successfully. | |
628 */ | |
629 devtools.DomAgent.prototype.didApplyDomChange_ = function(node, | |
630 callback, success) { | |
631 if (!success) { | |
632 return; | |
633 } | |
634 callback(); | |
635 var elem = WebInspector.panels.elements.treeOutline.findTreeElement(node); | |
636 if (elem) { | |
637 elem._updateTitle(); | |
638 } | |
639 }; | |
640 | |
641 | |
642 /** | |
643 * @see DomAgentDelegate. | |
644 * {@inheritDoc}. | |
645 */ | |
646 devtools.DomAgent.prototype.attributesUpdated = function(nodeId, attrsArray) { | |
647 var node = this.idToDomNode_[nodeId]; | |
648 node.setAttributesPayload_(attrsArray); | |
649 }; | |
650 | |
651 | |
652 /** | |
653 * Returns node for id. | |
654 * @param {number} nodeId Id to get node for. | |
655 * @return {devtools.DomNode} Node with given id. | |
656 */ | |
657 devtools.DomAgent.prototype.getNodeForId = function(nodeId) { | |
658 return this.idToDomNode_[nodeId]; | |
659 }; | |
660 | |
661 | |
662 /** | |
663 * @see DomAgentDelegate. | |
664 * {@inheritDoc}. | |
665 */ | |
666 devtools.DomAgent.prototype.setDocumentElement = function(payload) { | |
667 var doc = this.getDocument(); | |
668 if (doc.documentElement) { | |
669 this.reset(); | |
670 doc = this.getDocument(); | |
671 } | |
672 this.setChildNodes(0, [payload]); | |
673 doc.documentElement = doc.firstChild; | |
674 doc.documentElement.ownerDocument = doc; | |
675 WebInspector.panels.elements.reset(); | |
676 }; | |
677 | |
678 | |
679 /** | |
680 * @see DomAgentDelegate. | |
681 * {@inheritDoc}. | |
682 */ | |
683 devtools.DomAgent.prototype.setChildNodes = function(parentId, payloads) { | |
684 var parent = this.idToDomNode_[parentId]; | |
685 if (parent.children) { | |
686 return; | |
687 } | |
688 parent.setChildrenPayload_(payloads); | |
689 this.bindNodes_(parent.children); | |
690 }; | |
691 | |
692 | |
693 /** | |
694 * Binds nodes to ids recursively. | |
695 * @param {Array.<devtools.DomNode>} children Nodes to bind. | |
696 */ | |
697 devtools.DomAgent.prototype.bindNodes_ = function(children) { | |
698 for (var i = 0; i < children.length; ++i) { | |
699 var child = children[i]; | |
700 this.idToDomNode_[child.id_] = child; | |
701 if (child.children) { | |
702 this.bindNodes_(child.children); | |
703 } | |
704 } | |
705 }; | |
706 | |
707 | |
708 /** | |
709 * @see DomAgentDelegate. | |
710 * {@inheritDoc}. | |
711 */ | |
712 devtools.DomAgent.prototype.hasChildrenUpdated = function(nodeId, newValue) { | |
713 var node = this.idToDomNode_[nodeId]; | |
714 var outline = WebInspector.panels.elements.treeOutline; | |
715 var treeElement = outline.findTreeElement(node); | |
716 if (treeElement) { | |
717 treeElement.hasChildren = newValue; | |
718 treeElement.whitespaceIgnored = Preferences.ignoreWhitespace; | |
719 } | |
720 }; | |
721 | |
722 | |
723 /** | |
724 * @see DomAgentDelegate. | |
725 * {@inheritDoc}. | |
726 */ | |
727 devtools.DomAgent.prototype.childNodeInserted = function( | |
728 parentId, prevId, payload) { | |
729 var parent = this.idToDomNode_[parentId]; | |
730 var prev = this.idToDomNode_[prevId]; | |
731 var node = parent.insertChild_(prev, payload); | |
732 this.idToDomNode_[node.id_] = node; | |
733 var event = { target : node, relatedNode : parent }; | |
734 this.getDocument().fireDomEvent_('DOMNodeInserted', event); | |
735 }; | |
736 | |
737 | |
738 /** | |
739 * @see DomAgentDelegate. | |
740 * {@inheritDoc}. | |
741 */ | |
742 devtools.DomAgent.prototype.childNodeRemoved = function( | |
743 parentId, nodeId) { | |
744 var parent = this.idToDomNode_[parentId]; | |
745 var node = this.idToDomNode_[nodeId]; | |
746 parent.removeChild_(node); | |
747 var event = { target : node, relatedNode : parent }; | |
748 this.getDocument().fireDomEvent_('DOMNodeRemoved', event); | |
749 delete this.idToDomNode_[nodeId]; | |
750 }; | |
751 | |
752 | |
753 /** | |
754 * @see DomAgentDelegate. | |
755 * {@inheritDoc}. | |
756 */ | |
757 devtools.DomAgent.prototype.performSearch = function(query, callback) { | |
758 this.searchResults_ = []; | |
759 RemoteDomAgent.PerformSearch( | |
760 devtools.Callback.wrap( | |
761 goog.bind(this.performSearchCallback_, this, callback, | |
762 this.searchResults_)), | |
763 query); | |
764 }; | |
765 | |
766 | |
767 /** | |
768 * Invokes callback for nodes that needs to clear highlighting. | |
769 * @param {function(Array.<devtools.DomNode>)} callback to accept the result. | |
770 */ | |
771 devtools.DomAgent.prototype.searchCanceled = function(callback) { | |
772 if (!this.searchResults_) | |
773 return; | |
774 | |
775 var nodes = []; | |
776 for (var i = 0; i < this.searchResults_.length; ++i) { | |
777 var nodeId = this.searchResults_[i]; | |
778 var node = this.idToDomNode_[nodeId]; | |
779 nodes.push(node); | |
780 } | |
781 | |
782 callback(nodes); | |
783 this.searchResults_ = null; | |
784 }; | |
785 | |
786 | |
787 /** | |
788 * Invokes callback for each node that needs to gain highlighting. | |
789 * @param {function(Array.<devtools.DomNode>)} callback to accept the result. | |
790 * @param {Array.<number>} searchResults to be populated. | |
791 * @param {Array.<number>} nodeIds Ids to highlight. | |
792 */ | |
793 devtools.DomAgent.prototype.performSearchCallback_ = function(callback, | |
794 searchResults, nodeIds) { | |
795 | |
796 if (this.searchResults_ !== searchResults) | |
797 return; // another search has requested and this results are obsolete | |
798 | |
799 var nodes = []; | |
800 | |
801 for (var i = 0; i < nodeIds.length; ++i) { | |
802 var node = this.idToDomNode_[nodeIds[i]]; | |
803 searchResults.push(nodeIds[i]); | |
804 nodes.push(node); | |
805 } | |
806 | |
807 callback(nodes); | |
808 }; | |
809 | |
810 | |
811 /** | |
812 * Returns a node by index from the actual search results | |
813 * (last performSearch). | |
814 * @param {number} index in the results. | |
815 * @return {devtools.DomNode} | |
816 */ | |
817 devtools.DomAgent.prototype.getSearchResultNode = function(index) { | |
818 return this.idToDomNode_[this.searchResults_[index]]; | |
819 }; | |
820 | |
821 | |
822 /** | |
823 * Returns all properties of the given node. | |
824 * @param {number} nodeId Node to get properties for. | |
825 * @param {Array.<string>} path Path to the object. | |
826 * @param {number} protoDepth Depth to the exact proto level. | |
827 * @param {function(string):undefined} callback Function to call with the | |
828 * result. | |
829 */ | |
830 devtools.DomAgent.prototype.getNodePropertiesAsync = function(nodeId, | |
831 path, protoDepth, callback) { | |
832 var callbackId = this.utilityFunctionCallbackWrapper_(callback); | |
833 RemoteToolsAgent.ExecuteUtilityFunction(callbackId, | |
834 'getProperties', nodeId, | |
835 JSON.stringify([path, protoDepth])); | |
836 }; | |
837 | |
838 | |
839 /** | |
840 * Returns prototype chain for a given node. | |
841 * @param {number} nodeId Node to get prototypes for. | |
842 * @param {Function} callback. | |
843 */ | |
844 devtools.DomAgent.prototype.getNodePrototypesAsync = function(nodeId, | |
845 callback) { | |
846 var callbackId = this.utilityFunctionCallbackWrapper_(callback); | |
847 RemoteToolsAgent.ExecuteUtilityFunction(callbackId, | |
848 'getPrototypes', nodeId, '[]'); | |
849 }; | |
850 | |
851 | |
852 /** | |
853 * Returns styles for given node. | |
854 * @param {devtools.DomNode} node Node to get prototypes for. | |
855 * @param {boolean} authorOnly Returns only author styles if true. | |
856 * @param {Function} callback. | |
857 */ | |
858 devtools.DomAgent.prototype.getNodeStylesAsync = function(node, | |
859 authorOnly, callback) { | |
860 var callbackId = this.utilityFunctionCallbackWrapper_(callback); | |
861 RemoteToolsAgent.ExecuteUtilityFunction(callbackId, | |
862 'getStyles', | |
863 node.id_, | |
864 JSON.stringify([authorOnly])); | |
865 }; | |
866 | |
867 | |
868 /** | |
869 * Toggles style with given id on/off. | |
870 * @param {devtools.CSSStyleDeclaration} style Style to toggle. | |
871 * @param {boolean} enabled True if style should be enabled. | |
872 * @param {string} name Style name. | |
873 * @param {Function} callback. | |
874 */ | |
875 devtools.DomAgent.prototype.toggleNodeStyleAsync = function( | |
876 style, enabled, name, callback) { | |
877 var callbackId = this.utilityFunctionCallbackWrapper_(callback); | |
878 RemoteToolsAgent.ExecuteUtilityFunction(callbackId, | |
879 'toggleNodeStyle', | |
880 style.nodeId_, | |
881 JSON.stringify([style.id_, enabled, name])); | |
882 }; | |
883 | |
884 | |
885 /** | |
886 * Applies new text to a style. | |
887 * @param {devtools.CSSStyleDeclaration} style Style to edit. | |
888 * @param {string} name Property name to edit. | |
889 * @param {string} styleText Text to set the style from. | |
890 * @param {Function} callback. | |
891 */ | |
892 devtools.DomAgent.prototype.applyStyleTextAsync = function( | |
893 style, name, styleText, callback) { | |
894 var callbackId = this.utilityFunctionCallbackWrapper_(callback); | |
895 RemoteToolsAgent.ExecuteUtilityFunction( | |
896 callbackId, | |
897 'applyStyleText', | |
898 style.nodeId_, | |
899 JSON.stringify([style.id_, name, styleText])); | |
900 }; | |
901 | |
902 | |
903 /** | |
904 * Sets style property with given name to a value. | |
905 * @param {devtools.DomNode} node Node to edit style for. | |
906 * @param {string} name Property name to edit. | |
907 * @param {string} value New value. | |
908 * @param {Function} callback. | |
909 */ | |
910 devtools.DomAgent.prototype.setStylePropertyAsync = function( | |
911 node, name, value, callback) { | |
912 var callbackId = this.utilityFunctionCallbackWrapper_(callback); | |
913 RemoteToolsAgent.ExecuteUtilityFunction( | |
914 callbackId, | |
915 'setStyleProperty', | |
916 node.id_, | |
917 JSON.stringify([name, value])); | |
918 }; | |
919 | |
920 | |
921 /** | |
922 * Dumps exception if something went wrong in ExecuteUtilityFunction. | |
923 * @param {Function} callback Callback to wrap. | |
924 * @return {number} Callback id. | |
925 */ | |
926 devtools.DomAgent.prototype.utilityFunctionCallbackWrapper_ = | |
927 function(callback) { | |
928 var mycallback = function(result, exception) { | |
929 if (exception && exception.length) { | |
930 debugPrint('Exception in ExecuteUtilityFunction styles:' + exception); | |
931 return; | |
932 } | |
933 callback(result); | |
934 }; | |
935 return devtools.Callback.wrap(mycallback); | |
936 }; | |
937 | |
938 | |
939 /** | |
940 * Represents remote CSSStyleDeclaration for using in StyleSidebarPane. | |
941 * @param {id, Array<Object>} payload built by inject's getStyle from the | |
942 * injected js. | |
943 * @constructor | |
944 */ | |
945 devtools.CSSStyleDeclaration = function(payload) { | |
946 this.id_ = payload[0]; | |
947 this.width = payload[1]; | |
948 this.height = payload[2]; | |
949 this.__disabledProperties = payload[3]; | |
950 this.__disabledPropertyValues = payload[4]; | |
951 this.__disabledPropertyPriorities = payload[5]; | |
952 | |
953 this.length = payload.length - 6; | |
954 this.priority_ = {}; | |
955 this.implicit_ = {}; | |
956 this.shorthand_ = {}; | |
957 this.value_ = {}; | |
958 | |
959 for (var i = 6; i < payload.length; ++i) { | |
960 var p = payload[i]; | |
961 var name = p[0]; | |
962 | |
963 this.priority_[name] = p[1]; | |
964 this.implicit_[name] = p[2]; | |
965 this.shorthand_[name] = p[3]; | |
966 this.value_[name] = p[4]; | |
967 | |
968 this[i - 6] = name; | |
969 } | |
970 }; | |
971 | |
972 | |
973 /** | |
974 * @param {string} name of a CSS property. | |
975 * @return {string} | |
976 */ | |
977 devtools.CSSStyleDeclaration.prototype.getPropertyValue = function(name) { | |
978 return this.value_[name] || ''; | |
979 }; | |
980 | |
981 | |
982 /** | |
983 * @param {string} name of a CSS property. | |
984 * @return {string} 'important' | ''. | |
985 */ | |
986 devtools.CSSStyleDeclaration.prototype.getPropertyPriority = function(name) { | |
987 return this.priority_[name] || ''; | |
988 }; | |
989 | |
990 | |
991 /** | |
992 * @param {string} name of a CSS property. | |
993 * @return {string} shorthand name or '' | |
994 */ | |
995 devtools.CSSStyleDeclaration.prototype.getPropertyShorthand = function(name) { | |
996 return this.shorthand_[name] || ''; | |
997 }; | |
998 | |
999 | |
1000 /** | |
1001 * @param {string} name of a CSS property. | |
1002 * @return {boolean} | |
1003 */ | |
1004 devtools.CSSStyleDeclaration.prototype.isPropertyImplicit = function(name) { | |
1005 return !!this.implicit_[name]; | |
1006 }; | |
1007 | |
1008 | |
1009 function firstChildSkippingWhitespace() { | |
1010 return this.firstChild; | |
1011 } | |
1012 | |
1013 | |
1014 function onlyTextChild() { | |
1015 if (!this.children) { | |
1016 return null; | |
1017 } else if (this.children.length == 1 && | |
1018 this.children[0].nodeType == Node.TEXT_NODE) { | |
1019 return this.children[0]; | |
1020 } else { | |
1021 return null; | |
1022 } | |
1023 } | |
OLD | NEW |