2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
28 WebInspector.Resource = function(identifier, url)
30 this.identifier = identifier;
34 this._requestMethod = "";
35 this._category = WebInspector.resourceCategories.other;
36 this._pendingContentCallbacks = [];
39 // Keep these in sync with WebCore::InspectorResource::Type
40 WebInspector.Resource.Type = {
51 isTextType: function(type)
53 return (type === this.Document) || (type === this.Stylesheet) || (type === this.Script) || (type === this.XHR);
56 toUIString: function(type)
58 return WebInspector.UIString(WebInspector.Resource.Type.toString(type));
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)
89 WebInspector.Resource.prototype = {
101 delete this._parsedQueryParameters;
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);
114 // Then take last path component.
115 var lastSlashIndex = path.lastIndexOf("/");
116 if (lastSlashIndex !== -1)
117 this.lastPathComponent = path.substring(lastSlashIndex + 1);
119 this.lastPathComponentLowerCase = this.lastPathComponent.toLowerCase();
124 return this._documentURL;
129 this._documentURL = x;
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;
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)))
156 return this._startTime || -1;
164 get responseReceivedTime()
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;
171 return this._responseReceivedTime || -1;
174 set responseReceivedTime(x)
176 this._responseReceivedTime = x;
181 return this._endTime || -1;
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;
197 if (this._endTime === -1 || this._startTime === -1)
199 return this._endTime - this._startTime;
204 if (this._responseReceivedTime === -1 || this._startTime === -1)
206 return this._responseReceivedTime - this._startTime;
209 get receiveDuration()
211 if (this._endTime === -1 || this._responseReceivedTime === -1)
213 return this._endTime - this._responseReceivedTime;
218 return this._resourceSize || 0;
223 this._resourceSize = x;
228 // FIXME: this is wrong for chunked-encoding resources.
229 return this.cached ? 0 : Number(this.responseHeaders["Content-Length"] || this.resourceSize || 0);
232 get expectedContentLength()
234 return this._expectedContentLength || 0;
237 set expectedContentLength(x)
239 this._expectedContentLength = x;
244 return this._finished;
249 if (this._finished === x)
255 this._checkWarnings();
256 this.dispatchEventToListeners("finished");
257 if (this._pendingContentCallbacks.length)
258 this._innerRequestContent();
274 return this._category;
308 return this._mimeType;
323 if (this._type === x)
329 case WebInspector.Resource.Type.Document:
330 this.category = WebInspector.resourceCategories.documents;
332 case WebInspector.Resource.Type.Stylesheet:
333 this.category = WebInspector.resourceCategories.stylesheets;
335 case WebInspector.Resource.Type.Script:
336 this.category = WebInspector.resourceCategories.scripts;
338 case WebInspector.Resource.Type.Image:
339 this.category = WebInspector.resourceCategories.images;
341 case WebInspector.Resource.Type.Font:
342 this.category = WebInspector.resourceCategories.fonts;
344 case WebInspector.Resource.Type.XHR:
345 this.category = WebInspector.resourceCategories.xhr;
347 case WebInspector.Resource.Type.WebSocket:
348 this.category = WebInspector.resourceCategories.websockets;
350 case WebInspector.Resource.Type.Other:
352 this.category = WebInspector.resourceCategories.other;
359 return this._requestHeaders || {};
362 set requestHeaders(x)
364 this._requestHeaders = x;
365 delete this._sortedRequestHeaders;
366 delete this._requestCookies;
368 this.dispatchEventToListeners("requestHeaders changed");
371 get sortedRequestHeaders()
373 if (this._sortedRequestHeaders !== undefined)
374 return this._sortedRequestHeaders;
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) });
381 return this._sortedRequestHeaders;
384 requestHeaderValue: function(headerName)
386 return this._headerValue(this.requestHeaders, headerName);
391 if (!this._requestCookies)
392 this._requestCookies = WebInspector.CookieParser.parseCookie(this.requestHeaderValue("Cookie"));
393 return this._requestCookies;
396 get requestFormData()
398 return this._requestFormData;
401 set requestFormData(x)
403 this._requestFormData = x;
404 delete this._parsedFormParameters;
407 get responseHeaders()
409 return this._responseHeaders || {};
412 set responseHeaders(x)
414 this._responseHeaders = x;
415 delete this._sortedResponseHeaders;
416 delete this._responseCookies;
418 this.dispatchEventToListeners("responseHeaders changed");
421 get sortedResponseHeaders()
423 if (this._sortedResponseHeaders !== undefined)
424 return this._sortedResponseHeaders;
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) });
431 return this._sortedResponseHeaders;
434 responseHeaderValue: function(headerName)
436 return this._headerValue(this.responseHeaders, headerName);
439 get responseCookies()
441 if (!this._responseCookies)
442 this._responseCookies = WebInspector.CookieParser.parseSetCookie(this.responseHeaderValue("Set-Cookie"));
443 return this._responseCookies;
446 get queryParameters()
448 if (this._parsedQueryParameters)
449 return this._parsedQueryParameters;
450 var queryString = this.url.split("?", 2)[1];
453 this._parsedQueryParameters = this._parseParameters(queryString);
454 return this._parsedQueryParameters;
459 if (this._parsedFormParameters)
460 return this._parsedFormParameters;
461 if (!this.requestFormData)
463 var requestContentType = this.requestHeaderValue("Content-Type");
464 if (!requestContentType || !requestContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i))
466 this._parsedFormParameters = this._parseParameters(this.requestFormData);
467 return this._parsedFormParameters;
470 _parseParameters: function(queryString)
472 function parseNameValue(pair)
475 var splitPair = pair.split("=", 2);
477 parameter.name = splitPair[0];
478 if (splitPair.length === 1)
479 parameter.value = "";
481 parameter.value = splitPair[1];
484 return queryString.split("&").map(parseNameValue);
487 _headerValue: function(headers, headerName)
489 headerName = headerName.toLowerCase();
490 for (var header in headers) {
491 if (header.toLowerCase() === headerName)
492 return headers[header];
498 if (!("_scripts" in this))
500 return this._scripts;
503 addScript: function(script)
507 this.scripts.unshift(script);
508 script.resource = this;
511 removeAllScripts: function()
516 for (var i = 0; i < this._scripts.length; ++i) {
517 if (this._scripts[i].resource === this)
518 delete this._scripts[i].resource;
521 delete this._scripts;
524 removeScript: function(script)
529 if (script.resource === this)
530 delete script.resource;
535 this._scripts.remove(script);
540 return this._errors || 0;
546 this.dispatchEventToListeners("errors-warnings-updated");
551 return this._warnings || 0;
557 this.dispatchEventToListeners("errors-warnings-updated");
560 clearErrorsAndWarnings: function()
564 this.dispatchEventToListeners("errors-warnings-updated");
567 _mimeTypeIsConsistentWithType: function()
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)
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)
582 return true; // Might be not known for cached resources with null responses.
584 if (this.mimeType in WebInspector.MIMETypes)
585 return this.type in WebInspector.MIMETypes[this.mimeType];
590 _checkWarnings: function()
592 for (var warning in WebInspector.Warnings)
593 this._checkWarning(WebInspector.Warnings[warning]);
596 _checkWarning: function(warning)
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,
609 String.sprintf(WebInspector.Warnings.IncorrectMIMEType.message, WebInspector.Resource.Type.toUIString(this.type), this.mimeType),
616 WebInspector.console.addMessage(msg);
621 return this._content;
626 this._content = content;
629 requestContent: function(callback)
632 callback(this._content, this._contentEncoded);
635 this._pendingContentCallbacks.push(callback);
637 this._innerRequestContent();
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)
647 return "data:" + this.mimeType + (this._contentEncoded ? ";base64," : ",") + this._content;
650 _innerRequestContent: function()
652 if (this._contentRequested)
654 this._contentRequested = true;
655 this._contentEncoded = !WebInspector.Resource.Type.isTextType(this.type);
657 function onResourceContent(data)
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;
666 WebInspector.ResourceManager.requestContent(this, this._contentEncoded, onResourceContent.bind(this));
670 WebInspector.Resource.prototype.__proto__ = WebInspector.Object.prototype;