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