1/* 2 * Copyright (C) 2013 Apple 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 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26WebInspector.Resource = function(url, mimeType, type, loaderIdentifier, requestIdentifier, requestMethod, requestHeaders, requestData, requestSentTimestamp, initiatorSourceCodeLocation) 27{ 28 WebInspector.SourceCode.call(this); 29 30 console.assert(url); 31 32 if (type in WebInspector.Resource.Type) 33 type = WebInspector.Resource.Type[type]; 34 35 this._url = url; 36 this._mimeType = mimeType; 37 this._type = type || WebInspector.Resource.Type.fromMIMEType(mimeType); 38 this._loaderIdentifier = loaderIdentifier || null; 39 this._requestIdentifier = requestIdentifier || null; 40 this._requestMethod = requestMethod || null; 41 this._requestData = requestData || null; 42 this._requestHeaders = requestHeaders || {}; 43 this._responseHeaders = {}; 44 this._parentFrame = null; 45 this._initiatorSourceCodeLocation = initiatorSourceCodeLocation || null; 46 this._requestSentTimestamp = requestSentTimestamp || NaN; 47 this._responseReceivedTimestamp = NaN; 48 this._lastRedirectReceivedTimestamp = NaN; 49 this._lastDataReceivedTimestamp = NaN; 50 this._finishedOrFailedTimestamp = NaN; 51 this._size = NaN; 52 this._transferSize = NaN; 53 this._cached = false; 54}; 55 56WebInspector.Object.addConstructorFunctions(WebInspector.Resource); 57 58WebInspector.Resource.TypeIdentifier = "resource"; 59WebInspector.Resource.URLCookieKey = "resource-url"; 60WebInspector.Resource.MainResourceCookieKey = "resource-is-main-resource"; 61 62WebInspector.Resource.Event = { 63 URLDidChange: "resource-url-did-change", 64 MIMETypeDidChange: "resource-mime-type-did-change", 65 TypeDidChange: "resource-type-did-change", 66 RequestHeadersDidChange: "resource-request-headers-did-change", 67 ResponseReceived: "resource-response-received", 68 LoadingDidFinish: "resource-loading-did-finish", 69 LoadingDidFail: "resource-loading-did-fail", 70 TimestampsDidChange: "resource-timestamps-did-change", 71 SizeDidChange: "resource-size-did-change", 72 TransferSizeDidChange: "resource-transfer-size-did-change", 73 CacheStatusDidChange: "resource-cached-did-change" 74}; 75 76// Keep these in sync with the "ResourceType" enum defined by the "Page" domain. 77WebInspector.Resource.Type = { 78 Document: "resource-type-document", 79 Stylesheet: "resource-type-stylesheet", 80 Image: "resource-type-image", 81 Font: "resource-type-font", 82 Script: "resource-type-script", 83 XHR: "resource-type-xhr", 84 WebSocket: "resource-type-websocket", 85 Other: "resource-type-other" 86}; 87 88// This MIME Type map is private, use WebInspector.Resource.Type.fromMIMEType(). 89WebInspector.Resource.Type._mimeTypeMap = { 90 "text/html": WebInspector.Resource.Type.Document, 91 "text/xml": WebInspector.Resource.Type.Document, 92 "text/plain": WebInspector.Resource.Type.Document, 93 "application/xhtml+xml": WebInspector.Resource.Type.Document, 94 "image/svg+xml": WebInspector.Resource.Type.Document, 95 96 "text/css": WebInspector.Resource.Type.Stylesheet, 97 "text/xsl": WebInspector.Resource.Type.Stylesheet, 98 "text/x-less": WebInspector.Resource.Type.Stylesheet, 99 "text/x-sass": WebInspector.Resource.Type.Stylesheet, 100 "text/x-scss": WebInspector.Resource.Type.Stylesheet, 101 102 "application/pdf": WebInspector.Resource.Type.Image, 103 104 "application/x-font-type1": WebInspector.Resource.Type.Font, 105 "application/x-font-ttf": WebInspector.Resource.Type.Font, 106 "application/x-font-woff": WebInspector.Resource.Type.Font, 107 "application/x-truetype-font": WebInspector.Resource.Type.Font, 108 109 "text/javascript": WebInspector.Resource.Type.Script, 110 "text/ecmascript": WebInspector.Resource.Type.Script, 111 "application/javascript": WebInspector.Resource.Type.Script, 112 "application/ecmascript": WebInspector.Resource.Type.Script, 113 "application/x-javascript": WebInspector.Resource.Type.Script, 114 "application/json": WebInspector.Resource.Type.Script, 115 "application/x-json": WebInspector.Resource.Type.Script, 116 "text/x-javascript": WebInspector.Resource.Type.Script, 117 "text/x-json": WebInspector.Resource.Type.Script, 118 "text/javascript1.1": WebInspector.Resource.Type.Script, 119 "text/javascript1.2": WebInspector.Resource.Type.Script, 120 "text/javascript1.3": WebInspector.Resource.Type.Script, 121 "text/jscript": WebInspector.Resource.Type.Script, 122 "text/livescript": WebInspector.Resource.Type.Script, 123 "text/x-livescript": WebInspector.Resource.Type.Script, 124 "text/typescript": WebInspector.Resource.Type.Script, 125 "text/x-clojure": WebInspector.Resource.Type.Script, 126 "text/x-coffeescript": WebInspector.Resource.Type.Script 127}; 128 129WebInspector.Resource.Type.fromMIMEType = function(mimeType) 130{ 131 if (!mimeType) 132 return WebInspector.Resource.Type.Other; 133 134 mimeType = parseMIMEType(mimeType).type; 135 136 if (mimeType in WebInspector.Resource.Type._mimeTypeMap) 137 return WebInspector.Resource.Type._mimeTypeMap[mimeType]; 138 139 if (mimeType.startsWith("image/")) 140 return WebInspector.Resource.Type.Image; 141 142 if (mimeType.startsWith("font/")) 143 return WebInspector.Resource.Type.Font; 144 145 return WebInspector.Resource.Type.Other; 146}; 147 148WebInspector.Resource.Type.displayName = function(type, plural) 149{ 150 switch(type) { 151 case WebInspector.Resource.Type.Document: 152 if (plural) 153 return WebInspector.UIString("Documents"); 154 return WebInspector.UIString("Document"); 155 case WebInspector.Resource.Type.Stylesheet: 156 if (plural) 157 return WebInspector.UIString("Stylesheets"); 158 return WebInspector.UIString("Stylesheet"); 159 case WebInspector.Resource.Type.Image: 160 if (plural) 161 return WebInspector.UIString("Images"); 162 return WebInspector.UIString("Image"); 163 case WebInspector.Resource.Type.Font: 164 if (plural) 165 return WebInspector.UIString("Fonts"); 166 return WebInspector.UIString("Font"); 167 case WebInspector.Resource.Type.Script: 168 if (plural) 169 return WebInspector.UIString("Scripts"); 170 return WebInspector.UIString("Script"); 171 case WebInspector.Resource.Type.XHR: 172 if (plural) 173 return WebInspector.UIString("XHRs"); 174 return WebInspector.UIString("XHR"); 175 case WebInspector.Resource.Type.WebSocket: 176 if (plural) 177 return WebInspector.UIString("Sockets"); 178 return WebInspector.UIString("Socket"); 179 case WebInspector.Resource.Type.Other: 180 return WebInspector.UIString("Other"); 181 default: 182 console.error("Unknown resource type: ", type); 183 return null; 184 } 185}; 186 187WebInspector.Resource.prototype = { 188 constructor: WebInspector.Resource, 189 190 // Public 191 192 get url() 193 { 194 return this._url; 195 }, 196 197 get urlComponents() 198 { 199 if (!this._urlComponents) 200 this._urlComponents = parseURL(this._url); 201 return this._urlComponents; 202 }, 203 204 get displayName() 205 { 206 return WebInspector.displayNameForURL(this._url, this.urlComponents); 207 }, 208 209 get initiatorSourceCodeLocation() 210 { 211 return this._initiatorSourceCodeLocation; 212 }, 213 214 get type() 215 { 216 return this._type; 217 }, 218 219 get mimeType() 220 { 221 return this._mimeType; 222 }, 223 224 get mimeTypeComponents() 225 { 226 if (!this._mimeTypeComponents) 227 this._mimeTypeComponents = parseMIMEType(this._mimeType); 228 return this._mimeTypeComponents; 229 }, 230 231 get syntheticMIMEType() 232 { 233 // Resources are often transferred with a MIME-type that doesn't match the purpose the 234 // resource was loaded for, which is what WebInspector.Resource.Type represents. 235 // This getter generates a MIME-type, if needed, that matches the resource type. 236 237 // If the type matches the Resource.Type of the MIME-type, then return the actual MIME-type. 238 if (this._type === WebInspector.Resource.Type.fromMIMEType(this._mimeType)) 239 return this._mimeType; 240 241 // Return the default MIME-types for the Resource.Type, since the current MIME-type 242 // does not match what is expected for the Resource.Type. 243 switch (this._type) { 244 case WebInspector.Resource.Type.Document: 245 return "text/html"; 246 case WebInspector.Resource.Type.Stylesheet: 247 return "text/css"; 248 case WebInspector.Resource.Type.Script: 249 return "text/javascript"; 250 } 251 252 // Return the actual MIME-type since we don't have a better synthesized one to return. 253 return this._mimeType; 254 }, 255 256 get contentURL() 257 { 258 const maximumDataURLSize = 1024 * 1024; // 1 MiB 259 260 // If content is not available or won't fit a data URL, fallback to using original URL. 261 var content = this.content; 262 if (content === null || content.length > maximumDataURLSize) 263 return this._url; 264 265 return "data:" + this.mimeTypeComponents.type + (this.contentIsBase64Encoded ? ";base64," + content : "," + encodeURIComponent(content)); 266 }, 267 268 isMainResource: function() 269 { 270 return this._parentFrame ? this._parentFrame.mainResource === this : false; 271 }, 272 273 get parentFrame() 274 { 275 return this._parentFrame; 276 }, 277 278 get loaderIdentifier() 279 { 280 return this._loaderIdentifier; 281 }, 282 283 get requestIdentifier() 284 { 285 return this._requestIdentifier; 286 }, 287 288 get finished() 289 { 290 return this._finished; 291 }, 292 293 get failed() 294 { 295 return this._failed; 296 }, 297 298 get canceled() 299 { 300 return this._canceled; 301 }, 302 303 get requestMethod() 304 { 305 return this._requestMethod; 306 }, 307 308 get requestData() 309 { 310 return this._requestData; 311 }, 312 313 get requestDataContentType() 314 { 315 return this._requestHeaders.valueForCaseInsensitiveKey("Content-Type") || null; 316 }, 317 318 get requestHeaders() 319 { 320 return this._requestHeaders; 321 }, 322 323 get responseHeaders() 324 { 325 return this._responseHeaders; 326 }, 327 328 get requestSentTimestamp() 329 { 330 return this._requestSentTimestamp; 331 }, 332 333 get lastRedirectReceivedTimestamp() 334 { 335 return this._lastRedirectReceivedTimestamp; 336 }, 337 338 get responseReceivedTimestamp() 339 { 340 return this._responseReceivedTimestamp; 341 }, 342 343 get lastDataReceivedTimestamp() 344 { 345 return this._lastDataReceivedTimestamp; 346 }, 347 348 get finishedOrFailedTimestamp() 349 { 350 return this._finishedOrFailedTimestamp; 351 }, 352 353 get firstTimestamp() 354 { 355 return this.requestSentTimestamp || this.lastRedirectReceivedTimestamp || this.responseReceivedTimestamp || this.lastDataReceivedTimestamp || this.finishedOrFailedTimestamp; 356 }, 357 358 get lastTimestamp() 359 { 360 return this.finishedOrFailedTimestamp || this.lastDataReceivedTimestamp || this.responseReceivedTimestamp || this.lastRedirectReceivedTimestamp || this.requestSentTimestamp; 361 }, 362 363 get duration() 364 { 365 return this._finishedOrFailedTimestamp - this._requestSentTimestamp; 366 }, 367 368 get latency() 369 { 370 return this._responseReceivedTimestamp - this._requestSentTimestamp; 371 }, 372 373 get receiveDuration() 374 { 375 return this._finishedOrFailedTimestamp - this._responseReceivedTimestamp; 376 }, 377 378 get cached() 379 { 380 return this._cached; 381 }, 382 383 get statusCode() 384 { 385 return this._statusCode; 386 }, 387 388 get statusText() 389 { 390 return this._statusText; 391 }, 392 393 get size() 394 { 395 return this._size; 396 }, 397 398 get encodedSize() 399 { 400 if (!isNaN(this._transferSize)) 401 return this._transferSize; 402 403 // If we did not receive actual transfer size from network 404 // stack, we prefer using Content-Length over resourceSize as 405 // resourceSize may differ from actual transfer size if platform's 406 // network stack performed decoding (e.g. gzip decompression). 407 // The Content-Length, though, is expected to come from raw 408 // response headers and will reflect actual transfer length. 409 // This won't work for chunked content encoding, so fall back to 410 // resourceSize when we don't have Content-Length. This still won't 411 // work for chunks with non-trivial encodings. We need a way to 412 // get actual transfer size from the network stack. 413 414 return Number(this._responseHeaders.valueForCaseInsensitiveKey("Content-Length") || this._size); 415 }, 416 417 get transferSize() 418 { 419 if (this.statusCode === 304) // Not modified 420 return this._responseHeadersSize; 421 422 if (this._cached) 423 return 0; 424 425 return this._responseHeadersSize + this.encodedSize; 426 }, 427 428 get compressed() 429 { 430 var contentEncoding = this._responseHeaders.valueForCaseInsensitiveKey("Content-Encoding"); 431 return contentEncoding && /\b(?:gzip|deflate)\b/.test(contentEncoding); 432 }, 433 434 get scripts() 435 { 436 return this._scripts || []; 437 }, 438 439 scriptForLocation: function(sourceCodeLocation) 440 { 441 console.assert(!(this instanceof WebInspector.SourceMapResource)); 442 console.assert(sourceCodeLocation.sourceCode === this, "SourceCodeLocation must be in this Resource"); 443 if (sourceCodeLocation.sourceCode !== this) 444 return null; 445 446 var lineNumber = sourceCodeLocation.lineNumber; 447 var columnNumber = sourceCodeLocation.columnNumber; 448 for (var i = 0; i < this._scripts.length; ++i) { 449 var script = this._scripts[i]; 450 if (script.range.startLine <= lineNumber && script.range.endLine >= lineNumber) { 451 if (script.range.startLine === lineNumber && columnNumber < script.range.startColumn) 452 continue; 453 if (script.range.endLine === lineNumber && columnNumber > script.range.endColumn) 454 continue; 455 return script; 456 } 457 } 458 459 return null; 460 }, 461 462 updateForRedirectResponse: function(url, requestHeaders, timestamp) 463 { 464 console.assert(!this._finished); 465 console.assert(!this._failed); 466 console.assert(!this._canceled); 467 468 var oldURL = this._url; 469 470 this._url = url; 471 this._requestHeaders = requestHeaders || {}; 472 this._lastRedirectReceivedTimestamp = timestamp || NaN; 473 474 if (oldURL !== url) { 475 // Delete the URL components so the URL is re-parsed the next time it is requested. 476 delete this._urlComponents; 477 478 this.dispatchEventToListeners(WebInspector.Resource.Event.URLDidChange, {oldURL: oldURL}); 479 } 480 481 this.dispatchEventToListeners(WebInspector.Resource.Event.RequestHeadersDidChange); 482 this.dispatchEventToListeners(WebInspector.Resource.Event.TimestampsDidChange); 483 }, 484 485 updateForResponse: function(url, mimeType, type, responseHeaders, statusCode, statusText, timestamp) 486 { 487 console.assert(!this._finished); 488 console.assert(!this._failed); 489 console.assert(!this._canceled); 490 491 var oldURL = this._url; 492 var oldMIMEType = this._mimeType; 493 var oldType = this._type; 494 495 if (type in WebInspector.Resource.Type) 496 type = WebInspector.Resource.Type[type]; 497 498 this._url = url; 499 this._mimeType = mimeType; 500 this._type = type || WebInspector.Resource.Type.fromMIMEType(mimeType); 501 this._statusCode = statusCode; 502 this._statusText = statusText; 503 this._responseHeaders = responseHeaders || {}; 504 this._responseReceivedTimestamp = timestamp || NaN; 505 506 this._responseHeadersSize = String(this._statusCode).length + this._statusText.length + 12; // Extra length is for "HTTP/1.1 ", " ", and "\r\n". 507 for (var name in this._responseHeaders) 508 this._responseHeadersSize += name.length + this._responseHeaders[name].length + 4; // Extra length is for ": ", and "\r\n". 509 510 if (statusCode === 304 && !this._cached) 511 this.markAsCached(); 512 513 if (oldURL !== url) { 514 // Delete the URL components so the URL is re-parsed the next time it is requested. 515 delete this._urlComponents; 516 517 this.dispatchEventToListeners(WebInspector.Resource.Event.URLDidChange, {oldURL: oldURL}); 518 } 519 520 if (oldMIMEType !== mimeType) { 521 // Delete the MIME-type components so the MIME-type is re-parsed the next time it is requested. 522 delete this._mimeTypeComponents; 523 524 this.dispatchEventToListeners(WebInspector.Resource.Event.MIMETypeDidChange, {oldMIMEType: oldMIMEType}); 525 } 526 527 if (oldType !== type) 528 this.dispatchEventToListeners(WebInspector.Resource.Event.TypeDidChange, {oldType: oldType}); 529 530 console.assert(isNaN(this._size)); 531 console.assert(isNaN(this._transferSize)); 532 533 // The transferSize becomes 0 when status is 304 or Content-Length is available, so 534 // notify listeners of that change. 535 if (statusCode === 304 || this._responseHeaders.valueForCaseInsensitiveKey("Content-Length")) 536 this.dispatchEventToListeners(WebInspector.Resource.Event.TransferSizeDidChange); 537 538 this.dispatchEventToListeners(WebInspector.Resource.Event.ResponseReceived); 539 this.dispatchEventToListeners(WebInspector.Resource.Event.TimestampsDidChange); 540 }, 541 542 canRequestContentFromBackend: function() 543 { 544 return this._finished; 545 }, 546 547 requestContentFromBackend: function(callback) 548 { 549 // If we have the requestIdentifier we can get the actual response for this specific resource. 550 // Otherwise the content will be cached resource data, which might not exist anymore. 551 if (this._requestIdentifier) { 552 NetworkAgent.getResponseBody(this._requestIdentifier, callback); 553 return true; 554 } 555 556 if (this._parentFrame) { 557 PageAgent.getResourceContent(this._parentFrame.id, this._url, callback); 558 return true; 559 } 560 561 // There is no request identifier or frame to request content from. Return false to cause the 562 // pending callbacks to get null content. 563 return false; 564 }, 565 566 increaseSize: function(dataLength, timestamp) 567 { 568 console.assert(dataLength >= 0); 569 570 if (isNaN(this._size)) 571 this._size = 0; 572 573 var previousSize = this._size; 574 575 this._size += dataLength; 576 577 this._lastDataReceivedTimestamp = timestamp || NaN; 578 579 this.dispatchEventToListeners(WebInspector.Resource.Event.SizeDidChange, {previousSize: previousSize}); 580 581 // The transferSize is based off of size when status is not 304 or Content-Length is missing. 582 if (isNaN(this._transferSize) && this._statusCode !== 304 && !this._responseHeaders.valueForCaseInsensitiveKey("Content-Length")) 583 this.dispatchEventToListeners(WebInspector.Resource.Event.TransferSizeDidChange); 584 }, 585 586 increaseTransferSize: function(encodedDataLength) 587 { 588 console.assert(encodedDataLength >= 0); 589 590 if (isNaN(this._transferSize)) 591 this._transferSize = 0; 592 this._transferSize += encodedDataLength; 593 594 this.dispatchEventToListeners(WebInspector.Resource.Event.TransferSizeDidChange); 595 }, 596 597 markAsCached: function() 598 { 599 this._cached = true; 600 601 this.dispatchEventToListeners(WebInspector.Resource.Event.CacheStatusDidChange); 602 603 // The transferSize is starts returning 0 when cached is true, unless status is 304. 604 if (this._statusCode !== 304) 605 this.dispatchEventToListeners(WebInspector.Resource.Event.TransferSizeDidChange); 606 }, 607 608 markAsFinished: function(timestamp) 609 { 610 console.assert(!this._failed); 611 console.assert(!this._canceled); 612 613 this._finished = true; 614 this._finishedOrFailedTimestamp = timestamp || NaN; 615 616 this.dispatchEventToListeners(WebInspector.Resource.Event.LoadingDidFinish); 617 this.dispatchEventToListeners(WebInspector.Resource.Event.TimestampsDidChange); 618 619 if (this.canRequestContentFromBackend()) 620 this.requestContentFromBackendIfNeeded(); 621 }, 622 623 markAsFailed: function(canceled, timestamp) 624 { 625 console.assert(!this._finished); 626 627 this._failed = true; 628 this._canceled = canceled; 629 this._finishedOrFailedTimestamp = timestamp || NaN; 630 631 this.dispatchEventToListeners(WebInspector.Resource.Event.LoadingDidFail); 632 this.dispatchEventToListeners(WebInspector.Resource.Event.TimestampsDidChange); 633 634 // Force the content requests to be serviced. They will get null as the content. 635 this.servicePendingContentRequests(true); 636 }, 637 638 revertMarkAsFinished: function(timestamp) 639 { 640 console.assert(!this._failed); 641 console.assert(!this._canceled); 642 console.assert(this._finished); 643 644 this._finished = false; 645 this._finishedOrFailedTimestamp = NaN; 646 }, 647 648 getImageSize: function(callback) 649 { 650 // Throw an error in the case this resource is not an image. 651 if (this.type !== WebInspector.Resource.Type.Image) 652 throw "Resource is not an image."; 653 654 // See if we've already computed and cached the image size, 655 // in which case we can provide them directly. 656 if (this._imageSize) { 657 callback(this._imageSize); 658 return; 659 } 660 661 // Event handler for the image "load" event. 662 function imageDidLoad() 663 { 664 // Cache the image metrics. 665 this._imageSize = { 666 width: image.width, 667 height: image.height 668 }; 669 670 callback(this._imageSize); 671 }; 672 673 // Create an <img> element that we'll use to load the image resource 674 // so that we can query its intrinsic size. 675 var image = new Image; 676 image.addEventListener("load", imageDidLoad.bind(this), false); 677 678 // Set the image source once we've obtained the base64-encoded URL for it. 679 this.requestContent(function() { 680 image.src = this.contentURL; 681 }.bind(this)); 682 }, 683 684 associateWithScript: function(script) 685 { 686 if (!this._scripts) 687 this._scripts = [] 688 689 this._scripts.push(script); 690 691 // COMPATIBILITY (iOS 6): Resources did not know their type until a response 692 // was received. We can set the Resource type to be Script here. 693 if (this._type === WebInspector.Resource.Type.Other) { 694 var oldType = this._type; 695 this._type = WebInspector.Resource.Type.Script; 696 this.dispatchEventToListeners(WebInspector.Resource.Event.TypeDidChange, {oldType: oldType}); 697 } 698 }, 699 700 saveIdentityToCookie: function(cookie) 701 { 702 cookie[WebInspector.Resource.URLCookieKey] = this.url.hash; 703 cookie[WebInspector.Resource.MainResourceCookieKey] = this.isMainResource(); 704 } 705}; 706 707WebInspector.Resource.prototype.__proto__ = WebInspector.SourceCode.prototype; 708