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