1/* 2 * Copyright (C) 2009 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31WebInspector.DOMNode = function(doc, payload) { 32 this.ownerDocument = doc; 33 34 this._id = payload.id; 35 this.nodeType = payload.nodeType; 36 this.nodeName = payload.nodeName; 37 this._nodeValue = payload.nodeValue; 38 this.textContent = this.nodeValue; 39 40 this.attributes = []; 41 this._attributesMap = {}; 42 if (payload.attributes) 43 this._setAttributesPayload(payload.attributes); 44 45 this._childNodeCount = payload.childNodeCount; 46 this.children = null; 47 48 this.nextSibling = null; 49 this.prevSibling = null; 50 this.firstChild = null; 51 this.parentNode = null; 52 53 if (payload.childNodes) 54 this._setChildrenPayload(payload.childNodes); 55 56 this._computedStyle = null; 57 this.style = null; 58 this._matchedCSSRules = []; 59} 60 61WebInspector.DOMNode.prototype = { 62 hasAttributes: function() 63 { 64 return this.attributes.length > 0; 65 }, 66 67 hasChildNodes: function() { 68 return this._childNodeCount > 0; 69 }, 70 71 get nodeValue() { 72 return this._nodeValue; 73 }, 74 75 set nodeValue(value) { 76 if (this.nodeType != Node.TEXT_NODE) 77 return; 78 var self = this; 79 var callback = function() 80 { 81 self._nodeValue = value; 82 self.textContent = value; 83 }; 84 this.ownerDocument._domAgent.setTextNodeValueAsync(this, value, callback); 85 }, 86 87 getAttribute: function(name) 88 { 89 var attr = this._attributesMap[name]; 90 return attr ? attr.value : undefined; 91 }, 92 93 setAttribute: function(name, value) 94 { 95 var self = this; 96 var callback = function() 97 { 98 var attr = self._attributesMap[name]; 99 if (attr) 100 attr.value = value; 101 else 102 attr = self._addAttribute(name, value); 103 }; 104 this.ownerDocument._domAgent.setAttributeAsync(this, name, value, callback); 105 }, 106 107 removeAttribute: function(name) 108 { 109 var self = this; 110 var callback = function() 111 { 112 delete self._attributesMap[name]; 113 for (var i = 0; i < self.attributes.length; ++i) { 114 if (self.attributes[i].name == name) { 115 self.attributes.splice(i, 1); 116 break; 117 } 118 } 119 }; 120 this.ownerDocument._domAgent.removeAttributeAsync(this, name, callback); 121 }, 122 123 _setAttributesPayload: function(attrs) 124 { 125 for (var i = 0; i < attrs.length; i += 2) 126 this._addAttribute(attrs[i], attrs[i + 1]); 127 }, 128 129 _insertChild: function(prev, payload) 130 { 131 var node = new WebInspector.DOMNode(this.ownerDocument, payload); 132 if (!prev) 133 // First node 134 this.children = [ node ]; 135 else 136 this.children.splice(this.children.indexOf(prev) + 1, 0, node); 137 this._renumber(); 138 return node; 139 }, 140 141 removeChild_: function(node) 142 { 143 this.children.splice(this.children.indexOf(node), 1); 144 node.parentNode = null; 145 this._renumber(); 146 }, 147 148 _setChildrenPayload: function(payloads) 149 { 150 this.children = []; 151 for (var i = 0; i < payloads.length; ++i) { 152 var payload = payloads[i]; 153 var node = new WebInspector.DOMNode(this.ownerDocument, payload); 154 this.children.push(node); 155 } 156 this._renumber(); 157 }, 158 159 _renumber: function() 160 { 161 this._childNodeCount = this.children.length; 162 if (this._childNodeCount == 0) { 163 this.firstChild = null; 164 return; 165 } 166 this.firstChild = this.children[0]; 167 for (var i = 0; i < this._childNodeCount; ++i) { 168 var child = this.children[i]; 169 child.nextSibling = i + 1 < this._childNodeCount ? this.children[i + 1] : null; 170 child.prevSibling = i - 1 >= 0 ? this.children[i - 1] : null; 171 child.parentNode = this; 172 } 173 }, 174 175 _addAttribute: function(name, value) 176 { 177 var attr = { 178 "name": name, 179 "value": value, 180 "_node": this 181 }; 182 this._attributesMap[name] = attr; 183 this.attributes.push(attr); 184 }, 185 186 _setStyles: function(computedStyle, inlineStyle, styleAttributes, matchedCSSRules) 187 { 188 this._computedStyle = new WebInspector.CSSStyleDeclaration(computedStyle); 189 this.style = new WebInspector.CSSStyleDeclaration(inlineStyle); 190 191 for (var name in styleAttributes) { 192 if (this._attributesMap[name]) 193 this._attributesMap[name].style = new WebInspector.CSSStyleDeclaration(styleAttributes[name]); 194 } 195 196 this._matchedCSSRules = []; 197 for (var i = 0; i < matchedCSSRules.length; i++) 198 this._matchedCSSRules.push(WebInspector.CSSStyleDeclaration.parseRule(matchedCSSRules[i])); 199 }, 200 201 _clearStyles: function() 202 { 203 this.computedStyle = null; 204 this.style = null; 205 for (var name in this._attributesMap) 206 this._attributesMap[name].style = null; 207 this._matchedCSSRules = null; 208 } 209} 210 211WebInspector.DOMDocument = function(domAgent, defaultView) 212{ 213 WebInspector.DOMNode.call(this, null, 214 { 215 id: 0, 216 nodeType: Node.DOCUMENT_NODE, 217 nodeName: "", 218 nodeValue: "", 219 attributes: [], 220 childNodeCount: 0 221 }); 222 this._listeners = {}; 223 this._domAgent = domAgent; 224 this.defaultView = defaultView; 225} 226 227WebInspector.DOMDocument.prototype = { 228 229 addEventListener: function(name, callback, useCapture) 230 { 231 var listeners = this._listeners[name]; 232 if (!listeners) { 233 listeners = []; 234 this._listeners[name] = listeners; 235 } 236 listeners.push(callback); 237 }, 238 239 removeEventListener: function(name, callback, useCapture) 240 { 241 var listeners = this._listeners[name]; 242 if (!listeners) 243 return; 244 245 var index = listeners.indexOf(callback); 246 if (index != -1) 247 listeners.splice(index, 1); 248 }, 249 250 _fireDomEvent: function(name, event) 251 { 252 var listeners = this._listeners[name]; 253 if (!listeners) 254 return; 255 256 for (var i = 0; i < listeners.length; ++i) 257 listeners[i](event); 258 } 259} 260 261WebInspector.DOMDocument.prototype.__proto__ = WebInspector.DOMNode.prototype; 262 263 264WebInspector.DOMWindow = function(domAgent) 265{ 266 this._domAgent = domAgent; 267} 268 269WebInspector.DOMWindow.prototype = { 270 get document() 271 { 272 return this._domAgent.document; 273 }, 274 275 get Node() 276 { 277 return WebInspector.DOMNode; 278 }, 279 280 get Element() 281 { 282 return WebInspector.DOMNode; 283 }, 284 285 Object: function() 286 { 287 }, 288 289 getComputedStyle: function(node) 290 { 291 return node._computedStyle; 292 }, 293 294 getMatchedCSSRules: function(node, pseudoElement, authorOnly) 295 { 296 return node._matchedCSSRules; 297 } 298} 299 300WebInspector.DOMAgent = function() { 301 this._window = new WebInspector.DOMWindow(this); 302 this._idToDOMNode = null; 303 this.document = null; 304 305 // Install onpopulate handler. This is a temporary measure. 306 // TODO: add this code into the original updateChildren once domAgent 307 // becomes primary source of DOM information. 308 // TODO2: update ElementsPanel to not track embedded iframes - it is already being handled 309 // in the agent backend. 310 var domAgent = this; 311 var originalUpdateChildren = WebInspector.ElementsTreeElement.prototype.updateChildren; 312 WebInspector.ElementsTreeElement.prototype.updateChildren = function() 313 { 314 domAgent.getChildNodesAsync(this.representedObject, originalUpdateChildren.bind(this)); 315 }; 316 317 // Mute console handle to avoid crash on selection change. 318 // TODO: Re-implement inspectorConsoleAPI to work in a serialized way and remove this workaround. 319 WebInspector.Console.prototype.addInspectedNode = function() 320 { 321 }; 322 323 // Whitespace is ignored in InspectorDOMAgent already -> no need to filter. 324 // TODO: Either remove all of its usages or push value into the agent backend. 325 Preferences.ignoreWhitespace = false; 326} 327 328WebInspector.DOMAgent.prototype = { 329 get inspectedWindow() 330 { 331 return this._window; 332 }, 333 334 getChildNodesAsync: function(parent, opt_callback) 335 { 336 var children = parent.children; 337 if (children && opt_callback) { 338 opt_callback(children); 339 return; 340 } 341 var mycallback = function() { 342 if (opt_callback) { 343 opt_callback(parent.children); 344 } 345 }; 346 var callId = WebInspector.Callback.wrap(mycallback); 347 InspectorController.getChildNodes(callId, parent._id); 348 }, 349 350 setAttributeAsync: function(node, name, value, callback) 351 { 352 var mycallback = this._didApplyDomChange.bind(this, node, callback); 353 InspectorController.setAttribute(WebInspector.Callback.wrap(mycallback), node._id, name, value); 354 }, 355 356 removeAttributeAsync: function(node, name, callback) 357 { 358 var mycallback = this._didApplyDomChange.bind(this, node, callback); 359 InspectorController.removeAttribute(WebInspector.Callback.wrap(mycallback), node._id, name); 360 }, 361 362 setTextNodeValueAsync: function(node, text, callback) 363 { 364 var mycallback = this._didApplyDomChange.bind(this, node, callback); 365 InspectorController.setTextNodeValue(WebInspector.Callback.wrap(mycallback), node._id, text); 366 }, 367 368 _didApplyDomChange: function(node, callback, success) 369 { 370 if (!success) 371 return; 372 callback(); 373 // TODO(pfeldman): Fix this hack. 374 var elem = WebInspector.panels.elements.treeOutline.findTreeElement(node); 375 if (elem) { 376 elem._updateTitle(); 377 } 378 }, 379 380 _attributesUpdated: function(nodeId, attrsArray) 381 { 382 var node = this._idToDOMNode[nodeId]; 383 node._setAttributesPayload(attrsArray); 384 }, 385 386 getNodeForId: function(nodeId) { 387 return this._idToDOMNode[nodeId]; 388 }, 389 390 _setDocumentElement: function(payload) 391 { 392 this.document = new WebInspector.DOMDocument(this, this._window); 393 this._idToDOMNode = { 0 : this.document }; 394 this._setChildNodes(0, [payload]); 395 this.document.documentElement = this.document.firstChild; 396 this.document.documentElement.ownerDocument = this.document; 397 WebInspector.panels.elements.reset(); 398 }, 399 400 _setChildNodes: function(parentId, payloads) 401 { 402 var parent = this._idToDOMNode[parentId]; 403 if (parent.children) { 404 return; 405 } 406 parent._setChildrenPayload(payloads); 407 this._bindNodes(parent.children); 408 }, 409 410 _bindNodes: function(children) 411 { 412 for (var i = 0; i < children.length; ++i) { 413 var child = children[i]; 414 this._idToDOMNode[child._id] = child; 415 if (child.children) 416 this._bindNodes(child.children); 417 } 418 }, 419 420 _hasChildrenUpdated: function(nodeId, newValue) 421 { 422 var node = this._idToDOMNode[nodeId]; 423 var outline = WebInspector.panels.elements.treeOutline; 424 var treeElement = outline.findTreeElement(node); 425 if (treeElement) { 426 treeElement.hasChildren = newValue; 427 treeElement.whitespaceIgnored = Preferences.ignoreWhitespace; 428 } 429 }, 430 431 _childNodeInserted: function(parentId, prevId, payload) 432 { 433 var parent = this._idToDOMNode[parentId]; 434 var prev = this._idToDOMNode[prevId]; 435 var node = parent._insertChild(prev, payload); 436 this._idToDOMNode[node._id] = node; 437 var event = { target : node, relatedNode : parent }; 438 this.document._fireDomEvent("DOMNodeInserted", event); 439 }, 440 441 _childNodeRemoved: function(parentId, nodeId) 442 { 443 var parent = this._idToDOMNode[parentId]; 444 var node = this._idToDOMNode[nodeId]; 445 parent.removeChild_(node); 446 var event = { target : node, relatedNode : parent }; 447 this.document._fireDomEvent("DOMNodeRemoved", event); 448 delete this._idToDOMNode[nodeId]; 449 } 450} 451 452WebInspector.CSSStyleDeclaration = function(payload) { 453 this._id = payload.id; 454 this.width = payload.width; 455 this.height = payload.height; 456 this.__disabledProperties = payload.__disabledProperties; 457 this.__disabledPropertyValues = payload.__disabledPropertyValues; 458 this.__disabledPropertyPriorities = payload.__disabledPropertyPriorities; 459 this.uniqueStyleProperties = payload.uniqueStyleProperties; 460 this._shorthandValues = payload.shorthandValues; 461 this._propertyMap = {}; 462 this._longhandProperties = {}; 463 this.length = payload.properties.length; 464 465 for (var i = 0; i < this.length; ++i) { 466 var property = payload.properties[i]; 467 var name = property.name; 468 this[i] = name; 469 this._propertyMap[name] = property; 470 } 471 472 // Index longhand properties. 473 for (var i = 0; i < this.uniqueStyleProperties.length; ++i) { 474 var name = this.uniqueStyleProperties[i]; 475 var property = this._propertyMap[name]; 476 if (property.shorthand) { 477 var longhands = this._longhandProperties[property.shorthand]; 478 if (!longhands) { 479 longhands = []; 480 this._longhandProperties[property.shorthand] = longhands; 481 } 482 longhands.push(name); 483 } 484 } 485} 486 487WebInspector.CSSStyleDeclaration.parseStyle = function(payload) 488{ 489 return new WebInspector.CSSStyleDeclaration(payload); 490} 491 492WebInspector.CSSStyleDeclaration.parseRule = function(payload) 493{ 494 var rule = {}; 495 rule._id = payload.id; 496 rule.selectorText = payload.selectorText; 497 rule.style = new WebInspector.CSSStyleDeclaration(payload.style); 498 rule.style.parentRule = rule; 499 rule.isUserAgent = payload.isUserAgent; 500 rule.isUser = payload.isUser; 501 if (payload.parentStyleSheet) 502 rule.parentStyleSheet = { href: payload.parentStyleSheet.href }; 503 504 return rule; 505} 506 507WebInspector.CSSStyleDeclaration.prototype = { 508 getPropertyValue: function(name) 509 { 510 var property = this._propertyMap[name]; 511 return property ? property.value : ""; 512 }, 513 514 getPropertyPriority: function(name) 515 { 516 var property = this._propertyMap[name]; 517 return property ? property.priority : ""; 518 }, 519 520 getPropertyShorthand: function(name) 521 { 522 var property = this._propertyMap[name]; 523 return property ? property.shorthand : ""; 524 }, 525 526 isPropertyImplicit: function(name) 527 { 528 var property = this._propertyMap[name]; 529 return property ? property.implicit : ""; 530 }, 531 532 styleTextWithShorthands: function() 533 { 534 var cssText = ""; 535 var foundProperties = {}; 536 for (var i = 0; i < this.length; ++i) { 537 var individualProperty = this[i]; 538 var shorthandProperty = this.getPropertyShorthand(individualProperty); 539 var propertyName = (shorthandProperty || individualProperty); 540 541 if (propertyName in foundProperties) 542 continue; 543 544 if (shorthandProperty) { 545 var value = this.getPropertyValue(shorthandProperty); 546 var priority = this.getShorthandPriority(shorthandProperty); 547 } else { 548 var value = this.getPropertyValue(individualProperty); 549 var priority = this.getPropertyPriority(individualProperty); 550 } 551 552 foundProperties[propertyName] = true; 553 554 cssText += propertyName + ": " + value; 555 if (priority) 556 cssText += " !" + priority; 557 cssText += "; "; 558 } 559 560 return cssText; 561 }, 562 563 getLonghandProperties: function(name) 564 { 565 return this._longhandProperties[name] || []; 566 }, 567 568 getShorthandValue: function(shorthandProperty) 569 { 570 return this._shorthandValues[shorthandProperty]; 571 }, 572 573 getShorthandPriority: function(shorthandProperty) 574 { 575 var priority = this.getPropertyPriority(shorthandProperty); 576 if (priority) 577 return priority; 578 579 var longhands = this._longhandProperties[shorthandProperty]; 580 return longhands ? this.getPropertyPriority(longhands[0]) : null; 581 } 582} 583 584WebInspector.attributesUpdated = function() 585{ 586 this.domAgent._attributesUpdated.apply(this.domAgent, arguments); 587} 588 589WebInspector.setDocumentElement = function() 590{ 591 this.domAgent._setDocumentElement.apply(this.domAgent, arguments); 592} 593 594WebInspector.setChildNodes = function() 595{ 596 this.domAgent._setChildNodes.apply(this.domAgent, arguments); 597} 598 599WebInspector.hasChildrenUpdated = function() 600{ 601 this.domAgent._hasChildrenUpdated.apply(this.domAgent, arguments); 602} 603 604WebInspector.childNodeInserted = function() 605{ 606 this.domAgent._childNodeInserted.apply(this.domAgent, arguments); 607 this._childNodeInserted.bind(this); 608} 609 610WebInspector.childNodeRemoved = function() 611{ 612 this.domAgent._childNodeRemoved.apply(this.domAgent, arguments); 613 this._childNodeRemoved.bind(this); 614} 615 616WebInspector.didGetChildNodes = WebInspector.Callback.processCallback; 617WebInspector.didPerformSearch = WebInspector.Callback.processCallback; 618WebInspector.didApplyDomChange = WebInspector.Callback.processCallback; 619WebInspector.didRemoveAttribute = WebInspector.Callback.processCallback; 620WebInspector.didSetTextNodeValue = WebInspector.Callback.processCallback; 621 622// Temporary methods for DOMAgent migration. 623WebInspector.wrapNodeWithStyles = function(node, styles) 624{ 625 var windowStub = new WebInspector.DOMWindow(null); 626 var docStub = new WebInspector.DOMDocument(null, windowStub); 627 var payload = {}; 628 payload.nodeType = node.nodeType; 629 payload.nodeName = node.nodeName; 630 payload.nodeValue = node.nodeValue; 631 payload.attributes = []; 632 payload.childNodeCount = 0; 633 634 for (var i = 0; i < node.attributes.length; ++i) { 635 var attr = node.attributes[i]; 636 payload.attributes.push(attr.name); 637 payload.attributes.push(attr.value); 638 } 639 var nodeStub = new WebInspector.DOMNode(docStub, payload); 640 nodeStub._setStyles(styles.computedStyle, styles.inlineStyle, styles.styleAttributes, styles.matchedCSSRules); 641 return nodeStub; 642} 643 644// Temporary methods that will be dispatched via InspectorController into the injected context. 645InspectorController.getStyles = function(nodeId, authorOnly, callback) 646{ 647 setTimeout(function() { 648 callback(InjectedScript.getStyles(nodeId, authorOnly)); 649 }, 0) 650} 651 652InspectorController.getComputedStyle = function(nodeId, callback) 653{ 654 setTimeout(function() { 655 callback(InjectedScript.getComputedStyle(nodeId)); 656 }, 0) 657} 658 659InspectorController.getInlineStyle = function(nodeId, callback) 660{ 661 setTimeout(function() { 662 callback(InjectedScript.getInlineStyle(nodeId)); 663 }, 0) 664} 665 666InspectorController.applyStyleText = function(styleId, styleText, propertyName, callback) 667{ 668 setTimeout(function() { 669 callback(InjectedScript.applyStyleText(styleId, styleText, propertyName)); 670 }, 0) 671} 672 673InspectorController.setStyleText = function(style, cssText, callback) 674{ 675 setTimeout(function() { 676 callback(InjectedScript.setStyleText(style, cssText)); 677 }, 0) 678} 679 680InspectorController.toggleStyleEnabled = function(styleId, propertyName, disabled, callback) 681{ 682 setTimeout(function() { 683 callback(InjectedScript.toggleStyleEnabled(styleId, propertyName, disabled)); 684 }, 0) 685} 686 687InspectorController.applyStyleRuleText = function(ruleId, newContent, selectedNode, callback) 688{ 689 setTimeout(function() { 690 callback(InjectedScript.applyStyleRuleText(ruleId, newContent, selectedNode)); 691 }, 0) 692} 693 694InspectorController.addStyleSelector = function(newContent, callback) 695{ 696 setTimeout(function() { 697 callback(InjectedScript.addStyleSelector(newContent)); 698 }, 0) 699} 700 701InspectorController.setStyleProperty = function(styleId, name, value, callback) { 702 setTimeout(function() { 703 callback(InjectedScript.setStyleProperty(styleId, name, value)); 704 }, 0) 705} 706 707InspectorController.getPrototypes = function(objectProxy, callback) { 708 setTimeout(function() { 709 callback(InjectedScript.getPrototypes(objectProxy)); 710 }, 0) 711} 712 713InspectorController.getProperties = function(objectProxy, ignoreHasOwnProperty, callback) { 714 setTimeout(function() { 715 callback(InjectedScript.getProperties(objectProxy, ignoreHasOwnProperty)); 716 }, 0) 717} 718 719InspectorController.setPropertyValue = function(objectProxy, propertyName, expression, callback) { 720 setTimeout(function() { 721 callback(InjectedScript.setPropertyValue(objectProxy, propertyName, expression)); 722 }, 0) 723} 724 725