• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2011 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.SDKModel}
34 * @param {!WebInspector.Target} target
35 */
36WebInspector.NetworkManager = function(target)
37{
38    WebInspector.SDKModel.call(this, WebInspector.NetworkManager, target);
39    this._dispatcher = new WebInspector.NetworkDispatcher(this);
40    this._target = target;
41    this._networkAgent = target.networkAgent();
42    target.registerNetworkDispatcher(this._dispatcher);
43    if (WebInspector.settings.cacheDisabled.get())
44        this._networkAgent.setCacheDisabled(true);
45
46    this._networkAgent.enable();
47
48    WebInspector.settings.cacheDisabled.addChangeListener(this._cacheDisabledSettingChanged, this);
49}
50
51WebInspector.NetworkManager.EventTypes = {
52    RequestStarted: "RequestStarted",
53    RequestUpdated: "RequestUpdated",
54    RequestFinished: "RequestFinished",
55    RequestUpdateDropped: "RequestUpdateDropped"
56}
57
58WebInspector.NetworkManager._MIMETypes = {
59    "text/html":                   {"document": true},
60    "text/xml":                    {"document": true},
61    "text/plain":                  {"document": true},
62    "application/xhtml+xml":       {"document": true},
63    "text/css":                    {"stylesheet": true},
64    "text/xsl":                    {"stylesheet": true},
65    "image/jpg":                   {"image": true},
66    "image/jpeg":                  {"image": true},
67    "image/pjpeg":                 {"image": true},
68    "image/png":                   {"image": true},
69    "image/gif":                   {"image": true},
70    "image/bmp":                   {"image": true},
71    "image/svg+xml":               {"image": true, "font": true, "document": true},
72    "image/vnd.microsoft.icon":    {"image": true},
73    "image/webp":                  {"image": true},
74    "image/x-icon":                {"image": true},
75    "image/x-xbitmap":             {"image": true},
76    "font/ttf":                    {"font": true},
77    "font/otf":                    {"font": true},
78    "font/woff":                   {"font": true},
79    "font/woff2":                  {"font": true},
80    "font/truetype":               {"font": true},
81    "font/opentype":               {"font": true},
82    "application/octet-stream":    {"font": true, "image": true},
83    "application/font-woff":       {"font": true},
84    "application/font-woff2":      {"font": true},
85    "application/x-font-woff":     {"font": true},
86    "application/x-font-type1":    {"font": true},
87    "application/x-font-ttf":      {"font": true},
88    "application/x-truetype-font": {"font": true},
89    "text/javascript":             {"script": true},
90    "text/ecmascript":             {"script": true},
91    "application/javascript":      {"script": true},
92    "application/ecmascript":      {"script": true},
93    "application/x-javascript":    {"script": true},
94    "application/json":            {"script": true},
95    "text/javascript1.1":          {"script": true},
96    "text/javascript1.2":          {"script": true},
97    "text/javascript1.3":          {"script": true},
98    "text/jscript":                {"script": true},
99    "text/livescript":             {"script": true},
100    "text/vtt":                    {"texttrack": true},
101}
102
103// Keep in sync with kDevToolsRequestInitiator defined in InspectorResourceAgent.cpp
104WebInspector.NetworkManager._devToolsRequestHeader = "X-DevTools-Request-Initiator";
105
106/**
107 * @param {?WebInspector.NetworkRequest} request
108 * @return {boolean}
109 */
110WebInspector.NetworkManager.hasDevToolsRequestHeader = function(request)
111{
112    return !!request && !!request.requestHeaderValue(WebInspector.NetworkManager._devToolsRequestHeader);
113}
114
115WebInspector.NetworkManager.prototype = {
116    /**
117     * @param {string} url
118     * @return {!WebInspector.NetworkRequest}
119     */
120    inflightRequestForURL: function(url)
121    {
122        return this._dispatcher._inflightRequestsByURL[url];
123    },
124
125    /**
126     * @param {!WebInspector.Event} event
127     */
128    _cacheDisabledSettingChanged: function(event)
129    {
130        var enabled = /** @type {boolean} */ (event.data);
131        this._networkAgent.setCacheDisabled(enabled);
132    },
133
134    dispose: function()
135    {
136        WebInspector.settings.cacheDisabled.removeChangeListener(this._cacheDisabledSettingChanged, this)
137    },
138
139    __proto__: WebInspector.SDKModel.prototype
140}
141
142/**
143 * @constructor
144 * @implements {NetworkAgent.Dispatcher}
145 */
146WebInspector.NetworkDispatcher = function(manager)
147{
148    this._manager = manager;
149    this._inflightRequestsById = {};
150    this._inflightRequestsByURL = {};
151}
152
153WebInspector.NetworkDispatcher.prototype = {
154    /**
155     * @param {!NetworkAgent.Headers} headersMap
156     * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
157     */
158    _headersMapToHeadersArray: function(headersMap)
159    {
160        var result = [];
161        for (var name in headersMap) {
162            var values = headersMap[name].split("\n");
163            for (var i = 0; i < values.length; ++i)
164                result.push({name: name, value: values[i]});
165        }
166        return result;
167    },
168
169    /**
170     * @param {!WebInspector.NetworkRequest} networkRequest
171     * @param {!NetworkAgent.Request} request
172     */
173    _updateNetworkRequestWithRequest: function(networkRequest, request)
174    {
175        networkRequest.requestMethod = request.method;
176        networkRequest.setRequestHeaders(this._headersMapToHeadersArray(request.headers));
177        networkRequest.requestFormData = request.postData;
178    },
179
180    /**
181     * @param {!WebInspector.NetworkRequest} networkRequest
182     * @param {!NetworkAgent.Response=} response
183     */
184    _updateNetworkRequestWithResponse: function(networkRequest, response)
185    {
186        if (response.url && networkRequest.url !== response.url)
187            networkRequest.url = response.url;
188        networkRequest.mimeType = response.mimeType;
189        networkRequest.statusCode = response.status;
190        networkRequest.statusText = response.statusText;
191        networkRequest.responseHeaders = this._headersMapToHeadersArray(response.headers);
192        if (response.encodedDataLength >= 0)
193            networkRequest.setTransferSize(response.encodedDataLength);
194        if (response.headersText)
195            networkRequest.responseHeadersText = response.headersText;
196        if (response.requestHeaders) {
197            networkRequest.setRequestHeaders(this._headersMapToHeadersArray(response.requestHeaders));
198            networkRequest.setRequestHeadersText(response.requestHeadersText || "");
199        }
200
201        networkRequest.connectionReused = response.connectionReused;
202        networkRequest.connectionId = String(response.connectionId);
203        if (response.remoteIPAddress)
204            networkRequest.setRemoteAddress(response.remoteIPAddress, response.remotePort || -1);
205
206        if (response.fromServiceWorker)
207            networkRequest.fetchedViaServiceWorker = true;
208
209        if (response.fromDiskCache)
210            networkRequest.cached = true;
211        else
212            networkRequest.timing = response.timing;
213
214        if (!this._mimeTypeIsConsistentWithType(networkRequest)) {
215            var consoleModel = this._manager._target.consoleModel;
216            consoleModel.addMessage(new WebInspector.ConsoleMessage(consoleModel.target(), WebInspector.ConsoleMessage.MessageSource.Network,
217                WebInspector.ConsoleMessage.MessageLevel.Log,
218                WebInspector.UIString("Resource interpreted as %s but transferred with MIME type %s: \"%s\".", networkRequest.type.title(), networkRequest.mimeType, networkRequest.url),
219                WebInspector.ConsoleMessage.MessageType.Log,
220                "",
221                0,
222                0,
223                networkRequest.requestId));
224        }
225    },
226
227    /**
228     * @param {!WebInspector.NetworkRequest} networkRequest
229     * @return {boolean}
230     */
231    _mimeTypeIsConsistentWithType: function(networkRequest)
232    {
233        // If status is an error, content is likely to be of an inconsistent type,
234        // as it's going to be an error message. We do not want to emit a warning
235        // for this, though, as this will already be reported as resource loading failure.
236        // Also, if a URL like http://localhost/wiki/load.php?debug=true&lang=en produces text/css and gets reloaded,
237        // it is 304 Not Modified and its guessed mime-type is text/php, which is wrong.
238        // Don't check for mime-types in 304-resources.
239        if (networkRequest.hasErrorStatusCode() || networkRequest.statusCode === 304 || networkRequest.statusCode === 204)
240            return true;
241
242        if (typeof networkRequest.type === "undefined"
243            || networkRequest.type === WebInspector.resourceTypes.Other
244            || networkRequest.type === WebInspector.resourceTypes.Media
245            || networkRequest.type === WebInspector.resourceTypes.XHR
246            || networkRequest.type === WebInspector.resourceTypes.WebSocket)
247            return true;
248
249        if (!networkRequest.mimeType)
250            return true; // Might be not known for cached resources with null responses.
251
252        if (networkRequest.mimeType in WebInspector.NetworkManager._MIMETypes)
253            return networkRequest.type.name() in WebInspector.NetworkManager._MIMETypes[networkRequest.mimeType];
254
255        return false;
256    },
257
258    /**
259     * @param {!NetworkAgent.RequestId} requestId
260     * @param {!PageAgent.FrameId} frameId
261     * @param {!NetworkAgent.LoaderId} loaderId
262     * @param {string} documentURL
263     * @param {!NetworkAgent.Request} request
264     * @param {!NetworkAgent.Timestamp} time
265     * @param {!NetworkAgent.Initiator} initiator
266     * @param {!NetworkAgent.Response=} redirectResponse
267     */
268    requestWillBeSent: function(requestId, frameId, loaderId, documentURL, request, time, initiator, redirectResponse)
269    {
270        var networkRequest = this._inflightRequestsById[requestId];
271        if (networkRequest) {
272            // FIXME: move this check to the backend.
273            if (!redirectResponse)
274                return;
275            this.responseReceived(requestId, frameId, loaderId, time, PageAgent.ResourceType.Other, redirectResponse);
276            networkRequest = this._appendRedirect(requestId, time, request.url);
277        } else
278            networkRequest = this._createNetworkRequest(requestId, frameId, loaderId, request.url, documentURL, initiator);
279        networkRequest.hasNetworkData = true;
280        this._updateNetworkRequestWithRequest(networkRequest, request);
281        networkRequest.startTime = time;
282
283        this._startNetworkRequest(networkRequest);
284    },
285
286    /**
287     * @param {!NetworkAgent.RequestId} requestId
288     */
289    requestServedFromCache: function(requestId)
290    {
291        var networkRequest = this._inflightRequestsById[requestId];
292        if (!networkRequest)
293            return;
294
295        networkRequest.cached = true;
296    },
297
298    /**
299     * @param {!NetworkAgent.RequestId} requestId
300     * @param {!PageAgent.FrameId} frameId
301     * @param {!NetworkAgent.LoaderId} loaderId
302     * @param {!NetworkAgent.Timestamp} time
303     * @param {!PageAgent.ResourceType} resourceType
304     * @param {!NetworkAgent.Response} response
305     */
306    responseReceived: function(requestId, frameId, loaderId, time, resourceType, response)
307    {
308        var networkRequest = this._inflightRequestsById[requestId];
309        if (!networkRequest) {
310            // We missed the requestWillBeSent.
311            var eventData = {};
312            eventData.url = response.url;
313            eventData.frameId = frameId;
314            eventData.loaderId = loaderId;
315            eventData.resourceType = resourceType;
316            eventData.mimeType = response.mimeType;
317            this._manager.dispatchEventToListeners(WebInspector.NetworkManager.EventTypes.RequestUpdateDropped, eventData);
318            return;
319        }
320
321        networkRequest.responseReceivedTime = time;
322        networkRequest.type = WebInspector.resourceTypes[resourceType];
323
324        this._updateNetworkRequestWithResponse(networkRequest, response);
325
326        this._updateNetworkRequest(networkRequest);
327    },
328
329    /**
330     * @param {!NetworkAgent.RequestId} requestId
331     * @param {!NetworkAgent.Timestamp} time
332     * @param {number} dataLength
333     * @param {number} encodedDataLength
334     */
335    dataReceived: function(requestId, time, dataLength, encodedDataLength)
336    {
337        var networkRequest = this._inflightRequestsById[requestId];
338        if (!networkRequest)
339            return;
340
341        networkRequest.resourceSize += dataLength;
342        if (encodedDataLength != -1)
343            networkRequest.increaseTransferSize(encodedDataLength);
344        networkRequest.endTime = time;
345
346        this._updateNetworkRequest(networkRequest);
347    },
348
349    /**
350     * @param {!NetworkAgent.RequestId} requestId
351     * @param {!NetworkAgent.Timestamp} finishTime
352     * @param {number} encodedDataLength
353     */
354    loadingFinished: function(requestId, finishTime, encodedDataLength)
355    {
356        var networkRequest = this._inflightRequestsById[requestId];
357        if (!networkRequest)
358            return;
359        this._finishNetworkRequest(networkRequest, finishTime, encodedDataLength);
360    },
361
362    /**
363     * @param {!NetworkAgent.RequestId} requestId
364     * @param {!NetworkAgent.Timestamp} time
365     * @param {!PageAgent.ResourceType} resourceType
366     * @param {string} localizedDescription
367     * @param {boolean=} canceled
368     */
369    loadingFailed: function(requestId, time, resourceType, localizedDescription, canceled)
370    {
371        var networkRequest = this._inflightRequestsById[requestId];
372        if (!networkRequest)
373            return;
374
375        networkRequest.failed = true;
376        networkRequest.type = WebInspector.resourceTypes[resourceType];
377        networkRequest.canceled = canceled;
378        networkRequest.localizedFailDescription = localizedDescription;
379        this._finishNetworkRequest(networkRequest, time, -1);
380    },
381
382    /**
383     * @param {!NetworkAgent.RequestId} requestId
384     * @param {string} requestURL
385     */
386    webSocketCreated: function(requestId, requestURL)
387    {
388        // FIXME: WebSocket MUST have initiator info.
389        var networkRequest = new WebInspector.NetworkRequest(this._manager._target, requestId, requestURL, "", "", "", null);
390        networkRequest.type = WebInspector.resourceTypes.WebSocket;
391        this._startNetworkRequest(networkRequest);
392    },
393
394    /**
395     * @param {!NetworkAgent.RequestId} requestId
396     * @param {!NetworkAgent.Timestamp} time
397     * @param {!NetworkAgent.WebSocketRequest} request
398     */
399    webSocketWillSendHandshakeRequest: function(requestId, time, request)
400    {
401        var networkRequest = this._inflightRequestsById[requestId];
402        if (!networkRequest)
403            return;
404
405        networkRequest.requestMethod = "GET";
406        networkRequest.setRequestHeaders(this._headersMapToHeadersArray(request.headers));
407        networkRequest.startTime = time;
408
409        this._updateNetworkRequest(networkRequest);
410    },
411
412    /**
413     * @param {!NetworkAgent.RequestId} requestId
414     * @param {!NetworkAgent.Timestamp} time
415     * @param {!NetworkAgent.WebSocketResponse} response
416     */
417    webSocketHandshakeResponseReceived: function(requestId, time, response)
418    {
419        var networkRequest = this._inflightRequestsById[requestId];
420        if (!networkRequest)
421            return;
422
423        networkRequest.statusCode = response.status;
424        networkRequest.statusText = response.statusText;
425        networkRequest.responseHeaders = this._headersMapToHeadersArray(response.headers);
426        networkRequest.responseHeadersText = response.headersText;
427        if (response.requestHeaders)
428            networkRequest.setRequestHeaders(this._headersMapToHeadersArray(response.requestHeaders));
429        if (response.requestHeadersText)
430            networkRequest.setRequestHeadersText(response.requestHeadersText);
431        networkRequest.responseReceivedTime = time;
432
433        this._updateNetworkRequest(networkRequest);
434    },
435
436    /**
437     * @param {!NetworkAgent.RequestId} requestId
438     * @param {!NetworkAgent.Timestamp} time
439     * @param {!NetworkAgent.WebSocketFrame} response
440     */
441    webSocketFrameReceived: function(requestId, time, response)
442    {
443        var networkRequest = this._inflightRequestsById[requestId];
444        if (!networkRequest)
445            return;
446
447        networkRequest.addFrame(response, time);
448        networkRequest.responseReceivedTime = time;
449
450        this._updateNetworkRequest(networkRequest);
451    },
452
453    /**
454     * @param {!NetworkAgent.RequestId} requestId
455     * @param {!NetworkAgent.Timestamp} time
456     * @param {!NetworkAgent.WebSocketFrame} response
457     */
458    webSocketFrameSent: function(requestId, time, response)
459    {
460        var networkRequest = this._inflightRequestsById[requestId];
461        if (!networkRequest)
462            return;
463
464        networkRequest.addFrame(response, time, true);
465        networkRequest.responseReceivedTime = time;
466
467        this._updateNetworkRequest(networkRequest);
468    },
469
470    /**
471     * @param {!NetworkAgent.RequestId} requestId
472     * @param {!NetworkAgent.Timestamp} time
473     * @param {string} errorMessage
474     */
475    webSocketFrameError: function(requestId, time, errorMessage)
476    {
477        var networkRequest = this._inflightRequestsById[requestId];
478        if (!networkRequest)
479            return;
480
481        networkRequest.addFrameError(errorMessage, time);
482        networkRequest.responseReceivedTime = time;
483
484        this._updateNetworkRequest(networkRequest);
485    },
486
487    /**
488     * @param {!NetworkAgent.RequestId} requestId
489     * @param {!NetworkAgent.Timestamp} time
490     */
491    webSocketClosed: function(requestId, time)
492    {
493        var networkRequest = this._inflightRequestsById[requestId];
494        if (!networkRequest)
495            return;
496        this._finishNetworkRequest(networkRequest, time, -1);
497    },
498
499    /**
500     * @param {!NetworkAgent.RequestId} requestId
501     * @param {!NetworkAgent.Timestamp} time
502     * @param {string} redirectURL
503     * @return {!WebInspector.NetworkRequest}
504     */
505    _appendRedirect: function(requestId, time, redirectURL)
506    {
507        var originalNetworkRequest = this._inflightRequestsById[requestId];
508        var previousRedirects = originalNetworkRequest.redirects || [];
509        originalNetworkRequest.requestId = requestId + ":redirected." + previousRedirects.length;
510        delete originalNetworkRequest.redirects;
511        if (previousRedirects.length > 0)
512            originalNetworkRequest.redirectSource = previousRedirects[previousRedirects.length - 1];
513        this._finishNetworkRequest(originalNetworkRequest, time, -1);
514        var newNetworkRequest = this._createNetworkRequest(requestId, originalNetworkRequest.frameId, originalNetworkRequest.loaderId,
515             redirectURL, originalNetworkRequest.documentURL, originalNetworkRequest.initiator());
516        newNetworkRequest.redirects = previousRedirects.concat(originalNetworkRequest);
517        return newNetworkRequest;
518    },
519
520    /**
521     * @param {!WebInspector.NetworkRequest} networkRequest
522     */
523    _startNetworkRequest: function(networkRequest)
524    {
525        this._inflightRequestsById[networkRequest.requestId] = networkRequest;
526        this._inflightRequestsByURL[networkRequest.url] = networkRequest;
527        this._dispatchEventToListeners(WebInspector.NetworkManager.EventTypes.RequestStarted, networkRequest);
528    },
529
530    /**
531     * @param {!WebInspector.NetworkRequest} networkRequest
532     */
533    _updateNetworkRequest: function(networkRequest)
534    {
535        this._dispatchEventToListeners(WebInspector.NetworkManager.EventTypes.RequestUpdated, networkRequest);
536    },
537
538    /**
539     * @param {!WebInspector.NetworkRequest} networkRequest
540     * @param {!NetworkAgent.Timestamp} finishTime
541     * @param {number} encodedDataLength
542     */
543    _finishNetworkRequest: function(networkRequest, finishTime, encodedDataLength)
544    {
545        networkRequest.endTime = finishTime;
546        networkRequest.finished = true;
547        if (encodedDataLength >= 0)
548            networkRequest.setTransferSize(encodedDataLength);
549        this._dispatchEventToListeners(WebInspector.NetworkManager.EventTypes.RequestFinished, networkRequest);
550        delete this._inflightRequestsById[networkRequest.requestId];
551        delete this._inflightRequestsByURL[networkRequest.url];
552    },
553
554    /**
555     * @param {string} eventType
556     * @param {!WebInspector.NetworkRequest} networkRequest
557     */
558    _dispatchEventToListeners: function(eventType, networkRequest)
559    {
560        this._manager.dispatchEventToListeners(eventType, networkRequest);
561    },
562
563    /**
564     * @param {!NetworkAgent.RequestId} requestId
565     * @param {string} frameId
566     * @param {!NetworkAgent.LoaderId} loaderId
567     * @param {string} url
568     * @param {string} documentURL
569     * @param {?NetworkAgent.Initiator} initiator
570     */
571    _createNetworkRequest: function(requestId, frameId, loaderId, url, documentURL, initiator)
572    {
573        return new WebInspector.NetworkRequest(this._manager._target, requestId, url, documentURL, frameId, loaderId, initiator);
574    }
575}
576