Home | History | Annotate | Download | only in front_end
      1 /*
      2  * Copyright (C) 2012 Google 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 are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 /**
     32  * @constructor
     33  * @extends {WebInspector.Object}
     34  * @implements {WebInspector.ContentProvider}
     35  * @param {!NetworkAgent.RequestId} requestId
     36  * @param {string} url
     37  * @param {string} documentURL
     38  * @param {!PageAgent.FrameId} frameId
     39  * @param {!NetworkAgent.LoaderId} loaderId
     40  */
     41 WebInspector.NetworkRequest = function(requestId, url, documentURL, frameId, loaderId)
     42 {
     43     this._requestId = requestId;
     44     this.url = url;
     45     this._documentURL = documentURL;
     46     this._frameId = frameId;
     47     this._loaderId = loaderId;
     48     this._startTime = -1;
     49     this._endTime = -1;
     50 
     51     this.statusCode = 0;
     52     this.statusText = "";
     53     this.requestMethod = "";
     54     this.requestTime = 0;
     55 
     56     this._type = WebInspector.resourceTypes.Other;
     57     this._contentEncoded = false;
     58     this._pendingContentCallbacks = [];
     59     this._frames = [];
     60 
     61     this._responseHeaderValues = {};
     62 }
     63 
     64 WebInspector.NetworkRequest.Events = {
     65     FinishedLoading: "FinishedLoading",
     66     TimingChanged: "TimingChanged",
     67     RequestHeadersChanged: "RequestHeadersChanged",
     68     ResponseHeadersChanged: "ResponseHeadersChanged",
     69 }
     70 
     71 /** @enum {string} */
     72 WebInspector.NetworkRequest.InitiatorType = {
     73     Other: "other",
     74     Parser: "parser",
     75     Redirect: "redirect",
     76     Script: "script"
     77 }
     78 
     79 /** @typedef {!{name: string, value: string}} */
     80 WebInspector.NetworkRequest.NameValue;
     81 
     82 WebInspector.NetworkRequest.prototype = {
     83     /**
     84      * @return {!NetworkAgent.RequestId}
     85      */
     86     get requestId()
     87     {
     88         return this._requestId;
     89     },
     90 
     91     set requestId(requestId)
     92     {
     93         this._requestId = requestId;
     94     },
     95 
     96     /**
     97      * @return {string}
     98      */
     99     get url()
    100     {
    101         return this._url;
    102     },
    103 
    104     set url(x)
    105     {
    106         if (this._url === x)
    107             return;
    108 
    109         this._url = x;
    110         this._parsedURL = new WebInspector.ParsedURL(x);
    111         delete this._queryString;
    112         delete this._parsedQueryParameters;
    113         delete this._name;
    114         delete this._path;
    115     },
    116 
    117     /**
    118      * @return {string}
    119      */
    120     get documentURL()
    121     {
    122         return this._documentURL;
    123     },
    124 
    125     get parsedURL()
    126     {
    127         return this._parsedURL;
    128     },
    129 
    130     /**
    131      * @return {!PageAgent.FrameId}
    132      */
    133     get frameId()
    134     {
    135         return this._frameId;
    136     },
    137 
    138     /**
    139      * @return {!NetworkAgent.LoaderId}
    140      */
    141     get loaderId()
    142     {
    143         return this._loaderId;
    144     },
    145 
    146     /**
    147      * @return {number}
    148      */
    149     get startTime()
    150     {
    151         return this._startTime || -1;
    152     },
    153 
    154     set startTime(x)
    155     {
    156         this._startTime = x;
    157     },
    158 
    159     /**
    160      * @return {number}
    161      */
    162     get responseReceivedTime()
    163     {
    164         return this._responseReceivedTime || -1;
    165     },
    166 
    167     set responseReceivedTime(x)
    168     {
    169         this._responseReceivedTime = x;
    170     },
    171 
    172     /**
    173      * @return {number}
    174      */
    175     get endTime()
    176     {
    177         return this._endTime || -1;
    178     },
    179 
    180     set endTime(x)
    181     {
    182         if (this.timing && this.timing.requestTime) {
    183             // Check against accurate responseReceivedTime.
    184             this._endTime = Math.max(x, this.responseReceivedTime);
    185         } else {
    186             // Prefer endTime since it might be from the network stack.
    187             this._endTime = x;
    188             if (this._responseReceivedTime > x)
    189                 this._responseReceivedTime = x;
    190         }
    191     },
    192 
    193     /**
    194      * @return {number}
    195      */
    196     get duration()
    197     {
    198         if (this._endTime === -1 || this._startTime === -1)
    199             return -1;
    200         return this._endTime - this._startTime;
    201     },
    202 
    203     /**
    204      * @return {number}
    205      */
    206     get latency()
    207     {
    208         if (this._responseReceivedTime === -1 || this._startTime === -1)
    209             return -1;
    210         return this._responseReceivedTime - this._startTime;
    211     },
    212 
    213     /**
    214      * @return {number}
    215      */
    216     get resourceSize()
    217     {
    218         return this._resourceSize || 0;
    219     },
    220 
    221     set resourceSize(x)
    222     {
    223         this._resourceSize = x;
    224     },
    225 
    226     /**
    227      * @return {number}
    228      */
    229     get transferSize()
    230     {
    231         if (typeof this._transferSize === "number")
    232             return this._transferSize;
    233         if (this.statusCode === 304) // Not modified
    234             return this.responseHeadersSize;
    235         if (this._cached)
    236             return 0;
    237         // If we did not receive actual transfer size from network
    238         // stack, we prefer using Content-Length over resourceSize as
    239         // resourceSize may differ from actual transfer size if platform's
    240         // network stack performed decoding (e.g. gzip decompression).
    241         // The Content-Length, though, is expected to come from raw
    242         // response headers and will reflect actual transfer length.
    243         // This won't work for chunked content encoding, so fall back to
    244         // resourceSize when we don't have Content-Length. This still won't
    245         // work for chunks with non-trivial encodings. We need a way to
    246         // get actual transfer size from the network stack.
    247         var bodySize = Number(this.responseHeaderValue("Content-Length") || this.resourceSize);
    248         return this.responseHeadersSize + bodySize;
    249     },
    250 
    251     /**
    252      * @param {number} x
    253      */
    254     increaseTransferSize: function(x)
    255     {
    256         this._transferSize = (this._transferSize || 0) + x;
    257     },
    258 
    259     /**
    260      * @return {boolean}
    261      */
    262     get finished()
    263     {
    264         return this._finished;
    265     },
    266 
    267     set finished(x)
    268     {
    269         if (this._finished === x)
    270             return;
    271 
    272         this._finished = x;
    273 
    274         if (x) {
    275             this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.FinishedLoading, this);
    276             if (this._pendingContentCallbacks.length)
    277                 this._innerRequestContent();
    278         }
    279     },
    280 
    281     /**
    282      * @return {boolean}
    283      */
    284     get failed()
    285     {
    286         return this._failed;
    287     },
    288 
    289     set failed(x)
    290     {
    291         this._failed = x;
    292     },
    293 
    294     /**
    295      * @return {boolean}
    296      */
    297     get canceled()
    298     {
    299         return this._canceled;
    300     },
    301 
    302     set canceled(x)
    303     {
    304         this._canceled = x;
    305     },
    306 
    307     /**
    308      * @return {boolean}
    309      */
    310     get cached()
    311     {
    312         return !!this._cached && !this._transferSize;
    313     },
    314 
    315     set cached(x)
    316     {
    317         this._cached = x;
    318         if (x)
    319             delete this._timing;
    320     },
    321 
    322     /**
    323      * @return {!NetworkAgent.ResourceTiming|undefined}
    324      */
    325     get timing()
    326     {
    327         return this._timing;
    328     },
    329 
    330     set timing(x)
    331     {
    332         if (x && !this._cached) {
    333             // Take startTime and responseReceivedTime from timing data for better accuracy.
    334             // Timing's requestTime is a baseline in seconds, rest of the numbers there are ticks in millis.
    335             this._startTime = x.requestTime;
    336             this._responseReceivedTime = x.requestTime + x.receiveHeadersEnd / 1000.0;
    337 
    338             this._timing = x;
    339             this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.TimingChanged, this);
    340         }
    341     },
    342 
    343     /**
    344      * @return {string}
    345      */
    346     get mimeType()
    347     {
    348         return this._mimeType;
    349     },
    350 
    351     set mimeType(x)
    352     {
    353         this._mimeType = x;
    354     },
    355 
    356     /**
    357      * @return {string}
    358      */
    359     get displayName()
    360     {
    361         return this._parsedURL.displayName;
    362     },
    363 
    364     name: function()
    365     {
    366         if (this._name)
    367             return this._name;
    368         this._parseNameAndPathFromURL();
    369         return this._name;
    370     },
    371 
    372     path: function()
    373     {
    374         if (this._path)
    375             return this._path;
    376         this._parseNameAndPathFromURL();
    377         return this._path;
    378     },
    379 
    380     _parseNameAndPathFromURL: function()
    381     {
    382         if (this._parsedURL.isDataURL()) {
    383             this._name = this._parsedURL.dataURLDisplayName();
    384             this._path = "";
    385         } else if (this._parsedURL.isAboutBlank()) {
    386             this._name = this._parsedURL.url;
    387             this._path = "";
    388         } else {
    389             this._path = this._parsedURL.host + this._parsedURL.folderPathComponents;
    390             this._path = this._path.trimURL(WebInspector.inspectedPageDomain ? WebInspector.inspectedPageDomain : "");
    391             if (this._parsedURL.lastPathComponent || this._parsedURL.queryParams)
    392                 this._name = this._parsedURL.lastPathComponent + (this._parsedURL.queryParams ? "?" + this._parsedURL.queryParams : "");
    393             else if (this._parsedURL.folderPathComponents) {
    394                 this._name = this._parsedURL.folderPathComponents.substring(this._parsedURL.folderPathComponents.lastIndexOf("/") + 1) + "/";
    395                 this._path = this._path.substring(0, this._path.lastIndexOf("/"));
    396             } else {
    397                 this._name = this._parsedURL.host;
    398                 this._path = "";
    399             }
    400         }
    401     },
    402 
    403     /**
    404      * @return {string}
    405      */
    406     get folder()
    407     {
    408         var path = this._parsedURL.path;
    409         var indexOfQuery = path.indexOf("?");
    410         if (indexOfQuery !== -1)
    411             path = path.substring(0, indexOfQuery);
    412         var lastSlashIndex = path.lastIndexOf("/");
    413         return lastSlashIndex !== -1 ? path.substring(0, lastSlashIndex) : "";
    414     },
    415 
    416     /**
    417      * @return {!WebInspector.ResourceType}
    418      */
    419     get type()
    420     {
    421         return this._type;
    422     },
    423 
    424     set type(x)
    425     {
    426         this._type = x;
    427     },
    428 
    429     /**
    430      * @return {string}
    431      */
    432     get domain()
    433     {
    434         return this._parsedURL.host;
    435     },
    436 
    437     /**
    438      * @return {string}
    439      */
    440     get scheme()
    441     {
    442         return this._parsedURL.scheme;
    443     },
    444 
    445     /**
    446      * @return {?WebInspector.NetworkRequest}
    447      */
    448     get redirectSource()
    449     {
    450         if (this.redirects && this.redirects.length > 0)
    451             return this.redirects[this.redirects.length - 1];
    452         return this._redirectSource;
    453     },
    454 
    455     set redirectSource(x)
    456     {
    457         this._redirectSource = x;
    458         delete this._initiatorInfo;
    459     },
    460 
    461     /**
    462      * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
    463      */
    464     requestHeaders: function()
    465     {
    466         return this._requestHeaders || [];
    467     },
    468 
    469     /**
    470      * @param {!Array.<!WebInspector.NetworkRequest.NameValue>} headers
    471      */
    472     setRequestHeaders: function(headers)
    473     {
    474         this._requestHeaders = headers;
    475         delete this._requestCookies;
    476 
    477         this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.RequestHeadersChanged);
    478     },
    479 
    480     /**
    481      * @return {string|undefined}
    482      */
    483     requestHeadersText: function()
    484     {
    485         return this._requestHeadersText;
    486     },
    487 
    488     /**
    489      * @param {string} text
    490      */
    491     setRequestHeadersText: function(text)
    492     {
    493         this._requestHeadersText = text;
    494 
    495         this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.RequestHeadersChanged);
    496     },
    497 
    498     /**
    499      * @param {string} headerName
    500      * @return {string|undefined}
    501      */
    502     requestHeaderValue: function(headerName)
    503     {
    504         return this._headerValue(this.requestHeaders(), headerName);
    505     },
    506 
    507     /**
    508      * @return {!Array.<!WebInspector.Cookie>}
    509      */
    510     get requestCookies()
    511     {
    512         if (!this._requestCookies)
    513             this._requestCookies = WebInspector.CookieParser.parseCookie(this.requestHeaderValue("Cookie"));
    514         return this._requestCookies;
    515     },
    516 
    517     /**
    518      * @return {string|undefined}
    519      */
    520     get requestFormData()
    521     {
    522         return this._requestFormData;
    523     },
    524 
    525     set requestFormData(x)
    526     {
    527         this._requestFormData = x;
    528         delete this._parsedFormParameters;
    529     },
    530 
    531     /**
    532      * @return {string|undefined}
    533      */
    534     requestHttpVersion: function()
    535     {
    536         var headersText = this.requestHeadersText();
    537         if (!headersText)
    538             return undefined;
    539         var firstLine = headersText.split(/\r\n/)[0];
    540         var match = firstLine.match(/(HTTP\/\d+\.\d+)$/);
    541         return match ? match[1] : undefined;
    542     },
    543 
    544     /**
    545      * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
    546      */
    547     get responseHeaders()
    548     {
    549         return this._responseHeaders || [];
    550     },
    551 
    552     set responseHeaders(x)
    553     {
    554         this._responseHeaders = x;
    555         delete this._sortedResponseHeaders;
    556         delete this._responseCookies;
    557         this._responseHeaderValues = {};
    558 
    559         this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.ResponseHeadersChanged);
    560     },
    561 
    562     /**
    563      * @return {string}
    564      */
    565     get responseHeadersText()
    566     {
    567         if (typeof this._responseHeadersText === "undefined") {
    568             this._responseHeadersText = "HTTP/1.1 " + this.statusCode + " " + this.statusText + "\r\n";
    569             for (var i = 0; i < this.responseHeaders.length; ++i)
    570                 this._responseHeadersText += this.responseHeaders[i].name + ": " + this.responseHeaders[i].value + "\r\n";
    571         }
    572         return this._responseHeadersText;
    573     },
    574 
    575     set responseHeadersText(x)
    576     {
    577         this._responseHeadersText = x;
    578 
    579         this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.ResponseHeadersChanged);
    580     },
    581 
    582     /**
    583      * @return {number}
    584      */
    585     get responseHeadersSize()
    586     {
    587         return this.responseHeadersText.length;
    588     },
    589 
    590     /**
    591      * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
    592      */
    593     get sortedResponseHeaders()
    594     {
    595         if (this._sortedResponseHeaders !== undefined)
    596             return this._sortedResponseHeaders;
    597 
    598         this._sortedResponseHeaders = this.responseHeaders.slice();
    599         this._sortedResponseHeaders.sort(function(a, b) { return a.name.toLowerCase().compareTo(b.name.toLowerCase()); });
    600         return this._sortedResponseHeaders;
    601     },
    602 
    603     /**
    604      * @param {string} headerName
    605      * @return {string|undefined}
    606      */
    607     responseHeaderValue: function(headerName)
    608     {
    609         var value = this._responseHeaderValues[headerName];
    610         if (value === undefined) {
    611             value = this._headerValue(this.responseHeaders, headerName);
    612             this._responseHeaderValues[headerName] = (value !== undefined) ? value : null;
    613         }
    614         return (value !== null) ? value : undefined;
    615     },
    616 
    617     /**
    618      * @return {!Array.<!WebInspector.Cookie>}
    619      */
    620     get responseCookies()
    621     {
    622         if (!this._responseCookies)
    623             this._responseCookies = WebInspector.CookieParser.parseSetCookie(this.responseHeaderValue("Set-Cookie"));
    624         return this._responseCookies;
    625     },
    626 
    627     /**
    628      * @return {?string}
    629      */
    630     queryString: function()
    631     {
    632         if (this._queryString !== undefined)
    633             return this._queryString;
    634 
    635         var queryString = null;
    636         var url = this.url;
    637         var questionMarkPosition = url.indexOf("?");
    638         if (questionMarkPosition !== -1) {
    639             queryString = url.substring(questionMarkPosition + 1);
    640             var hashSignPosition = queryString.indexOf("#");
    641             if (hashSignPosition !== -1)
    642                 queryString = queryString.substring(0, hashSignPosition);
    643         }
    644         this._queryString = queryString;
    645         return this._queryString;
    646     },
    647 
    648     /**
    649      * @return {?Array.<!WebInspector.NetworkRequest.NameValue>}
    650      */
    651     get queryParameters()
    652     {
    653         if (this._parsedQueryParameters)
    654             return this._parsedQueryParameters;
    655         var queryString = this.queryString();
    656         if (!queryString)
    657             return null;
    658         this._parsedQueryParameters = this._parseParameters(queryString);
    659         return this._parsedQueryParameters;
    660     },
    661 
    662     /**
    663      * @return {?Array.<!WebInspector.NetworkRequest.NameValue>}
    664      */
    665     get formParameters()
    666     {
    667         if (this._parsedFormParameters)
    668             return this._parsedFormParameters;
    669         if (!this.requestFormData)
    670             return null;
    671         var requestContentType = this.requestContentType();
    672         if (!requestContentType || !requestContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i))
    673             return null;
    674         this._parsedFormParameters = this._parseParameters(this.requestFormData);
    675         return this._parsedFormParameters;
    676     },
    677 
    678     /**
    679      * @return {string|undefined}
    680      */
    681     get responseHttpVersion()
    682     {
    683         var match = this.responseHeadersText.match(/^(HTTP\/\d+\.\d+)/);
    684         return match ? match[1] : undefined;
    685     },
    686 
    687     /**
    688      * @param {string} queryString
    689      * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
    690      */
    691     _parseParameters: function(queryString)
    692     {
    693         function parseNameValue(pair)
    694         {
    695             var splitPair = pair.split("=", 2);
    696             return {name: splitPair[0], value: splitPair[1] || ""};
    697         }
    698         return queryString.split("&").map(parseNameValue);
    699     },
    700 
    701     /**
    702      * @param {!Array.<!WebInspector.NetworkRequest.NameValue>} headers
    703      * @param {string} headerName
    704      * @return {string|undefined}
    705      */
    706     _headerValue: function(headers, headerName)
    707     {
    708         headerName = headerName.toLowerCase();
    709 
    710         var values = [];
    711         for (var i = 0; i < headers.length; ++i) {
    712             if (headers[i].name.toLowerCase() === headerName)
    713                 values.push(headers[i].value);
    714         }
    715         if (!values.length)
    716             return undefined;
    717         // Set-Cookie values should be separated by '\n', not comma, otherwise cookies could not be parsed.
    718         if (headerName === "set-cookie")
    719             return values.join("\n");
    720         return values.join(", ");
    721     },
    722 
    723     /**
    724      * @return {?string|undefined}
    725      */
    726     get content()
    727     {
    728         return this._content;
    729     },
    730 
    731     /**
    732      * @return {boolean}
    733      */
    734     get contentEncoded()
    735     {
    736         return this._contentEncoded;
    737     },
    738 
    739     /**
    740      * @return {string}
    741      */
    742     contentURL: function()
    743     {
    744         return this._url;
    745     },
    746 
    747     /**
    748      * @return {!WebInspector.ResourceType}
    749      */
    750     contentType: function()
    751     {
    752         return this._type;
    753     },
    754 
    755     /**
    756      * @param {function(?string)} callback
    757      */
    758     requestContent: function(callback)
    759     {
    760         // We do not support content retrieval for WebSockets at the moment.
    761         // Since WebSockets are potentially long-living, fail requests immediately
    762         // to prevent caller blocking until resource is marked as finished.
    763         if (this.type === WebInspector.resourceTypes.WebSocket) {
    764             callback(null);
    765             return;
    766         }
    767         if (typeof this._content !== "undefined") {
    768             callback(this.content || null);
    769             return;
    770         }
    771         this._pendingContentCallbacks.push(callback);
    772         if (this.finished)
    773             this._innerRequestContent();
    774     },
    775 
    776     /**
    777      * @param {string} query
    778      * @param {boolean} caseSensitive
    779      * @param {boolean} isRegex
    780      * @param {function(!Array.<!WebInspector.ContentProvider.SearchMatch>)} callback
    781      */
    782     searchInContent: function(query, caseSensitive, isRegex, callback)
    783     {
    784         callback([]);
    785     },
    786 
    787     /**
    788      * @return {boolean}
    789      */
    790     isHttpFamily: function()
    791     {
    792         return !!this.url.match(/^https?:/i);
    793     },
    794 
    795     /**
    796      * @return {string|undefined}
    797      */
    798     requestContentType: function()
    799     {
    800         return this.requestHeaderValue("Content-Type");
    801     },
    802 
    803     /**
    804      * @return {boolean}
    805      */
    806     isPingRequest: function()
    807     {
    808         return "text/ping" === this.requestContentType();
    809     },
    810 
    811     /**
    812      * @return {boolean}
    813      */
    814     hasErrorStatusCode: function()
    815     {
    816         return this.statusCode >= 400;
    817     },
    818 
    819     /**
    820      * @param {!Element} image
    821      */
    822     populateImageSource: function(image)
    823     {
    824         /**
    825          * @this {WebInspector.NetworkRequest}
    826          * @param {?string} content
    827          */
    828         function onResourceContent(content)
    829         {
    830             var imageSrc = this.asDataURL();
    831             if (imageSrc === null)
    832                 imageSrc = this.url;
    833             image.src = imageSrc;
    834         }
    835 
    836         this.requestContent(onResourceContent.bind(this));
    837     },
    838 
    839     /**
    840      * @return {?string}
    841      */
    842     asDataURL: function()
    843     {
    844         return WebInspector.contentAsDataURL(this._content, this.mimeType, this._contentEncoded);
    845     },
    846 
    847     _innerRequestContent: function()
    848     {
    849         if (this._contentRequested)
    850             return;
    851         this._contentRequested = true;
    852 
    853         /**
    854          * @param {?Protocol.Error} error
    855          * @param {string} content
    856          * @param {boolean} contentEncoded
    857          * @this {WebInspector.NetworkRequest}
    858          */
    859         function onResourceContent(error, content, contentEncoded)
    860         {
    861             this._content = error ? null : content;
    862             this._contentEncoded = contentEncoded;
    863             var callbacks = this._pendingContentCallbacks.slice();
    864             for (var i = 0; i < callbacks.length; ++i)
    865                 callbacks[i](this._content);
    866             this._pendingContentCallbacks.length = 0;
    867             delete this._contentRequested;
    868         }
    869         NetworkAgent.getResponseBody(this._requestId, onResourceContent.bind(this));
    870     },
    871 
    872     /**
    873      * @return {!{type: !WebInspector.NetworkRequest.InitiatorType, url: string, source: string, lineNumber: number, columnNumber: number}}
    874      */
    875     initiatorInfo: function()
    876     {
    877         if (this._initiatorInfo)
    878             return this._initiatorInfo;
    879 
    880         var type = WebInspector.NetworkRequest.InitiatorType.Other;
    881         var url = "";
    882         var lineNumber = -Infinity;
    883         var columnNumber = -Infinity;
    884 
    885         if (this.redirectSource) {
    886             type = WebInspector.NetworkRequest.InitiatorType.Redirect;
    887             url = this.redirectSource.url;
    888         } else if (this.initiator) {
    889             if (this.initiator.type === NetworkAgent.InitiatorType.Parser) {
    890                 type = WebInspector.NetworkRequest.InitiatorType.Parser;
    891                 url = this.initiator.url;
    892                 lineNumber = this.initiator.lineNumber;
    893             } else if (this.initiator.type === NetworkAgent.InitiatorType.Script) {
    894                 var topFrame = this.initiator.stackTrace[0];
    895                 if (topFrame.url) {
    896                     type = WebInspector.NetworkRequest.InitiatorType.Script;
    897                     url = topFrame.url;
    898                     lineNumber = topFrame.lineNumber;
    899                     columnNumber = topFrame.columnNumber;
    900                 }
    901             }
    902         }
    903 
    904         this._initiatorInfo = {type: type, url: url, source: WebInspector.displayNameForURL(url), lineNumber: lineNumber, columnNumber: columnNumber};
    905         return this._initiatorInfo;
    906     },
    907 
    908     /**
    909      * @return {!Array.<!Object>}
    910      */
    911     frames: function()
    912     {
    913         return this._frames;
    914     },
    915 
    916     /**
    917      * @param {number} position
    918      * @return {!Object|undefined}
    919      */
    920     frame: function(position)
    921     {
    922         return this._frames[position];
    923     },
    924 
    925     /**
    926      * @param {string} errorMessage
    927      * @param {number} time
    928      */
    929     addFrameError: function(errorMessage, time)
    930     {
    931         this._pushFrame({errorMessage: errorMessage, time: time});
    932     },
    933 
    934     /**
    935      * @param {!NetworkAgent.WebSocketFrame} response
    936      * @param {number} time
    937      * @param {boolean} sent
    938      */
    939     addFrame: function(response, time, sent)
    940     {
    941         response.time = time;
    942         if (sent)
    943             response.sent = sent;
    944         this._pushFrame(response);
    945     },
    946 
    947     /**
    948      * @param {!Object} frameOrError
    949      */
    950     _pushFrame: function(frameOrError)
    951     {
    952         if (this._frames.length >= 100)
    953             this._frames.splice(0, 10);
    954         this._frames.push(frameOrError);
    955     },
    956 
    957     __proto__: WebInspector.Object.prototype
    958 }
    959