HAREntry.js   [plain text]


/*
 * Copyright (C) 2011 Google 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:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * 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.
 *     * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
 * OWNER OR 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.
 */

// See http://groups.google.com/group/http-archive-specification/web/har-1-2-spec
// for HAR specification.

// FIXME: Some fields are not yet supported due to back-end limitations.
// See https://bugs.webkit.org/show_bug.cgi?id=58127 for details.

WebInspector.HAREntry = function(resource)
{
    this._resource = resource;
}

WebInspector.HAREntry.prototype = {
    build: function()
    {
        return {
            pageref: this._resource.documentURL,
            startedDateTime: new Date(this._resource.startTime * 1000),
            time: WebInspector.HAREntry._toMilliseconds(this._resource.duration),
            request: this._buildRequest(),
            response: this._buildResponse(),
            cache: { }, // Not supproted yet.
            timings: this._buildTimings()
        };
    },

    _buildRequest: function()
    {
        var res = {
            method: this._resource.requestMethod,
            url: this._resource.url,
            // httpVersion: "HTTP/1.1" -- Not available.
            headers: this._buildHeaders(this._resource.requestHeaders),
            queryString: this._buildParameters(this._resource.queryParameters || []),
            cookies: this._buildCookies(this._resource.requestCookies || []),
            headersSize: -1, // Not available.
            bodySize: -1 // Not available.
        };
        if (this._resource.requestFormData)
            res.postData = this._buildPostData();
        return res;
    },

    _buildResponse: function()
    {
        return {
            status: this._resource.statusCode,
            statusText: this._resource.statusText,
            // "httpVersion": "HTTP/1.1" -- Not available.
            headers: this._buildHeaders(this._resource.responseHeaders),
            cookies: this._buildCookies(this._resource.responseCookies || []),
            content: this._buildContent(),
            redirectURL: this._resource.responseHeaderValue("Location") || "",
            headersSize: -1, // Not available.
            bodySize: this._resource.resourceSize
        };
    },

    _buildContent: function()
    {
        return {
            size: this._resource.resourceSize,
            // compression: 0, -- Not available.
            mimeType: this._resource.mimeType,
            // text: -- Not available.
        };
    },

    _buildTimings: function()
    {
        var waitForConnection = this._interval("connectStart", "connectEnd");
        var blocked;
        var connect;
        var dns = this._interval("dnsStart", "dnsEnd");
        var send = this._interval("sendStart", "sendEnd");
        var ssl = this._interval("sslStart", "sslEnd");

        if (ssl !== -1 && send !== -1)
            send -= ssl;

        if (this._resource.connectionReused) {
            connect = -1;
            blocked = waitForConnection;
        } else {
            blocked = 0;
            connect = waitForConnection;
            if (dns !== -1)
                connect -= dns;
        }

        return {
            blocked: blocked,
            dns: dns,
            connect: connect,
            send: send,
            wait: this._interval("sendEnd", "receiveHeadersEnd"),
            receive: WebInspector.HAREntry._toMilliseconds(this._resource.receiveDuration),
            ssl: ssl
        };
    },

    _buildHeaders: function(headers)
    {
        var result = [];
        for (var name in headers)
            result.push({ name: name, value: headers[name] });
        return result;
    },

    _buildPostData: function()
    {
        var res = {
            mimeType: this._resource.requestHeaderValue("Content-Type"),
            text: this._resource.requestFormData
        };
        if (this._resource.formParameters)
           res.params = this._buildParameters(this._resource.formParameters);
        return res;
    },

    _buildParameters: function(parameters)
    {
        return parameters.slice();
    },

    _buildCookies: function(cookies)
    {
        return cookies.map(this._buildCookie.bind(this));
    },

    _buildCookie: function(cookie)
    {
        
        return {
            name: cookie.name,
            value: cookie.value,
            path: cookie.path,
            domain: cookie.domain,
            expires: cookie.expires(new Date(this._resource.startTime * 1000)),
            httpOnly: cookie.httpOnly,
            secure: cookie.secure
        };
    },

    _interval: function(start, end)
    {
        var timing = this._resource.timing;
        if (!timing)
            return -1;
        var startTime = timing[start];
        return typeof startTime !== "number" || startTime === -1 ? -1 : Math.round(timing[end] - startTime);
    }
}

WebInspector.HAREntry._toMilliseconds = function(time)
{
    return time === -1 ? -1 : Math.round(time * 1000);
}

WebInspector.HARLog = function()
{
}

WebInspector.HARLog.prototype = {
    build: function()
    {
        var webKitVersion = /AppleWebKit\/([^ ]+)/.exec(window.navigator.userAgent);
        
        return {
            version: "1.2",
            creator: {
                name: "WebInspector",
                version: webKitVersion ? webKitVersion[1] : "n/a"
            },
            pages: this._buildPages(),
            entries: WebInspector.networkResources.map(this._convertResource.bind(this))
        }
    },

    _buildPages: function()
    {
        return [
            {
                startedDateTime: new Date(WebInspector.mainResource.startTime * 1000),
                id: WebInspector.mainResource.documentURL,
                title: "",
                pageTimings: this.buildMainResourceTimings()
            }
        ];
    },

    buildMainResourceTimings: function()
    {
        return {
             onContentLoad: this._pageEventTime(WebInspector.mainResourceDOMContentTime),
             onLoad: this._pageEventTime(WebInspector.mainResourceLoadTime),
        }
    },

    _convertResource: function(resource)
    {
        return (new WebInspector.HAREntry(resource)).build();
    },

    _pageEventTime: function(time)
    {
        var startTime = WebInspector.mainResource.startTime;
        if (time === -1 || startTime === -1)
            return -1;
        return WebInspector.HAREntry._toMilliseconds(time - startTime);
    }
}