Web Inspector: REGRESSION: CSS Resource appears as empty after editing it via Styles...
[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.typeFromMIMEType(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.typeFromMIMEType().
89 WebInspector.Resource._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.typeFromMIMEType = 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._mimeTypeMap)
137         return WebInspector.Resource._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.displayNameForType = 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     __proto__: WebInspector.SourceCode.prototype,
190
191     // Public
192
193     get url()
194     {
195         return this._url;
196     },
197
198     get urlComponents()
199     {
200         if (!this._urlComponents)
201             this._urlComponents = parseURL(this._url);
202         return this._urlComponents;
203     },
204
205     get displayName()
206     {
207         return WebInspector.displayNameForURL(this._url, this.urlComponents);
208     },
209
210     get initiatorSourceCodeLocation()
211     {
212         return this._initiatorSourceCodeLocation;
213     },
214
215     get type()
216     {
217         return this._type;
218     },
219
220     get mimeType()
221     {
222         return this._mimeType;
223     },
224
225     get mimeTypeComponents()
226     {
227         if (!this._mimeTypeComponents)
228             this._mimeTypeComponents = parseMIMEType(this._mimeType);
229         return this._mimeTypeComponents;
230     },
231
232     get syntheticMIMEType()
233     {
234         // Resources are often transferred with a MIME-type that doesn't match the purpose the
235         // resource was loaded for, which is what WebInspector.Resource.Type represents.
236         // This getter generates a MIME-type, if needed, that matches the resource type.
237
238         // If the type matches the Resource.Type of the MIME-type, then return the actual MIME-type.
239         if (this._type === WebInspector.Resource.typeFromMIMEType(this._mimeType))
240             return this._mimeType;
241
242         // Return the default MIME-types for the Resource.Type, since the current MIME-type
243         // does not match what is expected for the Resource.Type.
244         switch (this._type) {
245         case WebInspector.Resource.Type.Document:
246             return "text/html";
247         case WebInspector.Resource.Type.Stylesheet:
248             return "text/css";
249         case WebInspector.Resource.Type.Script:
250             return "text/javascript";
251         }
252
253         // Return the actual MIME-type since we don't have a better synthesized one to return.
254         return this._mimeType;
255     },
256
257     get contentURL()
258     {
259         const maximumDataURLSize = 1024 * 1024; // 1 MiB
260
261         // If content is not available or won't fit a data URL, fallback to using original URL.
262         var content = this.content;
263         if (content === null || content.length > maximumDataURLSize)
264             return this._url;
265
266         return "data:" + this.mimeTypeComponents.type + (this.contentIsBase64Encoded ? ";base64," + content : "," + encodeURIComponent(content));
267     },
268
269     isMainResource: function()
270     {
271         return this._parentFrame ? this._parentFrame.mainResource === this : false;
272     },
273
274     get parentFrame()
275     {
276         return this._parentFrame;
277     },
278
279     get loaderIdentifier()
280     {
281         return this._loaderIdentifier;
282     },
283
284     get requestIdentifier()
285     {
286         return this._requestIdentifier;
287     },
288
289     get finished()
290     {
291         return this._finished;
292     },
293
294     get failed()
295     {
296         return this._failed;
297     },
298
299     get canceled()
300     {
301         return this._canceled;
302     },
303
304     get requestMethod()
305     {
306         return this._requestMethod;
307     },
308
309     get requestData()
310     {
311         return this._requestData;
312     },
313
314     get requestDataContentType()
315     {
316         return this._requestHeaders.valueForCaseInsensitiveKey("Content-Type") || null;
317     },
318
319     get requestHeaders()
320     {
321         return this._requestHeaders;
322     },
323
324     get responseHeaders()
325     {
326         return this._responseHeaders;
327     },
328
329     get requestSentTimestamp()
330     {
331         return this._requestSentTimestamp;
332     },
333
334     get lastRedirectReceivedTimestamp()
335     {
336         return this._lastRedirectReceivedTimestamp;
337     },
338
339     get responseReceivedTimestamp()
340     {
341         return this._responseReceivedTimestamp;
342     },
343
344     get lastDataReceivedTimestamp()
345     {
346         return this._lastDataReceivedTimestamp;
347     },
348
349     get finishedOrFailedTimestamp()
350     {
351         return this._finishedOrFailedTimestamp;
352     },
353
354     get firstTimestamp()
355     {
356         return this.requestSentTimestamp || this.lastRedirectReceivedTimestamp || this.responseReceivedTimestamp || this.lastDataReceivedTimestamp || this.finishedOrFailedTimestamp;
357     },
358
359     get lastTimestamp()
360     {
361         return this.finishedOrFailedTimestamp || this.lastDataReceivedTimestamp || this.responseReceivedTimestamp || this.lastRedirectReceivedTimestamp || this.requestSentTimestamp;
362     },
363
364     get duration()
365     {
366         return this._finishedOrFailedTimestamp - this._requestSentTimestamp;
367     },
368
369     get latency()
370     {
371         return this._responseReceivedTimestamp - this._requestSentTimestamp;
372     },
373
374     get receiveDuration()
375     {
376         return this._finishedOrFailedTimestamp - this._responseReceivedTimestamp;
377     },
378
379     get cached()
380     {
381         return this._cached;
382     },
383
384     get statusCode()
385     {
386         return this._statusCode;
387     },
388
389     get statusText()
390     {
391         return this._statusText;
392     },
393
394     get size()
395     {
396         return this._size;
397     },
398
399     get encodedSize()
400     {
401         if (!isNaN(this._transferSize))
402             return this._transferSize;
403
404         // If we did not receive actual transfer size from network
405         // stack, we prefer using Content-Length over resourceSize as
406         // resourceSize may differ from actual transfer size if platform's
407         // network stack performed decoding (e.g. gzip decompression).
408         // The Content-Length, though, is expected to come from raw
409         // response headers and will reflect actual transfer length.
410         // This won't work for chunked content encoding, so fall back to
411         // resourceSize when we don't have Content-Length. This still won't
412         // work for chunks with non-trivial encodings. We need a way to
413         // get actual transfer size from the network stack.
414
415         return Number(this._responseHeaders.valueForCaseInsensitiveKey("Content-Length") || this._size);
416     },
417
418     get transferSize()
419     {
420         if (this.statusCode === 304) // Not modified
421             return this._responseHeadersSize;
422
423         if (this._cached)
424             return 0;
425
426         return this._responseHeadersSize + this.encodedSize;
427     },
428
429     get compressed()
430     {
431         var contentEncoding = this._responseHeaders.valueForCaseInsensitiveKey("Content-Encoding");
432         return contentEncoding && /\b(?:gzip|deflate)\b/.test(contentEncoding);
433     },
434
435     get scripts()
436     {
437         return this._scripts || [];
438     },
439
440     scriptForLocation: function(sourceCodeLocation)
441     {
442         console.assert(!(this instanceof WebInspector.SourceMapResource));
443         console.assert(sourceCodeLocation.sourceCode === this, "SourceCodeLocation must be in this Resource");
444         if (sourceCodeLocation.sourceCode !== this)
445             return null;
446
447         var lineNumber = sourceCodeLocation.lineNumber;
448         var columnNumber = sourceCodeLocation.columnNumber;
449         for (var i = 0; i < this._scripts.length; ++i) {
450             var script = this._scripts[i];
451             if (script.range.startLine <= lineNumber && script.range.endLine >= lineNumber) {
452                 if (script.range.startLine === lineNumber && columnNumber < script.range.startColumn)
453                     continue;
454                 if (script.range.endLine === lineNumber && columnNumber > script.range.endColumn)
455                     continue;
456                 return script;
457             }
458         }
459
460         return null;
461     },
462
463     updateForRedirectResponse: function(url, requestHeaders, elapsedTime)
464     {
465         console.assert(!this._finished);
466         console.assert(!this._failed);
467         console.assert(!this._canceled);
468
469         var oldURL = this._url;
470
471         this._url = url;
472         this._requestHeaders = requestHeaders || {};
473         this._lastRedirectReceivedTimestamp = elapsedTime || NaN;
474
475         if (oldURL !== url) {
476             // Delete the URL components so the URL is re-parsed the next time it is requested.
477             delete this._urlComponents;
478
479             this.dispatchEventToListeners(WebInspector.Resource.Event.URLDidChange, {oldURL: oldURL});
480         }
481
482         this.dispatchEventToListeners(WebInspector.Resource.Event.RequestHeadersDidChange);
483         this.dispatchEventToListeners(WebInspector.Resource.Event.TimestampsDidChange);
484     },
485
486     updateForResponse: function(url, mimeType, type, responseHeaders, statusCode, statusText, elapsedTime)
487     {
488         console.assert(!this._finished);
489         console.assert(!this._failed);
490         console.assert(!this._canceled);
491
492         var oldURL = this._url;
493         var oldMIMEType = this._mimeType;
494         var oldType = this._type;
495
496         if (type in WebInspector.Resource.Type)
497             type = WebInspector.Resource.Type[type];
498
499         this._url = url;
500         this._mimeType = mimeType;
501         this._type = type || WebInspector.Resource.typeFromMIMEType(mimeType);
502         this._statusCode = statusCode;
503         this._statusText = statusText;
504         this._responseHeaders = responseHeaders || {};
505         this._responseReceivedTimestamp = elapsedTime || NaN;
506
507         this._responseHeadersSize = String(this._statusCode).length + this._statusText.length + 12; // Extra length is for "HTTP/1.1 ", " ", and "\r\n".
508         for (var name in this._responseHeaders)
509             this._responseHeadersSize += name.length + this._responseHeaders[name].length + 4; // Extra length is for ": ", and "\r\n".
510
511         if (statusCode === 304 && !this._cached)
512             this.markAsCached();
513
514         if (oldURL !== url) {
515             // Delete the URL components so the URL is re-parsed the next time it is requested.
516             delete this._urlComponents;
517
518             this.dispatchEventToListeners(WebInspector.Resource.Event.URLDidChange, {oldURL: oldURL});
519         }
520
521         if (oldMIMEType !== mimeType) {
522             // Delete the MIME-type components so the MIME-type is re-parsed the next time it is requested.
523             delete this._mimeTypeComponents;
524
525             this.dispatchEventToListeners(WebInspector.Resource.Event.MIMETypeDidChange, {oldMIMEType: oldMIMEType});
526         }
527
528         if (oldType !== type)
529             this.dispatchEventToListeners(WebInspector.Resource.Event.TypeDidChange, {oldType: oldType});
530
531         console.assert(isNaN(this._size));
532         console.assert(isNaN(this._transferSize));
533
534         // The transferSize becomes 0 when status is 304 or Content-Length is available, so
535         // notify listeners of that change.
536         if (statusCode === 304 || this._responseHeaders.valueForCaseInsensitiveKey("Content-Length"))
537             this.dispatchEventToListeners(WebInspector.Resource.Event.TransferSizeDidChange);
538
539         this.dispatchEventToListeners(WebInspector.Resource.Event.ResponseReceived);
540         this.dispatchEventToListeners(WebInspector.Resource.Event.TimestampsDidChange);
541     },
542
543     canRequestContent: function()
544     {
545         return this._finished;
546     },
547
548     requestContentFromBackend: function()
549     {
550         // If we have the requestIdentifier we can get the actual response for this specific resource.
551         // Otherwise the content will be cached resource data, which might not exist anymore.
552         if (this._requestIdentifier)
553             return NetworkAgent.getResponseBody.promise(this._requestIdentifier);
554
555         // There is no request identifier or frame to request content from.
556         if (this._parentFrame)
557             return PageAgent.getResourceContent.promise(this._parentFrame.id, this._url);
558
559         return Promise.reject(new Error("Content request failed."));
560     },
561
562     increaseSize: function(dataLength, elapsedTime)
563     {
564         console.assert(dataLength >= 0);
565
566         if (isNaN(this._size))
567             this._size = 0;
568
569         var previousSize = this._size;
570
571         this._size += dataLength;
572
573         this._lastDataReceivedTimestamp = elapsedTime || NaN;
574
575         this.dispatchEventToListeners(WebInspector.Resource.Event.SizeDidChange, {previousSize: previousSize});
576
577         // The transferSize is based off of size when status is not 304 or Content-Length is missing.
578         if (isNaN(this._transferSize) && this._statusCode !== 304 && !this._responseHeaders.valueForCaseInsensitiveKey("Content-Length"))
579             this.dispatchEventToListeners(WebInspector.Resource.Event.TransferSizeDidChange);
580     },
581
582     increaseTransferSize: function(encodedDataLength)
583     {
584         console.assert(encodedDataLength >= 0);
585
586         if (isNaN(this._transferSize))
587             this._transferSize = 0;
588         this._transferSize += encodedDataLength;
589
590         this.dispatchEventToListeners(WebInspector.Resource.Event.TransferSizeDidChange);
591     },
592
593     markAsCached: function()
594     {
595         this._cached = true;
596
597         this.dispatchEventToListeners(WebInspector.Resource.Event.CacheStatusDidChange);
598
599         // The transferSize is starts returning 0 when cached is true, unless status is 304.
600         if (this._statusCode !== 304)
601             this.dispatchEventToListeners(WebInspector.Resource.Event.TransferSizeDidChange);
602     },
603
604     markAsFinished: function(elapsedTime)
605     {
606         console.assert(!this._failed);
607         console.assert(!this._canceled);
608
609         this._finished = true;
610         this._finishedOrFailedTimestamp = elapsedTime || NaN;
611
612         if (this._finishThenRequestContentPromise)
613             delete this._finishThenRequestContentPromise;
614
615         this.dispatchEventToListeners(WebInspector.Resource.Event.LoadingDidFinish);
616         this.dispatchEventToListeners(WebInspector.Resource.Event.TimestampsDidChange);
617     },
618
619     markAsFailed: function(canceled, elapsedTime)
620     {
621         console.assert(!this._finished);
622
623         this._failed = true;
624         this._canceled = canceled;
625         this._finishedOrFailedTimestamp = elapsedTime || NaN;
626
627         this.dispatchEventToListeners(WebInspector.Resource.Event.LoadingDidFail);
628         this.dispatchEventToListeners(WebInspector.Resource.Event.TimestampsDidChange);
629     },
630
631     revertMarkAsFinished: function()
632     {
633         console.assert(!this._failed);
634         console.assert(!this._canceled);
635         console.assert(this._finished);
636
637         this._finished = false;
638         this._finishedOrFailedTimestamp = NaN;
639     },
640
641     getImageSize: function(callback)
642     {
643         // Throw an error in the case this resource is not an image.
644         if (this.type !== WebInspector.Resource.Type.Image)
645             throw "Resource is not an image.";
646
647         // See if we've already computed and cached the image size,
648         // in which case we can provide them directly.
649         if (this._imageSize) {
650             callback(this._imageSize);
651             return;
652         }
653
654         // Event handler for the image "load" event.
655         function imageDidLoad()
656         {
657             // Cache the image metrics.
658             this._imageSize = {
659                 width: image.width,
660                 height: image.height
661             };
662
663             callback(this._imageSize);
664         }
665
666         // Create an <img> element that we'll use to load the image resource
667         // so that we can query its intrinsic size.
668         var image = new Image;
669         image.addEventListener("load", imageDidLoad.bind(this), false);
670
671         // Set the image source once we've obtained the base64-encoded URL for it.
672         this.requestContent().then(function(content) {
673             image.src = content.sourceCode.contentURL;
674         });
675     },
676
677     requestContent: function()
678     {
679         if (this._finished)
680             return WebInspector.SourceCode.prototype.requestContent.call(this);
681
682         if (this._failed)
683             return Promise.resolve({error: WebInspector.UIString("An error occurred trying to load the resource.")});
684
685         if (!this._finishThenRequestContentPromise) {
686             this._finishThenRequestContentPromise = new Promise(function (resolve, reject) {
687                 this.addEventListener(WebInspector.Resource.Event.LoadingDidFinish, resolve);
688                 this.addEventListener(WebInspector.Resource.Event.LoadingDidFail, reject);
689             }.bind(this)).then(WebInspector.SourceCode.prototype.requestContent.bind(this));
690         }
691
692         return this._finishThenRequestContentPromise;
693     },
694
695     associateWithScript: function(script)
696     {
697         if (!this._scripts)
698             this._scripts = [];
699
700         this._scripts.push(script);
701
702         // COMPATIBILITY (iOS 6): Resources did not know their type until a response
703         // was received. We can set the Resource type to be Script here.
704         if (this._type === WebInspector.Resource.Type.Other) {
705             var oldType = this._type;
706             this._type = WebInspector.Resource.Type.Script;
707             this.dispatchEventToListeners(WebInspector.Resource.Event.TypeDidChange, {oldType: oldType});
708         }
709     },
710
711     saveIdentityToCookie: function(cookie)
712     {
713         cookie[WebInspector.Resource.URLCookieKey] = this.url.hash;
714         cookie[WebInspector.Resource.MainResourceCookieKey] = this.isMainResource();
715     }
716 };