OSDN Git Service

Merge WebKit at r71558: Initial merge by git.
[android-x86/external-webkit.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 WebInspector.Resource = function(identifier, url)
29 {
30     this.identifier = identifier;
31     this.url = url;
32     this._startTime = -1;
33     this._endTime = -1;
34     this._requestMethod = "";
35     this._category = WebInspector.resourceCategories.other;
36     this._pendingContentCallbacks = [];
37 }
38
39 // Keep these in sync with WebCore::InspectorResource::Type
40 WebInspector.Resource.Type = {
41     Document:   0,
42     Stylesheet: 1,
43     Image:      2,
44     Font:       3,
45     Script:     4,
46     XHR:        5,
47     Media:      6,
48     WebSocket:  7,
49     Other:      8,
50
51     isTextType: function(type)
52     {
53         return (type === this.Document) || (type === this.Stylesheet) || (type === this.Script) || (type === this.XHR);
54     },
55
56     toUIString: function(type)
57     {
58         return WebInspector.UIString(WebInspector.Resource.Type.toString(type));
59     },
60
61     // Returns locale-independent string identifier of resource type (primarily for use in extension API).
62     // The IDs need to be kept in sync with webInspector.resoureces.Types object in ExtensionAPI.js.
63     toString: function(type)
64     {
65         switch (type) {
66             case this.Document:
67                 return "document";
68             case this.Stylesheet:
69                 return "stylesheet";
70             case this.Image:
71                 return "image";
72             case this.Font:
73                 return "font";
74             case this.Script:
75                 return "script";
76             case this.XHR:
77                 return "xhr";
78             case this.Media:
79                 return "media";
80             case this.WebSocket:
81                 return "websocket";
82             case this.Other:
83             default:
84                 return "other";
85         }
86     }
87 }
88
89 WebInspector.Resource.prototype = {
90     get url()
91     {
92         return this._url;
93     },
94
95     set url(x)
96     {
97         if (this._url === x)
98             return;
99
100         this._url = x;
101         delete this._parsedQueryParameters;
102
103         var parsedURL = x.asParsedURL();
104         this.domain = parsedURL ? parsedURL.host : "";
105         this.path = parsedURL ? parsedURL.path : "";
106         this.lastPathComponent = "";
107         if (parsedURL && parsedURL.path) {
108             // First cut the query params.
109             var path = parsedURL.path;
110             var indexOfQuery = path.indexOf("?");
111             if (indexOfQuery !== -1)
112                 path = path.substring(0, indexOfQuery);
113
114             // Then take last path component.
115             var lastSlashIndex = path.lastIndexOf("/");
116             if (lastSlashIndex !== -1)
117                 this.lastPathComponent = path.substring(lastSlashIndex + 1);
118         }
119         this.lastPathComponentLowerCase = this.lastPathComponent.toLowerCase();
120     },
121
122     get documentURL()
123     {
124         return this._documentURL;
125     },
126
127     set documentURL(x)
128     {
129         this._documentURL = x;
130     },
131
132     get displayName()
133     {
134         if (this._displayName)
135             return this._displayName;
136         this._displayName = this.lastPathComponent;
137         if (!this._displayName)
138             this._displayName = this.displayDomain;
139         if (!this._displayName && this.url)
140             this._displayName = this.url.trimURL(WebInspector.mainResource ? WebInspector.mainResource.domain : "");
141         if (this._displayName === "/")
142             this._displayName = this.url;
143         return this._displayName;
144     },
145
146     get displayDomain()
147     {
148         // WebInspector.Database calls this, so don't access more than this.domain.
149         if (this.domain && (!WebInspector.mainResource || (WebInspector.mainResource && this.domain !== WebInspector.mainResource.domain)))
150             return this.domain;
151         return "";
152     },
153
154     get startTime()
155     {
156         return this._startTime || -1;
157     },
158
159     set startTime(x)
160     {
161         this._startTime = x;
162     },
163
164     get responseReceivedTime()
165     {
166         if (this.timing && this.timing.requestTime) {
167             // Calculate responseReceivedTime from timing data for better accuracy.
168             // Timing's requestTime is a baseline in seconds, rest of the numbers there are ticks in millis.
169             return this.timing.requestTime + this.timing.receiveHeadersEnd / 1000.0;
170         }
171         return this._responseReceivedTime || -1;
172     },
173
174     set responseReceivedTime(x)
175     {
176         this._responseReceivedTime = x;
177     },
178
179     get endTime()
180     {
181         return this._endTime || -1;
182     },
183
184     set endTime(x)
185     {
186         // In case of fast load (or in case of cached resources), endTime on network stack
187         // can be less then m_responseReceivedTime measured in WebCore. Normalize it here,
188         // prefer actualEndTime to m_responseReceivedTime.
189         if (x < this.responseReceivedTime)
190             this.responseReceivedTime = x;
191
192         this._endTime = x;
193     },
194
195     get duration()
196     {
197         if (this._endTime === -1 || this._startTime === -1)
198             return -1;
199         return this._endTime - this._startTime;
200     },
201
202     get latency()
203     {
204         if (this._responseReceivedTime === -1 || this._startTime === -1)
205             return -1;
206         return this._responseReceivedTime - this._startTime;
207     },
208
209     get receiveDuration()
210     {
211         if (this._endTime === -1 || this._responseReceivedTime === -1)
212             return -1;
213         return this._endTime - this._responseReceivedTime;
214     },
215
216     get resourceSize()
217     {
218         return this._resourceSize || 0;
219     },
220
221     set resourceSize(x)
222     {
223         this._resourceSize = x;
224     },
225
226     get transferSize()
227     {
228         // FIXME: this is wrong for chunked-encoding resources.
229         return this.cached ? 0 : Number(this.responseHeaders["Content-Length"] || this.resourceSize || 0);
230     },
231
232     get expectedContentLength()
233     {
234         return this._expectedContentLength || 0;
235     },
236
237     set expectedContentLength(x)
238     {
239         this._expectedContentLength = x;
240     },
241
242     get finished()
243     {
244         return this._finished;
245     },
246
247     set finished(x)
248     {
249         if (this._finished === x)
250             return;
251
252         this._finished = x;
253
254         if (x) {
255             this._checkWarnings();
256             this.dispatchEventToListeners("finished");
257             if (this._pendingContentCallbacks.length)
258                 this._innerRequestContent();
259         }
260     },
261
262     get failed()
263     {
264         return this._failed;
265     },
266
267     set failed(x)
268     {
269         this._failed = x;
270     },
271
272     get category()
273     {
274         return this._category;
275     },
276
277     set category(x)
278     {
279         this._category = x;
280     },
281
282     get cached()
283     {
284         return this._cached;
285     },
286
287     set cached(x)
288     {
289         this._cached = x;
290         if (x)
291             delete this._timing;
292     },
293
294
295     get timing()
296     {
297         return this._timing;
298     },
299
300     set timing(x)
301     {
302         if (!this._cached)
303             this._timing = x;
304     },
305
306     get mimeType()
307     {
308         return this._mimeType;
309     },
310
311     set mimeType(x)
312     {
313         this._mimeType = x;
314     },
315
316     get type()
317     {
318         return this._type;
319     },
320
321     set type(x)
322     {
323         if (this._type === x)
324             return;
325
326         this._type = x;
327
328         switch (x) {
329             case WebInspector.Resource.Type.Document:
330                 this.category = WebInspector.resourceCategories.documents;
331                 break;
332             case WebInspector.Resource.Type.Stylesheet:
333                 this.category = WebInspector.resourceCategories.stylesheets;
334                 break;
335             case WebInspector.Resource.Type.Script:
336                 this.category = WebInspector.resourceCategories.scripts;
337                 break;
338             case WebInspector.Resource.Type.Image:
339                 this.category = WebInspector.resourceCategories.images;
340                 break;
341             case WebInspector.Resource.Type.Font:
342                 this.category = WebInspector.resourceCategories.fonts;
343                 break;
344             case WebInspector.Resource.Type.XHR:
345                 this.category = WebInspector.resourceCategories.xhr;
346                 break;
347             case WebInspector.Resource.Type.WebSocket:
348                 this.category = WebInspector.resourceCategories.websockets;
349                 break;
350             case WebInspector.Resource.Type.Other:
351             default:
352                 this.category = WebInspector.resourceCategories.other;
353                 break;
354         }
355     },
356
357     get requestHeaders()
358     {
359         return this._requestHeaders || {};
360     },
361
362     set requestHeaders(x)
363     {
364         this._requestHeaders = x;
365         delete this._sortedRequestHeaders;
366         delete this._requestCookies;
367
368         this.dispatchEventToListeners("requestHeaders changed");
369     },
370
371     get sortedRequestHeaders()
372     {
373         if (this._sortedRequestHeaders !== undefined)
374             return this._sortedRequestHeaders;
375
376         this._sortedRequestHeaders = [];
377         for (var key in this.requestHeaders)
378             this._sortedRequestHeaders.push({header: key, value: this.requestHeaders[key]});
379         this._sortedRequestHeaders.sort(function(a,b) { return a.header.localeCompare(b.header) });
380
381         return this._sortedRequestHeaders;
382     },
383
384     requestHeaderValue: function(headerName)
385     {
386         return this._headerValue(this.requestHeaders, headerName);
387     },
388
389     get requestCookies()
390     {
391         if (!this._requestCookies)
392             this._requestCookies = WebInspector.CookieParser.parseCookie(this.requestHeaderValue("Cookie"));
393         return this._requestCookies;
394     },
395
396     get requestFormData()
397     {
398         return this._requestFormData;
399     },
400
401     set requestFormData(x)
402     {
403         this._requestFormData = x;
404         delete this._parsedFormParameters;
405     },
406
407     get responseHeaders()
408     {
409         return this._responseHeaders || {};
410     },
411
412     set responseHeaders(x)
413     {
414         this._responseHeaders = x;
415         delete this._sortedResponseHeaders;
416         delete this._responseCookies;
417
418         this.dispatchEventToListeners("responseHeaders changed");
419     },
420
421     get sortedResponseHeaders()
422     {
423         if (this._sortedResponseHeaders !== undefined)
424             return this._sortedResponseHeaders;
425
426         this._sortedResponseHeaders = [];
427         for (var key in this.responseHeaders)
428             this._sortedResponseHeaders.push({header: key, value: this.responseHeaders[key]});
429         this._sortedResponseHeaders.sort(function(a,b) { return a.header.localeCompare(b.header) });
430
431         return this._sortedResponseHeaders;
432     },
433
434     responseHeaderValue: function(headerName)
435     {
436         return this._headerValue(this.responseHeaders, headerName);
437     },
438
439     get responseCookies()
440     {
441         if (!this._responseCookies)
442             this._responseCookies = WebInspector.CookieParser.parseSetCookie(this.responseHeaderValue("Set-Cookie"));
443         return this._responseCookies;
444     },
445
446     get queryParameters()
447     {
448         if (this._parsedQueryParameters)
449             return this._parsedQueryParameters;
450         var queryString = this.url.split("?", 2)[1];
451         if (!queryString)
452             return;
453         this._parsedQueryParameters = this._parseParameters(queryString);
454         return this._parsedQueryParameters;
455     },
456
457     get formParameters()
458     {
459         if (this._parsedFormParameters)
460             return this._parsedFormParameters;
461         if (!this.requestFormData)
462             return;
463         var requestContentType = this.requestHeaderValue("Content-Type");
464         if (!requestContentType || !requestContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i))
465             return;
466         this._parsedFormParameters = this._parseParameters(this.requestFormData);
467         return this._parsedFormParameters;
468     },
469
470     _parseParameters: function(queryString)
471     {
472         function parseNameValue(pair)
473         {
474             var parameter = {};
475             var splitPair = pair.split("=", 2);
476
477             parameter.name = splitPair[0];
478             if (splitPair.length === 1)
479                 parameter.value = "";
480             else
481                 parameter.value = splitPair[1];
482             return parameter;
483         }
484         return queryString.split("&").map(parseNameValue);
485     },
486
487     _headerValue: function(headers, headerName)
488     {
489         headerName = headerName.toLowerCase();
490         for (var header in headers) {
491             if (header.toLowerCase() === headerName)
492                 return headers[header];
493         }
494     },
495
496     get scripts()
497     {
498         if (!("_scripts" in this))
499             this._scripts = [];
500         return this._scripts;
501     },
502
503     addScript: function(script)
504     {
505         if (!script)
506             return;
507         this.scripts.unshift(script);
508         script.resource = this;
509     },
510
511     removeAllScripts: function()
512     {
513         if (!this._scripts)
514             return;
515
516         for (var i = 0; i < this._scripts.length; ++i) {
517             if (this._scripts[i].resource === this)
518                 delete this._scripts[i].resource;
519         }
520
521         delete this._scripts;
522     },
523
524     removeScript: function(script)
525     {
526         if (!script)
527             return;
528
529         if (script.resource === this)
530             delete script.resource;
531
532         if (!this._scripts)
533             return;
534
535         this._scripts.remove(script);
536     },
537
538     get errors()
539     {
540         return this._errors || 0;
541     },
542
543     set errors(x)
544     {
545         this._errors = x;
546         this.dispatchEventToListeners("errors-warnings-updated");
547     },
548
549     get warnings()
550     {
551         return this._warnings || 0;
552     },
553
554     set warnings(x)
555     {
556         this._warnings = x;
557         this.dispatchEventToListeners("errors-warnings-updated");
558     },
559
560     clearErrorsAndWarnings: function()
561     {
562         this._warnings = 0;
563         this._errors = 0;
564         this.dispatchEventToListeners("errors-warnings-updated");
565     },
566
567     _mimeTypeIsConsistentWithType: function()
568     {
569         // If status is an error, content is likely to be of an inconsistent type,
570         // as it's going to be an error message. We do not want to emit a warning
571         // for this, though, as this will already be reported as resource loading failure.
572         if (this.statusCode >= 400)
573             return true;
574
575         if (typeof this.type === "undefined"
576          || this.type === WebInspector.Resource.Type.Other
577          || this.type === WebInspector.Resource.Type.XHR
578          || this.type === WebInspector.Resource.Type.WebSocket)
579             return true;
580
581         if (!this.mimeType)
582             return true; // Might be not known for cached resources with null responses.
583
584         if (this.mimeType in WebInspector.MIMETypes)
585             return this.type in WebInspector.MIMETypes[this.mimeType];
586
587         return false;
588     },
589
590     _checkWarnings: function()
591     {
592         for (var warning in WebInspector.Warnings)
593             this._checkWarning(WebInspector.Warnings[warning]);
594     },
595
596     _checkWarning: function(warning)
597     {
598         var msg;
599         switch (warning.id) {
600             case WebInspector.Warnings.IncorrectMIMEType.id:
601                 if (!this._mimeTypeIsConsistentWithType())
602                     msg = new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.Other,
603                         WebInspector.ConsoleMessage.MessageType.Log,
604                         WebInspector.ConsoleMessage.MessageLevel.Warning,
605                         -1,
606                         this.url,
607                         null,
608                         1,
609                         String.sprintf(WebInspector.Warnings.IncorrectMIMEType.message, WebInspector.Resource.Type.toUIString(this.type), this.mimeType),
610                         null,
611                         null);
612                 break;
613         }
614
615         if (msg)
616             WebInspector.console.addMessage(msg);
617     },
618
619     get content()
620     {
621         return this._content;
622     },
623
624     set content(content)
625     {
626         this._content = content;
627     },
628
629     requestContent: function(callback)
630     {
631         if (this._content) {
632             callback(this._content, this._contentEncoded);
633             return;
634         }
635         this._pendingContentCallbacks.push(callback);
636         if (this.finished)
637             this._innerRequestContent();
638     },
639
640     get contentURL()
641     {
642         const maxDataUrlSize = 1024 * 1024;
643         // If resource content is not available or won't fit a data URL, fall back to using original URL.
644         if (!this._content || this._content.length > maxDataUrlSize)
645             return this.url;
646
647         return "data:" + this.mimeType + (this._contentEncoded ? ";base64," : ",") + this._content;
648     },
649
650     _innerRequestContent: function()
651     {
652         if (this._contentRequested)
653             return;
654         this._contentRequested = true;
655         this._contentEncoded = !WebInspector.Resource.Type.isTextType(this.type);
656
657         function onResourceContent(data)
658         {
659             this._content = data;
660             var callbacks = this._pendingContentCallbacks.slice();
661             for (var i = 0; i < callbacks.length; ++i)
662                 callbacks[i](this._content, this._contentEncoded);
663             this._pendingContentCallbacks.length = 0;
664             delete this._contentRequested;
665         }
666         WebInspector.ResourceManager.requestContent(this, this._contentEncoded, onResourceContent.bind(this));
667     }
668 }
669
670 WebInspector.Resource.prototype.__proto__ = WebInspector.Object.prototype;