728f2acd1e32b7662625e6f224f3ec19784cdee5
[WebKit-https.git] / WebCore / inspector / front-end / Resource.js
1 /*
2  * Copyright (C) 2007, 2008 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  *
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  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 WebInspector.Resource = function(requestHeaders, url, domain, path, lastPathComponent, identifier, mainResource, cached, requestMethod, requestFormData)
30 {
31     this.identifier = identifier;
32
33     this.startTime = -1;
34     this.endTime = -1;
35     this.mainResource = mainResource;
36     this.requestHeaders = requestHeaders;
37     this.url = url;
38     this.domain = domain;
39     this.path = path;
40     this.lastPathComponent = lastPathComponent;
41     this.cached = cached;
42     this.requestMethod = requestMethod || "";
43     this.requestFormData = requestFormData || "";
44
45     this.category = WebInspector.resourceCategories.other;
46 }
47
48
49 WebInspector.Resource.StatusText = {
50     100: "Continue",
51     101: "Switching Protocols",
52     102: "Processing (WebDav)",
53     200: "OK",
54     201: "Created",
55     202: "Accepted",
56     203: "Non-Authoritative Information",
57     204: "No Content",
58     205: "Reset Content",
59     206: "Partial Content",
60     207: "Multi-Status (WebDav)",
61     300: "Multiple Choices",
62     301: "Moved Permanently",
63     302: "Found",
64     303: "See Other",
65     304: "Not Modified",
66     305: "Use Proxy",
67     306: "Switch Proxy",
68     307: "Temporary",
69     400: "Bad Request",
70     401: "Unauthorized",
71     402: "Payment Required",
72     403: "Forbidden",
73     404: "Not Found",
74     405: "Method Not Allowed",
75     406: "Not Acceptable",
76     407: "Proxy Authentication Required",
77     408: "Request Timeout",
78     409: "Conflict",
79     410: "Gone",
80     411: "Length Required",
81     412: "Precondition Failed",
82     413: "Request Entity Too Large",
83     414: "Request-URI Too Long",
84     415: "Unsupported Media Type",
85     416: "Requested Range Not Satisfiable",
86     417: "Expectation Failed",
87     418: "I'm a teapot",
88     422: "Unprocessable Entity (WebDav)",
89     423: "Locked (WebDav)",
90     424: "Failed Dependency (WebDav)",
91     425: "Unordered Collection",
92     426: "Upgrade Required",
93     449: "Retry With",
94     500: "Internal Server Error",
95     501: "Not Implemented",
96     502: "Bad Gateway",
97     503: "Service Unavailable",
98     504: "Gateway Timeout",
99     505: "HTTP Version Not Supported",
100     506: "Variant Also Negotiates",
101     507: "Insufficient Storage (WebDav)",
102     509: "Bandwidth Limit Exceeded",
103     510: "Not Extended"
104 };
105
106 // Keep these in sync with WebCore::InspectorResource::Type
107 WebInspector.Resource.Type = {
108     Document:   0,
109     Stylesheet: 1,
110     Image:      2,
111     Font:       3,
112     Script:     4,
113     XHR:        5,
114     Media:      6,
115     Other:      7,
116
117     isTextType: function(type)
118     {
119         return (type === this.Document) || (type === this.Stylesheet) || (type === this.Script) || (type === this.XHR);
120     },
121
122     toString: function(type)
123     {
124         switch (type) {
125             case this.Document:
126                 return WebInspector.UIString("document");
127             case this.Stylesheet:
128                 return WebInspector.UIString("stylesheet");
129             case this.Image:
130                 return WebInspector.UIString("image");
131             case this.Font:
132                 return WebInspector.UIString("font");
133             case this.Script:
134                 return WebInspector.UIString("script");
135             case this.XHR:
136                 return WebInspector.UIString("XHR");
137             case this.Other:
138             default:
139                 return WebInspector.UIString("other");
140         }
141     }
142 }
143
144 WebInspector.Resource.prototype = {
145     get url()
146     {
147         return this._url;
148     },
149
150     set url(x)
151     {
152         if (this._url === x)
153             return;
154
155         var oldURL = this._url;
156         this._url = x;
157
158         // FIXME: We should make the WebInspector object listen for the "url changed" event.
159         // Then resourceURLChanged can be removed.
160         WebInspector.resourceURLChanged(this, oldURL);
161
162         this.dispatchEventToListeners("url changed");
163     },
164
165     get domain()
166     {
167         return this._domain;
168     },
169
170     set domain(x)
171     {
172         if (this._domain === x)
173             return;
174         this._domain = x;
175     },
176
177     get lastPathComponent()
178     {
179         return this._lastPathComponent;
180     },
181
182     set lastPathComponent(x)
183     {
184         if (this._lastPathComponent === x)
185             return;
186         this._lastPathComponent = x;
187         this._lastPathComponentLowerCase = x ? x.toLowerCase() : null;
188     },
189
190     get displayName()
191     {
192         var title = this.lastPathComponent;
193         if (!title)
194             title = this.displayDomain;
195         if (!title && this.url)
196             title = this.url.trimURL(WebInspector.mainResource ? WebInspector.mainResource.domain : "");
197         if (title === "/")
198             title = this.url;
199         return title;
200     },
201
202     get displayDomain()
203     {
204         // WebInspector.Database calls this, so don't access more than this.domain.
205         if (this.domain && (!WebInspector.mainResource || (WebInspector.mainResource && this.domain !== WebInspector.mainResource.domain)))
206             return this.domain;
207         return "";
208     },
209
210     get startTime()
211     {
212         return this._startTime || -1;
213     },
214
215     set startTime(x)
216     {
217         if (this._startTime === x)
218             return;
219
220         this._startTime = x;
221
222         if (WebInspector.panels.resources)
223             WebInspector.panels.resources.refreshResource(this);
224     },
225
226     get responseReceivedTime()
227     {
228         return this._responseReceivedTime || -1;
229     },
230
231     set responseReceivedTime(x)
232     {
233         if (this._responseReceivedTime === x)
234             return;
235
236         this._responseReceivedTime = x;
237
238         if (WebInspector.panels.resources)
239             WebInspector.panels.resources.refreshResource(this);
240     },
241
242     get endTime()
243     {
244         return this._endTime || -1;
245     },
246
247     set endTime(x)
248     {
249         if (this._endTime === x)
250             return;
251
252         this._endTime = x;
253
254         if (WebInspector.panels.resources)
255             WebInspector.panels.resources.refreshResource(this);
256     },
257
258     get duration()
259     {
260         if (this._endTime === -1 || this._startTime === -1)
261             return -1;
262         return this._endTime - this._startTime;
263     },
264
265     get latency()
266     {
267         if (this._responseReceivedTime === -1 || this._startTime === -1)
268             return -1;
269         return this._responseReceivedTime - this._startTime;
270     },
271
272     get contentLength()
273     {
274         return this._contentLength || 0;
275     },
276
277     set contentLength(x)
278     {
279         if (this._contentLength === x)
280             return;
281
282         this._contentLength = x;
283
284         if (WebInspector.panels.resources)
285             WebInspector.panels.resources.refreshResource(this);
286     },
287
288     get expectedContentLength()
289     {
290         return this._expectedContentLength || 0;
291     },
292
293     set expectedContentLength(x)
294     {
295         if (this._expectedContentLength === x)
296             return;
297         this._expectedContentLength = x;
298     },
299
300     get finished()
301     {
302         return this._finished;
303     },
304
305     set finished(x)
306     {
307         if (this._finished === x)
308             return;
309
310         this._finished = x;
311
312         if (x) {
313             this._checkTips();
314             this._checkWarnings();
315             this.dispatchEventToListeners("finished");
316         }
317     },
318
319     get failed()
320     {
321         return this._failed;
322     },
323
324     set failed(x)
325     {
326         this._failed = x;
327     },
328
329     get category()
330     {
331         return this._category;
332     },
333
334     set category(x)
335     {
336         if (this._category === x)
337             return;
338
339         var oldCategory = this._category;
340         if (oldCategory)
341             oldCategory.removeResource(this);
342
343         this._category = x;
344
345         if (this._category)
346             this._category.addResource(this);
347
348         if (WebInspector.panels.resources) {
349             WebInspector.panels.resources.refreshResource(this);
350             WebInspector.panels.resources.recreateViewForResourceIfNeeded(this);
351         }
352     },
353
354     get mimeType()
355     {
356         return this._mimeType;
357     },
358
359     set mimeType(x)
360     {
361         if (this._mimeType === x)
362             return;
363
364         this._mimeType = x;
365     },
366
367     get type()
368     {
369         return this._type;
370     },
371
372     set type(x)
373     {
374         if (this._type === x)
375             return;
376
377         this._type = x;
378
379         switch (x) {
380             case WebInspector.Resource.Type.Document:
381                 this.category = WebInspector.resourceCategories.documents;
382                 break;
383             case WebInspector.Resource.Type.Stylesheet:
384                 this.category = WebInspector.resourceCategories.stylesheets;
385                 break;
386             case WebInspector.Resource.Type.Script:
387                 this.category = WebInspector.resourceCategories.scripts;
388                 break;
389             case WebInspector.Resource.Type.Image:
390                 this.category = WebInspector.resourceCategories.images;
391                 break;
392             case WebInspector.Resource.Type.Font:
393                 this.category = WebInspector.resourceCategories.fonts;
394                 break;
395             case WebInspector.Resource.Type.XHR:
396                 this.category = WebInspector.resourceCategories.xhr;
397                 break;
398             case WebInspector.Resource.Type.Other:
399             default:
400                 this.category = WebInspector.resourceCategories.other;
401                 break;
402         }
403     },
404
405     get requestHeaders()
406     {
407         if (this._requestHeaders === undefined)
408             this._requestHeaders = {};
409         return this._requestHeaders;
410     },
411
412     set requestHeaders(x)
413     {
414         if (this._requestHeaders === x)
415             return;
416
417         this._requestHeaders = x;
418         delete this._sortedRequestHeaders;
419
420         this.dispatchEventToListeners("requestHeaders changed");
421     },
422
423     get sortedRequestHeaders()
424     {
425         if (this._sortedRequestHeaders !== undefined)
426             return this._sortedRequestHeaders;
427
428         this._sortedRequestHeaders = [];
429         for (var key in this.requestHeaders)
430             this._sortedRequestHeaders.push({header: key, value: this.requestHeaders[key]});
431         this._sortedRequestHeaders.sort(function(a,b) { return a.header.localeCompare(b.header) });
432
433         return this._sortedRequestHeaders;
434     },
435
436     get responseHeaders()
437     {
438         if (this._responseHeaders === undefined)
439             this._responseHeaders = {};
440         return this._responseHeaders;
441     },
442
443     set responseHeaders(x)
444     {
445         if (this._responseHeaders === x)
446             return;
447
448         this._responseHeaders = x;
449         delete this._sortedResponseHeaders;
450
451         this.dispatchEventToListeners("responseHeaders changed");
452     },
453
454     get sortedResponseHeaders()
455     {
456         if (this._sortedResponseHeaders !== undefined)
457             return this._sortedResponseHeaders;
458
459         this._sortedResponseHeaders = [];
460         for (var key in this.responseHeaders)
461             this._sortedResponseHeaders.push({header: key, value: this.responseHeaders[key]});
462         this._sortedResponseHeaders.sort(function(a,b) { return a.header.localeCompare(b.header) });
463
464         return this._sortedResponseHeaders;
465     },
466
467     get scripts()
468     {
469         if (!("_scripts" in this))
470             this._scripts = [];
471         return this._scripts;
472     },
473
474     addScript: function(script)
475     {
476         if (!script)
477             return;
478         this.scripts.unshift(script);
479         script.resource = this;
480     },
481
482     removeAllScripts: function()
483     {
484         if (!this._scripts)
485             return;
486
487         for (var i = 0; i < this._scripts.length; ++i) {
488             if (this._scripts[i].resource === this)
489                 delete this._scripts[i].resource;
490         }
491
492         delete this._scripts;
493     },
494
495     removeScript: function(script)
496     {
497         if (!script)
498             return;
499
500         if (script.resource === this)
501             delete script.resource;
502
503         if (!this._scripts)
504             return;
505
506         this._scripts.remove(script);
507     },
508
509     get errors()
510     {
511         return this._errors || 0;
512     },
513
514     set errors(x)
515     {
516         this._errors = x;
517     },
518
519     get warnings()
520     {
521         return this._warnings || 0;
522     },
523
524     set warnings(x)
525     {
526         this._warnings = x;
527     },
528
529     get tips()
530     {
531         if (!("_tips" in this))
532             this._tips = {};
533         return this._tips;
534     },
535
536     _addTip: function(tip)
537     {
538         if (tip.id in this.tips)
539             return;
540
541         this.tips[tip.id] = tip;
542
543         // FIXME: Re-enable this code once we have a scope bar in the Console.
544         // Otherwise, we flood the Console with too many tips.
545         /*
546         var msg = new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.Other,
547             WebInspector.ConsoleMessage.MessageType.Log, WebInspector.ConsoleMessage.MessageLevel.Tip, 
548             -1, this.url, null, 1, tip.message);
549         WebInspector.console.addMessage(msg);
550         */
551     },
552
553     _checkTips: function()
554     {
555         for (var tip in WebInspector.Tips)
556             this._checkTip(WebInspector.Tips[tip]);
557     },
558
559     _checkTip: function(tip)
560     {
561         var addTip = false;
562         switch (tip.id) {
563             case WebInspector.Tips.ResourceNotCompressed.id:
564                 addTip = this._shouldCompress();
565                 break;
566         }
567
568         if (addTip)
569             this._addTip(tip);
570     },
571
572     _shouldCompress: function()
573     {
574         return WebInspector.Resource.Type.isTextType(this.type)
575             && this.domain
576             && !("Content-Encoding" in this.responseHeaders)
577             && this.contentLength !== undefined
578             && this.contentLength >= 512;
579     },
580
581     _mimeTypeIsConsistentWithType: function()
582     {
583         if (typeof this.type === "undefined"
584          || this.type === WebInspector.Resource.Type.Other
585          || this.type === WebInspector.Resource.Type.XHR)
586             return true;
587
588         if (this.mimeType in WebInspector.MIMETypes)
589             return this.type in WebInspector.MIMETypes[this.mimeType];
590
591         return false;
592     },
593
594     _checkWarnings: function()
595     {
596         for (var warning in WebInspector.Warnings)
597             this._checkWarning(WebInspector.Warnings[warning]);
598     },
599
600     _checkWarning: function(warning)
601     {
602         var msg;
603         switch (warning.id) {
604             case WebInspector.Warnings.IncorrectMIMEType.id:
605                 if (!this._mimeTypeIsConsistentWithType())
606                     msg = new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.Other,
607                         WebInspector.ConsoleMessage.MessageType.Log, 
608                         WebInspector.ConsoleMessage.MessageLevel.Warning, -1, this.url, null, 1,
609                         String.sprintf(WebInspector.Warnings.IncorrectMIMEType.message,
610                         WebInspector.Resource.Type.toString(this.type), this.mimeType));
611                 break;
612         }
613
614         if (msg)
615             WebInspector.console.addMessage(msg);
616     }
617 }
618
619 WebInspector.Resource.prototype.__proto__ = WebInspector.Object.prototype;
620
621 WebInspector.Resource.CompareByStartTime = function(a, b)
622 {
623     if (a.startTime < b.startTime)
624         return -1;
625     if (a.startTime > b.startTime)
626         return 1;
627     return 0;
628 }
629
630 WebInspector.Resource.CompareByResponseReceivedTime = function(a, b)
631 {
632     if (a.responseReceivedTime === -1 && b.responseReceivedTime !== -1)
633         return 1;
634     if (a.responseReceivedTime !== -1 && b.responseReceivedTime === -1)
635         return -1;
636     if (a.responseReceivedTime < b.responseReceivedTime)
637         return -1;
638     if (a.responseReceivedTime > b.responseReceivedTime)
639         return 1;
640     return 0;
641 }
642
643 WebInspector.Resource.CompareByEndTime = function(a, b)
644 {
645     if (a.endTime === -1 && b.endTime !== -1)
646         return 1;
647     if (a.endTime !== -1 && b.endTime === -1)
648         return -1;
649     if (a.endTime < b.endTime)
650         return -1;
651     if (a.endTime > b.endTime)
652         return 1;
653     return 0;
654 }
655
656 WebInspector.Resource.CompareByDuration = function(a, b)
657 {
658     if (a.duration < b.duration)
659         return -1;
660     if (a.duration > b.duration)
661         return 1;
662     return 0;
663 }
664
665 WebInspector.Resource.CompareByLatency = function(a, b)
666 {
667     if (a.latency < b.latency)
668         return -1;
669     if (a.latency > b.latency)
670         return 1;
671     return 0;
672 }
673
674 WebInspector.Resource.CompareBySize = function(a, b)
675 {
676     if (a.contentLength < b.contentLength)
677         return -1;
678     if (a.contentLength > b.contentLength)
679         return 1;
680     return 0;
681 }
682
683 WebInspector.Resource.StatusTextForCode = function(code)
684 {
685     return code ? code + " " + WebInspector.Resource.StatusText[code] : "";
686 }