/* * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ WebInspector.Resource = function(identifier, url) { this.identifier = identifier; this.url = url; this._startTime = -1; this._endTime = -1; this._requestMethod = ""; this._category = WebInspector.resourceCategories.other; this._pendingContentCallbacks = []; } // Keep these in sync with WebCore::InspectorResource::Type WebInspector.Resource.Type = { Document: 0, Stylesheet: 1, Image: 2, Font: 3, Script: 4, XHR: 5, Media: 6, WebSocket: 7, Other: 8, isTextType: function(type) { return (type === this.Document) || (type === this.Stylesheet) || (type === this.Script) || (type === this.XHR); }, toUIString: function(type) { return WebInspector.UIString(WebInspector.Resource.Type.toString(type)); }, // Returns locale-independent string identifier of resource type (primarily for use in extension API). // The IDs need to be kept in sync with webInspector.resoureces.Types object in ExtensionAPI.js. toString: function(type) { switch (type) { case this.Document: return "document"; case this.Stylesheet: return "stylesheet"; case this.Image: return "image"; case this.Font: return "font"; case this.Script: return "script"; case this.XHR: return "xhr"; case this.Media: return "media"; case this.WebSocket: return "websocket"; case this.Other: default: return "other"; } } } WebInspector.Resource.prototype = { get url() { return this._url; }, set url(x) { if (this._url === x) return; this._url = x; delete this._parsedQueryParameters; var parsedURL = x.asParsedURL(); this.domain = parsedURL ? parsedURL.host : ""; this.path = parsedURL ? parsedURL.path : ""; this.lastPathComponent = ""; if (parsedURL && parsedURL.path) { // First cut the query params. var path = parsedURL.path; var indexOfQuery = path.indexOf("?"); if (indexOfQuery !== -1) path = path.substring(0, indexOfQuery); // Then take last path component. var lastSlashIndex = path.lastIndexOf("/"); if (lastSlashIndex !== -1) this.lastPathComponent = path.substring(lastSlashIndex + 1); } this.lastPathComponentLowerCase = this.lastPathComponent.toLowerCase(); }, get documentURL() { return this._documentURL; }, set documentURL(x) { this._documentURL = x; }, get displayName() { if (this._displayName) return this._displayName; this._displayName = this.lastPathComponent; if (!this._displayName) this._displayName = this.displayDomain; if (!this._displayName && this.url) this._displayName = this.url.trimURL(WebInspector.mainResource ? WebInspector.mainResource.domain : ""); if (this._displayName === "/") this._displayName = this.url; return this._displayName; }, get displayDomain() { // WebInspector.Database calls this, so don't access more than this.domain. if (this.domain && (!WebInspector.mainResource || (WebInspector.mainResource && this.domain !== WebInspector.mainResource.domain))) return this.domain; return ""; }, get startTime() { return this._startTime || -1; }, set startTime(x) { this._startTime = x; }, get responseReceivedTime() { if (this.timing && this.timing.requestTime) { // Calculate responseReceivedTime from timing data for better accuracy. // Timing's requestTime is a baseline in seconds, rest of the numbers there are ticks in millis. return this.timing.requestTime + this.timing.receiveHeadersEnd / 1000.0; } return this._responseReceivedTime || -1; }, set responseReceivedTime(x) { this._responseReceivedTime = x; }, get endTime() { return this._endTime || -1; }, set endTime(x) { // In case of fast load (or in case of cached resources), endTime on network stack // can be less then m_responseReceivedTime measured in WebCore. Normalize it here, // prefer actualEndTime to m_responseReceivedTime. if (x < this.responseReceivedTime) this.responseReceivedTime = x; this._endTime = x; }, get duration() { if (this._endTime === -1 || this._startTime === -1) return -1; return this._endTime - this._startTime; }, get latency() { if (this._responseReceivedTime === -1 || this._startTime === -1) return -1; return this._responseReceivedTime - this._startTime; }, get receiveDuration() { if (this._endTime === -1 || this._responseReceivedTime === -1) return -1; return this._endTime - this._responseReceivedTime; }, get resourceSize() { return this._resourceSize || 0; }, set resourceSize(x) { this._resourceSize = x; }, get transferSize() { // FIXME: this is wrong for chunked-encoding resources. return this.cached ? 0 : Number(this.responseHeaders["Content-Length"] || this.resourceSize || 0); }, get expectedContentLength() { return this._expectedContentLength || 0; }, set expectedContentLength(x) { this._expectedContentLength = x; }, get finished() { return this._finished; }, set finished(x) { if (this._finished === x) return; this._finished = x; if (x) { this._checkWarnings(); this.dispatchEventToListeners("finished"); if (this._pendingContentCallbacks.length) this._innerRequestContent(); } }, get failed() { return this._failed; }, set failed(x) { this._failed = x; }, get category() { return this._category; }, set category(x) { this._category = x; }, get cached() { return this._cached; }, set cached(x) { this._cached = x; if (x) delete this._timing; }, get timing() { return this._timing; }, set timing(x) { if (!this._cached) this._timing = x; }, get mimeType() { return this._mimeType; }, set mimeType(x) { this._mimeType = x; }, get type() { return this._type; }, set type(x) { if (this._type === x) return; this._type = x; switch (x) { case WebInspector.Resource.Type.Document: this.category = WebInspector.resourceCategories.documents; break; case WebInspector.Resource.Type.Stylesheet: this.category = WebInspector.resourceCategories.stylesheets; break; case WebInspector.Resource.Type.Script: this.category = WebInspector.resourceCategories.scripts; break; case WebInspector.Resource.Type.Image: this.category = WebInspector.resourceCategories.images; break; case WebInspector.Resource.Type.Font: this.category = WebInspector.resourceCategories.fonts; break; case WebInspector.Resource.Type.XHR: this.category = WebInspector.resourceCategories.xhr; break; case WebInspector.Resource.Type.WebSocket: this.category = WebInspector.resourceCategories.websockets; break; case WebInspector.Resource.Type.Other: default: this.category = WebInspector.resourceCategories.other; break; } }, get requestHeaders() { return this._requestHeaders || {}; }, set requestHeaders(x) { this._requestHeaders = x; delete this._sortedRequestHeaders; delete this._requestCookies; this.dispatchEventToListeners("requestHeaders changed"); }, get sortedRequestHeaders() { if (this._sortedRequestHeaders !== undefined) return this._sortedRequestHeaders; this._sortedRequestHeaders = []; for (var key in this.requestHeaders) this._sortedRequestHeaders.push({header: key, value: this.requestHeaders[key]}); this._sortedRequestHeaders.sort(function(a,b) { return a.header.localeCompare(b.header) }); return this._sortedRequestHeaders; }, requestHeaderValue: function(headerName) { return this._headerValue(this.requestHeaders, headerName); }, get requestCookies() { if (!this._requestCookies) this._requestCookies = WebInspector.CookieParser.parseCookie(this.requestHeaderValue("Cookie")); return this._requestCookies; }, get requestFormData() { return this._requestFormData; }, set requestFormData(x) { this._requestFormData = x; delete this._parsedFormParameters; }, get responseHeaders() { return this._responseHeaders || {}; }, set responseHeaders(x) { this._responseHeaders = x; delete this._sortedResponseHeaders; delete this._responseCookies; this.dispatchEventToListeners("responseHeaders changed"); }, get sortedResponseHeaders() { if (this._sortedResponseHeaders !== undefined) return this._sortedResponseHeaders; this._sortedResponseHeaders = []; for (var key in this.responseHeaders) this._sortedResponseHeaders.push({header: key, value: this.responseHeaders[key]}); this._sortedResponseHeaders.sort(function(a,b) { return a.header.localeCompare(b.header) }); return this._sortedResponseHeaders; }, responseHeaderValue: function(headerName) { return this._headerValue(this.responseHeaders, headerName); }, get responseCookies() { if (!this._responseCookies) this._responseCookies = WebInspector.CookieParser.parseSetCookie(this.responseHeaderValue("Set-Cookie")); return this._responseCookies; }, get queryParameters() { if (this._parsedQueryParameters) return this._parsedQueryParameters; var queryString = this.url.split("?", 2)[1]; if (!queryString) return; this._parsedQueryParameters = this._parseParameters(queryString); return this._parsedQueryParameters; }, get formParameters() { if (this._parsedFormParameters) return this._parsedFormParameters; if (!this.requestFormData) return; var requestContentType = this.requestHeaderValue("Content-Type"); if (!requestContentType || !requestContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i)) return; this._parsedFormParameters = this._parseParameters(this.requestFormData); return this._parsedFormParameters; }, _parseParameters: function(queryString) { function parseNameValue(pair) { var parameter = {}; var splitPair = pair.split("=", 2); parameter.name = splitPair[0]; if (splitPair.length === 1) parameter.value = ""; else parameter.value = splitPair[1]; return parameter; } return queryString.split("&").map(parseNameValue); }, _headerValue: function(headers, headerName) { headerName = headerName.toLowerCase(); for (var header in headers) { if (header.toLowerCase() === headerName) return headers[header]; } }, get scripts() { if (!("_scripts" in this)) this._scripts = []; return this._scripts; }, addScript: function(script) { if (!script) return; this.scripts.unshift(script); script.resource = this; }, removeAllScripts: function() { if (!this._scripts) return; for (var i = 0; i < this._scripts.length; ++i) { if (this._scripts[i].resource === this) delete this._scripts[i].resource; } delete this._scripts; }, removeScript: function(script) { if (!script) return; if (script.resource === this) delete script.resource; if (!this._scripts) return; this._scripts.remove(script); }, get errors() { return this._errors || 0; }, set errors(x) { this._errors = x; this.dispatchEventToListeners("errors-warnings-updated"); }, get warnings() { return this._warnings || 0; }, set warnings(x) { this._warnings = x; this.dispatchEventToListeners("errors-warnings-updated"); }, clearErrorsAndWarnings: function() { this._warnings = 0; this._errors = 0; this.dispatchEventToListeners("errors-warnings-updated"); }, _mimeTypeIsConsistentWithType: function() { // If status is an error, content is likely to be of an inconsistent type, // as it's going to be an error message. We do not want to emit a warning // for this, though, as this will already be reported as resource loading failure. if (this.statusCode >= 400) return true; if (typeof this.type === "undefined" || this.type === WebInspector.Resource.Type.Other || this.type === WebInspector.Resource.Type.XHR || this.type === WebInspector.Resource.Type.WebSocket) return true; if (!this.mimeType) return true; // Might be not known for cached resources with null responses. if (this.mimeType in WebInspector.MIMETypes) return this.type in WebInspector.MIMETypes[this.mimeType]; return false; }, _checkWarnings: function() { for (var warning in WebInspector.Warnings) this._checkWarning(WebInspector.Warnings[warning]); }, _checkWarning: function(warning) { var msg; switch (warning.id) { case WebInspector.Warnings.IncorrectMIMEType.id: if (!this._mimeTypeIsConsistentWithType()) msg = new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.Other, WebInspector.ConsoleMessage.MessageType.Log, WebInspector.ConsoleMessage.MessageLevel.Warning, -1, this.url, null, 1, String.sprintf(WebInspector.Warnings.IncorrectMIMEType.message, WebInspector.Resource.Type.toUIString(this.type), this.mimeType), null, null); break; } if (msg) WebInspector.console.addMessage(msg); }, get content() { return this._content; }, set content(content) { this._content = content; }, requestContent: function(callback) { if (this._content) { callback(this._content, this._contentEncoded); return; } this._pendingContentCallbacks.push(callback); if (this.finished) this._innerRequestContent(); }, get contentURL() { const maxDataUrlSize = 1024 * 1024; // If resource content is not available or won't fit a data URL, fall back to using original URL. if (!this._content || this._content.length > maxDataUrlSize) return this.url; return "data:" + this.mimeType + (this._contentEncoded ? ";base64," : ",") + this._content; }, _innerRequestContent: function() { if (this._contentRequested) return; this._contentRequested = true; this._contentEncoded = !WebInspector.Resource.Type.isTextType(this.type); function onResourceContent(data) { this._content = data; var callbacks = this._pendingContentCallbacks.slice(); for (var i = 0; i < callbacks.length; ++i) callbacks[i](this._content, this._contentEncoded); this._pendingContentCallbacks.length = 0; delete this._contentRequested; } WebInspector.ResourceManager.requestContent(this, this._contentEncoded, onResourceContent.bind(this)); } } WebInspector.Resource.prototype.__proto__ = WebInspector.Object.prototype;