Web Inspector: Fix ESLint semi (missing semicolon)
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Models / Resource.js
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
26 WebInspector.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
56 WebInspector.Object.addConstructorFunctions(WebInspector.Resource);
57
58 WebInspector.Resource.TypeIdentifier = "resource";
59 WebInspector.Resource.URLCookieKey = "resource-url";
60 WebInspector.Resource.MainResourceCookieKey = "resource-is-main-resource";
61
62 WebInspector.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.
77 WebInspector.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().
89 WebInspector.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
129 WebInspector.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
148 WebInspector.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
187 WebInspector.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
707 WebInspector.Resource.prototype.__proto__ = WebInspector.SourceCode.prototype;