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