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