blob: 712917f8dcb73d18644f9ae03c638b9ee81a469a [file] [log] [blame]
Blink Reformat4c46d092018-04-07 15:32:371/*
2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
3 * Copyright (C) 2008, 2009 Anthony Ricaud <[email protected]>
4 * Copyright (C) 2011 Google Inc. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16 * its contributors may be used to endorse or promote products derived
17 * from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31/**
32 * @implements {SDK.SDKModelObserver<!SDK.NetworkManager>}
33 */
34Network.NetworkLogView = class extends UI.VBox {
35 /**
36 * @param {!UI.FilterBar} filterBar
37 * @param {!Element} progressBarContainer
38 * @param {!Common.Setting} networkLogLargeRowsSetting
39 */
40 constructor(filterBar, progressBarContainer, networkLogLargeRowsSetting) {
41 super();
42 this.setMinimumSize(50, 64);
43 this.registerRequiredCSS('network/networkLogView.css');
44
45 this.element.id = 'network-container';
46
47 this._networkHideDataURLSetting = Common.settings.createSetting('networkHideDataURL', false);
48 this._networkResourceTypeFiltersSetting = Common.settings.createSetting('networkResourceTypeFilters', {});
49
50 this._rawRowHeight = 0;
51 this._progressBarContainer = progressBarContainer;
52 this._networkLogLargeRowsSetting = networkLogLargeRowsSetting;
53 this._networkLogLargeRowsSetting.addChangeListener(updateRowHeight.bind(this), this);
54
55 /**
56 * @this {Network.NetworkLogView}
57 */
58 function updateRowHeight() {
59 this._rawRowHeight = !!this._networkLogLargeRowsSetting.get() ? 41 : 21;
60 this._rowHeight = this._computeRowHeight();
61 }
62 this._rawRowHeight = 0;
63 this._rowHeight = 0;
64 updateRowHeight.call(this);
65
66 /** @type {!Network.NetworkTransferTimeCalculator} */
67 this._timeCalculator = new Network.NetworkTransferTimeCalculator();
68 /** @type {!Network.NetworkTransferDurationCalculator} */
69 this._durationCalculator = new Network.NetworkTransferDurationCalculator();
70 this._calculator = this._timeCalculator;
71
72 this._columns = new Network.NetworkLogViewColumns(
73 this, this._timeCalculator, this._durationCalculator, networkLogLargeRowsSetting);
74 this._columns.show(this.element);
75
76 /** @type {!Set<!SDK.NetworkRequest>} */
77 this._staleRequests = new Set();
78 /** @type {number} */
79 this._mainRequestLoadTime = -1;
80 /** @type {number} */
81 this._mainRequestDOMContentLoadedTime = -1;
82 this._highlightedSubstringChanges = [];
83
84 /** @type {!Array.<!Network.NetworkLogView.Filter>} */
85 this._filters = [];
86 /** @type {?Network.NetworkLogView.Filter} */
87 this._timeFilter = null;
88 /** @type {?Network.NetworkNode} */
89 this._hoveredNode = null;
90 /** @type {?Element} */
91 this._recordingHint = null;
92 /** @type {?number} */
93 this._refreshRequestId = null;
94 /** @type {?Network.NetworkRequestNode} */
95 this._highlightedNode = null;
96
97 this.linkifier = new Components.Linkifier();
98 this.badgePool = new ProductRegistry.BadgePool();
99
100 this._recording = false;
101 this._needsRefresh = false;
102
103 this._headerHeight = 0;
104
105 /** @type {!Map<string, !Network.GroupLookupInterface>} */
106 this._groupLookups = new Map();
107 this._groupLookups.set('Frame', new Network.NetworkFrameGrouper(this));
108
109 /** @type {?Network.GroupLookupInterface} */
110 this._activeGroupLookup = null;
111
112 this._textFilterUI = new UI.TextFilterUI();
113 this._textFilterUI.addEventListener(UI.FilterUI.Events.FilterChanged, this._filterChanged, this);
114 filterBar.addFilter(this._textFilterUI);
115
116 this._dataURLFilterUI = new UI.CheckboxFilterUI(
117 'hide-data-url', Common.UIString('Hide data URLs'), true, this._networkHideDataURLSetting);
118 this._dataURLFilterUI.addEventListener(UI.FilterUI.Events.FilterChanged, this._filterChanged.bind(this), this);
119 filterBar.addFilter(this._dataURLFilterUI);
120
121 const filterItems =
122 Object.values(Common.resourceCategories)
123 .map(category => ({name: category.title, label: category.shortTitle, title: category.title}));
124 this._resourceCategoryFilterUI = new UI.NamedBitSetFilterUI(filterItems, this._networkResourceTypeFiltersSetting);
125 this._resourceCategoryFilterUI.addEventListener(
126 UI.FilterUI.Events.FilterChanged, this._filterChanged.bind(this), this);
127 filterBar.addFilter(this._resourceCategoryFilterUI);
128
129 this._filterParser = new TextUtils.FilterParser(Network.NetworkLogView._searchKeys);
130 this._suggestionBuilder =
131 new UI.FilterSuggestionBuilder(Network.NetworkLogView._searchKeys, Network.NetworkLogView._sortSearchValues);
132 this._resetSuggestionBuilder();
133
134 this._dataGrid = this._columns.dataGrid();
135 this._setupDataGrid();
136 this._columns.sortByCurrentColumn();
Erik Luo0187a022018-05-31 18:35:49137 filterBar.filterButton().addEventListener(
138 UI.ToolbarButton.Events.Click, this._dataGrid.scheduleUpdate.bind(this._dataGrid, true /* isFromUser */));
Blink Reformat4c46d092018-04-07 15:32:37139
140 this._summaryBarElement = this.element.createChild('div', 'network-summary-bar');
141
142 new UI.DropTarget(
143 this.element, [UI.DropTarget.Type.File], Common.UIString('Drop HAR files here'), this._handleDrop.bind(this));
144
145 Common.moduleSetting('networkColorCodeResourceTypes')
146 .addChangeListener(this._invalidateAllItems.bind(this, false), this);
147
148 SDK.targetManager.observeModels(SDK.NetworkManager, this);
Pavel Feldman18d13562018-07-31 03:31:18149 SDK.networkLog.addEventListener(SDK.NetworkLog.Events.RequestAdded, this._onRequestUpdated, this);
150 SDK.networkLog.addEventListener(SDK.NetworkLog.Events.RequestUpdated, this._onRequestUpdated, this);
151 SDK.networkLog.addEventListener(SDK.NetworkLog.Events.Reset, this._reset, this);
Blink Reformat4c46d092018-04-07 15:32:37152
153 this._updateGroupByFrame();
154 Common.moduleSetting('network.group-by-frame').addChangeListener(() => this._updateGroupByFrame());
155
156 this._filterBar = filterBar;
Blink Reformat4c46d092018-04-07 15:32:37157 }
158
Blink Reformat4c46d092018-04-07 15:32:37159 _updateGroupByFrame() {
160 const value = Common.moduleSetting('network.group-by-frame').get();
161 this._setGrouping(value ? 'Frame' : null);
162 }
163
164 /**
165 * @param {string} key
166 * @param {!Array<string>} values
167 */
168 static _sortSearchValues(key, values) {
169 if (key === Network.NetworkLogView.FilterType.Priority) {
170 values.sort((a, b) => {
171 const aPriority = /** @type {!Protocol.Network.ResourcePriority} */ (PerfUI.uiLabelToNetworkPriority(a));
172 const bPriority = /** @type {!Protocol.Network.ResourcePriority} */ (PerfUI.uiLabelToNetworkPriority(b));
173 return PerfUI.networkPriorityWeight(aPriority) - PerfUI.networkPriorityWeight(bPriority);
174 });
175 } else {
176 values.sort();
177 }
178 }
179
180 /**
181 * @param {!Network.NetworkLogView.Filter} filter
182 * @param {!SDK.NetworkRequest} request
183 * @return {boolean}
184 */
185 static _negativeFilter(filter, request) {
186 return !filter(request);
187 }
188
189 /**
190 * @param {?RegExp} regex
191 * @param {!SDK.NetworkRequest} request
192 * @return {boolean}
193 */
194 static _requestPathFilter(regex, request) {
195 if (!regex)
196 return false;
197
198 return regex.test(request.path() + '/' + request.name());
199 }
200
201 /**
202 * @param {string} domain
203 * @return {!Array.<string>}
204 */
205 static _subdomains(domain) {
206 const result = [domain];
207 let indexOfPeriod = domain.indexOf('.');
208 while (indexOfPeriod !== -1) {
209 result.push('*' + domain.substring(indexOfPeriod));
210 indexOfPeriod = domain.indexOf('.', indexOfPeriod + 1);
211 }
212 return result;
213 }
214
215 /**
216 * @param {string} value
217 * @return {!Network.NetworkLogView.Filter}
218 */
219 static _createRequestDomainFilter(value) {
220 /**
221 * @param {string} string
222 * @return {string}
223 */
224 function escapeForRegExp(string) {
225 return string.escapeForRegExp();
226 }
227 const escapedPattern = value.split('*').map(escapeForRegExp).join('.*');
228 return Network.NetworkLogView._requestDomainFilter.bind(null, new RegExp('^' + escapedPattern + '$', 'i'));
229 }
230
231 /**
232 * @param {!RegExp} regex
233 * @param {!SDK.NetworkRequest} request
234 * @return {boolean}
235 */
236 static _requestDomainFilter(regex, request) {
237 return regex.test(request.domain);
238 }
239
240 /**
241 * @param {!SDK.NetworkRequest} request
242 * @return {boolean}
243 */
244 static _runningRequestFilter(request) {
245 return !request.finished;
246 }
247
248 /**
249 * @param {!SDK.NetworkRequest} request
250 * @return {boolean}
251 */
252 static _fromCacheRequestFilter(request) {
253 return request.cached();
254 }
255
256 /**
257 * @param {string} value
258 * @param {!SDK.NetworkRequest} request
259 * @return {boolean}
260 */
261 static _requestResponseHeaderFilter(value, request) {
262 return request.responseHeaderValue(value) !== undefined;
263 }
264
265 /**
266 * @param {string} value
267 * @param {!SDK.NetworkRequest} request
268 * @return {boolean}
269 */
270 static _requestMethodFilter(value, request) {
271 return request.requestMethod === value;
272 }
273
274 /**
275 * @param {string} value
276 * @param {!SDK.NetworkRequest} request
277 * @return {boolean}
278 */
279 static _requestPriorityFilter(value, request) {
280 return request.priority() === value;
281 }
282
283 /**
284 * @param {string} value
285 * @param {!SDK.NetworkRequest} request
286 * @return {boolean}
287 */
288 static _requestMimeTypeFilter(value, request) {
289 return request.mimeType === value;
290 }
291
292 /**
293 * @param {!Network.NetworkLogView.MixedContentFilterValues} value
294 * @param {!SDK.NetworkRequest} request
295 * @return {boolean}
296 */
297 static _requestMixedContentFilter(value, request) {
298 if (value === Network.NetworkLogView.MixedContentFilterValues.Displayed)
299 return request.mixedContentType === Protocol.Security.MixedContentType.OptionallyBlockable;
300 else if (value === Network.NetworkLogView.MixedContentFilterValues.Blocked)
301 return request.mixedContentType === Protocol.Security.MixedContentType.Blockable && request.wasBlocked();
302 else if (value === Network.NetworkLogView.MixedContentFilterValues.BlockOverridden)
303 return request.mixedContentType === Protocol.Security.MixedContentType.Blockable && !request.wasBlocked();
304 else if (value === Network.NetworkLogView.MixedContentFilterValues.All)
305 return request.mixedContentType !== Protocol.Security.MixedContentType.None;
306
307 return false;
308 }
309
310 /**
311 * @param {string} value
312 * @param {!SDK.NetworkRequest} request
313 * @return {boolean}
314 */
315 static _requestSchemeFilter(value, request) {
316 return request.scheme === value;
317 }
318
319 /**
320 * @param {string} value
321 * @param {!SDK.NetworkRequest} request
322 * @return {boolean}
323 */
324 static _requestSetCookieDomainFilter(value, request) {
325 const cookies = request.responseCookies;
326 for (let i = 0, l = cookies ? cookies.length : 0; i < l; ++i) {
327 if (cookies[i].domain() === value)
328 return true;
329 }
330 return false;
331 }
332
333 /**
334 * @param {string} value
335 * @param {!SDK.NetworkRequest} request
336 * @return {boolean}
337 */
338 static _requestSetCookieNameFilter(value, request) {
339 const cookies = request.responseCookies;
340 for (let i = 0, l = cookies ? cookies.length : 0; i < l; ++i) {
341 if (cookies[i].name() === value)
342 return true;
343 }
344 return false;
345 }
346
347 /**
348 * @param {string} value
349 * @param {!SDK.NetworkRequest} request
350 * @return {boolean}
351 */
352 static _requestSetCookieValueFilter(value, request) {
353 const cookies = request.responseCookies;
354 for (let i = 0, l = cookies ? cookies.length : 0; i < l; ++i) {
355 if (cookies[i].value() === value)
356 return true;
357 }
358 return false;
359 }
360
361 /**
362 * @param {number} value
363 * @param {!SDK.NetworkRequest} request
364 * @return {boolean}
365 */
366 static _requestSizeLargerThanFilter(value, request) {
367 return request.transferSize >= value;
368 }
369
370 /**
371 * @param {string} value
372 * @param {!SDK.NetworkRequest} request
373 * @return {boolean}
374 */
375 static _statusCodeFilter(value, request) {
376 return ('' + request.statusCode) === value;
377 }
378
379 /**
380 * @param {!SDK.NetworkRequest} request
381 * @return {boolean}
382 */
383 static HTTPRequestsFilter(request) {
384 return request.parsedURL.isValid && (request.scheme in Network.NetworkLogView.HTTPSchemas);
385 }
386
387 /**
388 * @param {!SDK.NetworkRequest} request
389 * @return {boolean}
390 */
391 static FinishedRequestsFilter(request) {
392 return request.finished;
393 }
394
395 /**
396 * @param {number} windowStart
397 * @param {number} windowEnd
398 * @param {!SDK.NetworkRequest} request
399 * @return {boolean}
400 */
401 static _requestTimeFilter(windowStart, windowEnd, request) {
402 if (request.issueTime() > windowEnd)
403 return false;
404 if (request.endTime !== -1 && request.endTime < windowStart)
405 return false;
406 return true;
407 }
408
409 /**
410 * @param {!SDK.NetworkRequest} request
411 */
412 static _copyRequestHeaders(request) {
413 InspectorFrontendHost.copyText(request.requestHeadersText());
414 }
415
416 /**
417 * @param {!SDK.NetworkRequest} request
418 */
419 static _copyResponseHeaders(request) {
420 InspectorFrontendHost.copyText(request.responseHeadersText);
421 }
422
423 /**
424 * @param {!SDK.NetworkRequest} request
425 */
426 static async _copyResponse(request) {
427 const contentData = await request.contentData();
428 let content = contentData.content;
429 if (contentData.encoded) {
430 content = Common.ContentProvider.contentAsDataURL(
431 contentData.content, request.mimeType, contentData.encoded, contentData.encoded ? 'utf-8' : null);
432 }
433 InspectorFrontendHost.copyText(content || '');
434 }
435
436 /**
437 * @param {!DataTransfer} dataTransfer
438 */
439 _handleDrop(dataTransfer) {
440 const items = dataTransfer.items;
441 if (!items.length)
442 return;
443 const entry = items[0].webkitGetAsEntry();
444 if (entry.isDirectory)
445 return;
446
447 entry.file(this._onLoadFromFile.bind(this));
448 }
449
450 /**
451 * @param {!File} file
452 */
453 async _onLoadFromFile(file) {
454 const outputStream = new Common.StringOutputStream();
455 const reader = new Bindings.ChunkedFileReader(file, /* chunkSize */ 10000000);
456 const success = await reader.read(outputStream);
457 if (!success) {
458 this._harLoadFailed(reader.error().message);
459 return;
460 }
461 let harRoot;
462 try {
463 // HARRoot and JSON.parse might throw.
464 harRoot = new HARImporter.HARRoot(JSON.parse(outputStream.data()));
465 } catch (e) {
466 this._harLoadFailed(e);
467 return;
468 }
Pavel Feldman18d13562018-07-31 03:31:18469 SDK.networkLog.importRequests(HARImporter.Importer.requestsFromHARLog(harRoot.log));
Blink Reformat4c46d092018-04-07 15:32:37470 }
471
472 /**
473 * @param {string} message
474 */
475 _harLoadFailed(message) {
476 Common.console.error('Failed to load HAR file with following error: ' + message);
477 }
478
479 /**
480 * @param {?string} groupKey
481 */
482 _setGrouping(groupKey) {
483 if (this._activeGroupLookup)
484 this._activeGroupLookup.reset();
485 const groupLookup = groupKey ? this._groupLookups.get(groupKey) || null : null;
486 this._activeGroupLookup = groupLookup;
487 this._invalidateAllItems();
488 }
489
490 /**
491 * @return {number}
492 */
493 _computeRowHeight() {
494 return Math.round(this._rawRowHeight * window.devicePixelRatio) / window.devicePixelRatio;
495 }
496
497 /**
498 * @param {!SDK.NetworkRequest} request
499 * @return {?Network.NetworkRequestNode}
500 */
501 nodeForRequest(request) {
502 return request[Network.NetworkLogView._networkNodeSymbol] || null;
503 }
504
505 /**
506 * @return {number}
507 */
508 headerHeight() {
509 return this._headerHeight;
510 }
511
512 /**
513 * @param {boolean} recording
514 */
515 setRecording(recording) {
516 this._recording = recording;
517 this._updateSummaryBar();
518 }
519
520 /**
521 * @override
522 * @param {!SDK.NetworkManager} networkManager
523 */
524 modelAdded(networkManager) {
525 // TODO(allada) Remove dependency on networkManager and instead use NetworkLog and PageLoad for needed data.
526 if (networkManager.target().parentTarget())
527 return;
528 const resourceTreeModel = networkManager.target().model(SDK.ResourceTreeModel);
529 if (resourceTreeModel) {
530 resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.Load, this._loadEventFired, this);
531 resourceTreeModel.addEventListener(
532 SDK.ResourceTreeModel.Events.DOMContentLoaded, this._domContentLoadedEventFired, this);
533 }
534 }
535
536 /**
537 * @override
538 * @param {!SDK.NetworkManager} networkManager
539 */
540 modelRemoved(networkManager) {
541 if (!networkManager.target().parentTarget()) {
542 const resourceTreeModel = networkManager.target().model(SDK.ResourceTreeModel);
543 if (resourceTreeModel) {
544 resourceTreeModel.removeEventListener(SDK.ResourceTreeModel.Events.Load, this._loadEventFired, this);
545 resourceTreeModel.removeEventListener(
546 SDK.ResourceTreeModel.Events.DOMContentLoaded, this._domContentLoadedEventFired, this);
547 }
548 }
549 }
550
551 /**
552 * @param {number} start
553 * @param {number} end
554 */
555 setWindow(start, end) {
556 if (!start && !end) {
557 this._timeFilter = null;
558 this._timeCalculator.setWindow(null);
559 } else {
560 this._timeFilter = Network.NetworkLogView._requestTimeFilter.bind(null, start, end);
561 this._timeCalculator.setWindow(new Network.NetworkTimeBoundary(start, end));
562 }
563 this._filterRequests();
564 }
565
566 clearSelection() {
567 if (this._dataGrid.selectedNode)
568 this._dataGrid.selectedNode.deselect();
569 }
570
571 _resetSuggestionBuilder() {
572 this._suggestionBuilder.clear();
573 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.Is, Network.NetworkLogView.IsFilterType.Running);
574 this._suggestionBuilder.addItem(
575 Network.NetworkLogView.FilterType.Is, Network.NetworkLogView.IsFilterType.FromCache);
576 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.LargerThan, '100');
577 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.LargerThan, '10k');
578 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.LargerThan, '1M');
579 this._textFilterUI.setSuggestionProvider(this._suggestionBuilder.completions.bind(this._suggestionBuilder));
580 }
581
582 /**
583 * @param {!Common.Event} event
584 */
585 _filterChanged(event) {
586 this.removeAllNodeHighlights();
587 this._parseFilterQuery(this._textFilterUI.value());
588 this._filterRequests();
Blink Reformat4c46d092018-04-07 15:32:37589 }
590
591 _showRecordingHint() {
592 this._hideRecordingHint();
593 this._recordingHint = this.element.createChild('div', 'network-status-pane fill');
594 const hintText = this._recordingHint.createChild('div', 'recording-hint');
595 const reloadShortcutNode = this._recordingHint.createChild('b');
596 reloadShortcutNode.textContent = UI.shortcutRegistry.shortcutDescriptorsForAction('inspector_main.reload')[0].name;
597
598 if (this._recording) {
599 const recordingText = hintText.createChild('span');
600 recordingText.textContent = Common.UIString('Recording network activity\u2026');
601 hintText.createChild('br');
602 hintText.appendChild(
603 UI.formatLocalized('Perform a request or hit %s to record the reload.', [reloadShortcutNode]));
604 } else {
605 const recordNode = hintText.createChild('b');
606 recordNode.textContent = UI.shortcutRegistry.shortcutTitleForAction('network.toggle-recording');
607 hintText.appendChild(UI.formatLocalized(
608 'Record (%s) or reload (%s) to display network activity.', [recordNode, reloadShortcutNode]));
609 }
610 }
611
612 _hideRecordingHint() {
613 if (this._recordingHint)
614 this._recordingHint.remove();
615 this._recordingHint = null;
616 }
617
618 /**
619 * @override
620 * @return {!Array.<!Element>}
621 */
622 elementsToRestoreScrollPositionsFor() {
623 if (!this._dataGrid) // Not initialized yet.
624 return [];
625 return [this._dataGrid.scrollContainer];
626 }
627
628 columnExtensionResolved() {
629 this._invalidateAllItems(true);
630 }
631
632 _setupDataGrid() {
633 this._dataGrid.setRowContextMenuCallback((contextMenu, node) => {
634 const request = node.request();
635 if (request)
636 this.handleContextMenuForRequest(contextMenu, request);
637 });
638 this._dataGrid.setStickToBottom(true);
639 this._dataGrid.setName('networkLog');
640 this._dataGrid.setResizeMethod(DataGrid.DataGrid.ResizeMethod.Last);
641 this._dataGrid.element.classList.add('network-log-grid');
642 this._dataGrid.element.addEventListener('mousedown', this._dataGridMouseDown.bind(this), true);
643 this._dataGrid.element.addEventListener('mousemove', this._dataGridMouseMove.bind(this), true);
644 this._dataGrid.element.addEventListener('mouseleave', () => this._setHoveredNode(null), true);
645 return this._dataGrid;
646 }
647
648 /**
649 * @param {!Event} event
650 */
651 _dataGridMouseMove(event) {
652 const node = (this._dataGrid.dataGridNodeFromNode(/** @type {!Node} */ (event.target)));
653 const highlightInitiatorChain = event.shiftKey;
654 this._setHoveredNode(node, highlightInitiatorChain);
655 }
656
657 /**
658 * @return {?Network.NetworkNode}
659 */
660 hoveredNode() {
661 return this._hoveredNode;
662 }
663
664 /**
665 * @param {?Network.NetworkNode} node
666 * @param {boolean=} highlightInitiatorChain
667 */
668 _setHoveredNode(node, highlightInitiatorChain) {
669 if (this._hoveredNode)
670 this._hoveredNode.setHovered(false, false);
671 this._hoveredNode = node;
672 if (this._hoveredNode)
673 this._hoveredNode.setHovered(true, !!highlightInitiatorChain);
674 }
675
676 /**
677 * @param {!Event} event
678 */
679 _dataGridMouseDown(event) {
680 if (!this._dataGrid.selectedNode && event.button)
681 event.consume();
682 }
683
684 _updateSummaryBar() {
685 this._hideRecordingHint();
686
687 let transferSize = 0;
688 let selectedNodeNumber = 0;
689 let selectedTransferSize = 0;
690 let baseTime = -1;
691 let maxTime = -1;
692
693 let nodeCount = 0;
Pavel Feldman18d13562018-07-31 03:31:18694 for (const request of SDK.networkLog.requests()) {
Blink Reformat4c46d092018-04-07 15:32:37695 const node = request[Network.NetworkLogView._networkNodeSymbol];
696 if (!node)
697 continue;
698 nodeCount++;
699 const requestTransferSize = request.transferSize;
700 transferSize += requestTransferSize;
701 if (!node[Network.NetworkLogView._isFilteredOutSymbol]) {
702 selectedNodeNumber++;
703 selectedTransferSize += requestTransferSize;
704 }
705 const networkManager = SDK.NetworkManager.forRequest(request);
706 // TODO(allada) inspectedURL should be stored in PageLoad used instead of target so HAR requests can have an
707 // inspected url.
708 if (networkManager && request.url() === networkManager.target().inspectedURL() &&
709 request.resourceType() === Common.resourceTypes.Document && !networkManager.target().parentTarget())
710 baseTime = request.startTime;
711 if (request.endTime > maxTime)
712 maxTime = request.endTime;
713 }
714
715 if (!nodeCount) {
716 this._showRecordingHint();
717 return;
718 }
719
720 const summaryBar = this._summaryBarElement;
721 summaryBar.removeChildren();
722 const separator = '\u2002\u2758\u2002';
723 let text = '';
724 /**
725 * @param {string} chunk
726 * @return {!Element}
727 */
728 function appendChunk(chunk) {
729 const span = summaryBar.createChild('span');
730 span.textContent = chunk;
731 text += chunk;
732 return span;
733 }
734
735 if (selectedNodeNumber !== nodeCount) {
736 appendChunk(Common.UIString('%d / %d requests', selectedNodeNumber, nodeCount));
737 appendChunk(separator);
738 appendChunk(Common.UIString(
739 '%s / %s transferred', Number.bytesToString(selectedTransferSize), Number.bytesToString(transferSize)));
740 } else {
741 appendChunk(Common.UIString('%d requests', nodeCount));
742 appendChunk(separator);
743 appendChunk(Common.UIString('%s transferred', Number.bytesToString(transferSize)));
744 }
745 if (baseTime !== -1 && maxTime !== -1) {
746 appendChunk(separator);
747 appendChunk(Common.UIString('Finish: %s', Number.secondsToString(maxTime - baseTime)));
748 if (this._mainRequestDOMContentLoadedTime !== -1 && this._mainRequestDOMContentLoadedTime > baseTime) {
749 appendChunk(separator);
750 const domContentLoadedText = Common.UIString(
751 'DOMContentLoaded: %s', Number.secondsToString(this._mainRequestDOMContentLoadedTime - baseTime));
752 appendChunk(domContentLoadedText).classList.add('summary-blue');
753 }
754 if (this._mainRequestLoadTime !== -1) {
755 appendChunk(separator);
756 const loadText = Common.UIString('Load: %s', Number.secondsToString(this._mainRequestLoadTime - baseTime));
757 appendChunk(loadText).classList.add('summary-red');
758 }
759 }
760 summaryBar.title = text;
761 }
762
763 scheduleRefresh() {
764 if (this._needsRefresh)
765 return;
766
767 this._needsRefresh = true;
768
769 if (this.isShowing() && !this._refreshRequestId)
770 this._refreshRequestId = this.element.window().requestAnimationFrame(this._refresh.bind(this));
771 }
772
773 /**
774 * @param {!Array<number>} times
775 */
776 addFilmStripFrames(times) {
777 this._columns.addEventDividers(times, 'network-frame-divider');
778 }
779
780 /**
781 * @param {number} time
782 */
783 selectFilmStripFrame(time) {
784 this._columns.selectFilmStripFrame(time);
785 }
786
787 clearFilmStripFrame() {
788 this._columns.clearFilmStripFrame();
789 }
790
791 _refreshIfNeeded() {
792 if (this._needsRefresh)
793 this._refresh();
794 }
795
796 /**
797 * @param {boolean=} deferUpdate
798 */
799 _invalidateAllItems(deferUpdate) {
Pavel Feldman18d13562018-07-31 03:31:18800 this._staleRequests = new Set(SDK.networkLog.requests());
Blink Reformat4c46d092018-04-07 15:32:37801 if (deferUpdate)
802 this.scheduleRefresh();
803 else
804 this._refresh();
805 }
806
807 /**
808 * @return {!Network.NetworkTimeCalculator}
809 */
810 timeCalculator() {
811 return this._timeCalculator;
812 }
813
814 /**
815 * @return {!Network.NetworkTimeCalculator}
816 */
817 calculator() {
818 return this._calculator;
819 }
820
821 /**
822 * @param {!Network.NetworkTimeCalculator} x
823 */
824 setCalculator(x) {
825 if (!x || this._calculator === x)
826 return;
827
828 if (this._calculator !== x) {
829 this._calculator = x;
830 this._columns.setCalculator(this._calculator);
831 }
832 this._calculator.reset();
833
834 if (this._calculator.startAtZero)
835 this._columns.hideEventDividers();
836 else
837 this._columns.showEventDividers();
838
839 this._invalidateAllItems();
840 }
841
842 /**
843 * @param {!Common.Event} event
844 */
845 _loadEventFired(event) {
846 if (!this._recording)
847 return;
848
849 const time = /** @type {number} */ (event.data.loadTime);
850 if (time) {
851 this._mainRequestLoadTime = time;
852 this._columns.addEventDividers([time], 'network-red-divider');
853 }
854 }
855
856 /**
857 * @param {!Common.Event} event
858 */
859 _domContentLoadedEventFired(event) {
860 if (!this._recording)
861 return;
862 const data = /** @type {number} */ (event.data);
863 if (data) {
864 this._mainRequestDOMContentLoadedTime = data;
865 this._columns.addEventDividers([data], 'network-blue-divider');
866 }
867 }
868
869 /**
870 * @override
871 */
872 wasShown() {
873 this._refreshIfNeeded();
874 this._columns.wasShown();
875 }
876
877 /**
878 * @override
879 */
880 willHide() {
881 this._columns.willHide();
882 }
883
884 /**
885 * @override
886 */
887 onResize() {
888 this._rowHeight = this._computeRowHeight();
889 }
890
891 /**
892 * @return {!Array<!Network.NetworkNode>}
893 */
894 flatNodesList() {
895 return this._dataGrid.rootNode().flatChildren();
896 }
897
898 stylesChanged() {
899 this._columns.scheduleRefresh();
900 }
901
902 _refresh() {
903 this._needsRefresh = false;
904
905 if (this._refreshRequestId) {
906 this.element.window().cancelAnimationFrame(this._refreshRequestId);
907 this._refreshRequestId = null;
908 }
909
910 this.removeAllNodeHighlights();
911
912 this._timeCalculator.updateBoundariesForEventTime(this._mainRequestLoadTime);
913 this._durationCalculator.updateBoundariesForEventTime(this._mainRequestLoadTime);
914 this._timeCalculator.updateBoundariesForEventTime(this._mainRequestDOMContentLoadedTime);
915 this._durationCalculator.updateBoundariesForEventTime(this._mainRequestDOMContentLoadedTime);
916
917 /** @type {!Map<!Network.NetworkNode, !Network.NetworkNode>} */
918 const nodesToInsert = new Map();
919 /** @type {!Array<!Network.NetworkNode>} */
920 const nodesToRefresh = [];
921
922 /** @type {!Set<!Network.NetworkRequestNode>} */
923 const staleNodes = new Set();
924
925 // While creating nodes it may add more entries into _staleRequests because redirect request nodes update the parent
926 // node so we loop until we have no more stale requests.
927 while (this._staleRequests.size) {
928 const request = this._staleRequests.firstValue();
929 this._staleRequests.delete(request);
930 let node = request[Network.NetworkLogView._networkNodeSymbol];
931 if (!node)
932 node = this._createNodeForRequest(request);
933 staleNodes.add(node);
934 }
935
936 for (const node of staleNodes) {
937 const isFilteredOut = !this._applyFilter(node);
938 if (isFilteredOut && node === this._hoveredNode)
939 this._setHoveredNode(null);
940
941 if (!isFilteredOut)
942 nodesToRefresh.push(node);
943 const request = node.request();
944 this._timeCalculator.updateBoundaries(request);
945 this._durationCalculator.updateBoundaries(request);
946 const newParent = this._parentNodeForInsert(node);
947 if (node[Network.NetworkLogView._isFilteredOutSymbol] === isFilteredOut && node.parent === newParent)
948 continue;
949 node[Network.NetworkLogView._isFilteredOutSymbol] = isFilteredOut;
950 const removeFromParent = node.parent && (isFilteredOut || node.parent !== newParent);
951 if (removeFromParent) {
952 let parent = node.parent;
953 parent.removeChild(node);
954 while (parent && !parent.hasChildren() && parent.dataGrid && parent.dataGrid.rootNode() !== parent) {
955 const grandparent = parent.parent;
956 grandparent.removeChild(parent);
957 parent = grandparent;
958 }
959 }
960
961 if (!newParent || isFilteredOut)
962 continue;
963
964 if (!newParent.dataGrid && !nodesToInsert.has(newParent)) {
965 nodesToInsert.set(newParent, this._dataGrid.rootNode());
966 nodesToRefresh.push(newParent);
967 }
968 nodesToInsert.set(node, newParent);
969 }
970
971 for (const node of nodesToInsert.keys())
972 nodesToInsert.get(node).appendChild(node);
973
974 for (const node of nodesToRefresh)
975 node.refresh();
976
977 this._updateSummaryBar();
978
979 if (nodesToInsert.size)
980 this._columns.sortByCurrentColumn();
981
982 this._dataGrid.updateInstantly();
983 this._didRefreshForTest();
984 }
985
986 _didRefreshForTest() {
987 }
988
989 /**
990 * @param {!Network.NetworkRequestNode} node
991 * @return {?Network.NetworkNode}
992 */
993 _parentNodeForInsert(node) {
994 if (!this._activeGroupLookup)
995 return this._dataGrid.rootNode();
996
997 const groupNode = this._activeGroupLookup.groupNodeForRequest(node.request());
998 if (!groupNode)
999 return this._dataGrid.rootNode();
1000 return groupNode;
1001 }
1002
1003 _reset() {
1004 this.dispatchEventToListeners(Network.NetworkLogView.Events.RequestSelected, null);
1005
1006 this._setHoveredNode(null);
1007 this._columns.reset();
1008
1009 this._timeFilter = null;
1010 this._calculator.reset();
1011
1012 this._timeCalculator.setWindow(null);
1013 this.linkifier.reset();
1014 this.badgePool.reset();
1015
1016 if (this._activeGroupLookup)
1017 this._activeGroupLookup.reset();
1018 this._staleRequests.clear();
1019 this._resetSuggestionBuilder();
1020
1021 this._mainRequestLoadTime = -1;
1022 this._mainRequestDOMContentLoadedTime = -1;
1023
1024 this._dataGrid.rootNode().removeChildren();
1025 this._updateSummaryBar();
1026 this._dataGrid.setStickToBottom(true);
1027 this.scheduleRefresh();
1028 }
1029
1030 /**
1031 * @param {string} filterString
1032 */
1033 setTextFilterValue(filterString) {
1034 this._textFilterUI.setValue(filterString);
1035 this._dataURLFilterUI.setChecked(false);
1036 this._resourceCategoryFilterUI.reset();
1037 }
1038
1039 /**
1040 * @param {!SDK.NetworkRequest} request
1041 */
1042 _createNodeForRequest(request) {
1043 const node = new Network.NetworkRequestNode(this, request);
1044 request[Network.NetworkLogView._networkNodeSymbol] = node;
1045 node[Network.NetworkLogView._isFilteredOutSymbol] = true;
1046
1047 for (let redirect = request.redirectSource(); redirect; redirect = redirect.redirectSource())
1048 this._refreshRequest(redirect);
1049 return node;
1050 }
1051
1052 /**
1053 * @param {!Common.Event} event
1054 */
1055 _onRequestUpdated(event) {
1056 const request = /** @type {!SDK.NetworkRequest} */ (event.data);
1057 this._refreshRequest(request);
1058 }
1059
1060 /**
1061 * @param {!SDK.NetworkRequest} request
1062 */
1063 _refreshRequest(request) {
1064 Network.NetworkLogView._subdomains(request.domain)
1065 .forEach(
1066 this._suggestionBuilder.addItem.bind(this._suggestionBuilder, Network.NetworkLogView.FilterType.Domain));
1067 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.Method, request.requestMethod);
1068 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.MimeType, request.mimeType);
1069 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.Scheme, '' + request.scheme);
1070 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.StatusCode, '' + request.statusCode);
1071
1072 const priority = request.priority();
1073 if (priority) {
1074 this._suggestionBuilder.addItem(
1075 Network.NetworkLogView.FilterType.Priority, PerfUI.uiLabelForNetworkPriority(priority));
1076 }
1077
1078 if (request.mixedContentType !== Protocol.Security.MixedContentType.None) {
1079 this._suggestionBuilder.addItem(
1080 Network.NetworkLogView.FilterType.MixedContent, Network.NetworkLogView.MixedContentFilterValues.All);
1081 }
1082
1083 if (request.mixedContentType === Protocol.Security.MixedContentType.OptionallyBlockable) {
1084 this._suggestionBuilder.addItem(
1085 Network.NetworkLogView.FilterType.MixedContent, Network.NetworkLogView.MixedContentFilterValues.Displayed);
1086 }
1087
1088 if (request.mixedContentType === Protocol.Security.MixedContentType.Blockable) {
1089 const suggestion = request.wasBlocked() ? Network.NetworkLogView.MixedContentFilterValues.Blocked :
1090 Network.NetworkLogView.MixedContentFilterValues.BlockOverridden;
1091 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.MixedContent, suggestion);
1092 }
1093
1094 const responseHeaders = request.responseHeaders;
1095 for (let i = 0, l = responseHeaders.length; i < l; ++i)
1096 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.HasResponseHeader, responseHeaders[i].name);
1097 const cookies = request.responseCookies;
1098 for (let i = 0, l = cookies ? cookies.length : 0; i < l; ++i) {
1099 const cookie = cookies[i];
1100 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.SetCookieDomain, cookie.domain());
1101 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.SetCookieName, cookie.name());
1102 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.SetCookieValue, cookie.value());
1103 }
1104
1105 this._staleRequests.add(request);
1106 this.scheduleRefresh();
1107 }
1108
1109 /**
1110 * @return {number}
1111 */
1112 rowHeight() {
1113 return this._rowHeight;
1114 }
1115
1116 /**
1117 * @param {boolean} gridMode
1118 */
1119 switchViewMode(gridMode) {
1120 this._columns.switchViewMode(gridMode);
1121 }
1122
1123 /**
1124 * @param {!UI.ContextMenu} contextMenu
1125 * @param {!SDK.NetworkRequest} request
1126 */
1127 handleContextMenuForRequest(contextMenu, request) {
1128 contextMenu.appendApplicableItems(request);
1129 let copyMenu = contextMenu.clipboardSection().appendSubMenuItem(Common.UIString('Copy'));
1130 const footerSection = copyMenu.footerSection();
1131 if (request) {
1132 copyMenu.defaultSection().appendItem(
1133 UI.copyLinkAddressLabel(), InspectorFrontendHost.copyText.bind(InspectorFrontendHost, request.contentURL()));
1134 if (request.requestHeadersText()) {
1135 copyMenu.defaultSection().appendItem(
1136 Common.UIString('Copy request headers'), Network.NetworkLogView._copyRequestHeaders.bind(null, request));
1137 }
1138
1139 if (request.responseHeadersText) {
1140 copyMenu.defaultSection().appendItem(
1141 Common.UIString('Copy response headers'), Network.NetworkLogView._copyResponseHeaders.bind(null, request));
1142 }
1143
1144 if (request.finished) {
1145 copyMenu.defaultSection().appendItem(
1146 Common.UIString('Copy response'), Network.NetworkLogView._copyResponse.bind(null, request));
1147 }
1148
1149 if (Host.isWin()) {
1150 footerSection.appendItem(
1151 Common.UIString('Copy as PowerShell'), this._copyPowerShellCommand.bind(this, request));
1152 footerSection.appendItem(Common.UIString('Copy as fetch'), this._copyFetchCall.bind(this, request));
1153 footerSection.appendItem(
1154 Common.UIString('Copy as cURL (cmd)'), this._copyCurlCommand.bind(this, request, 'win'));
1155 footerSection.appendItem(
1156 Common.UIString('Copy as cURL (bash)'), this._copyCurlCommand.bind(this, request, 'unix'));
1157 footerSection.appendItem(Common.UIString('Copy all as PowerShell'), this._copyAllPowerShellCommand.bind(this));
1158 footerSection.appendItem(Common.UIString('Copy all as fetch'), this._copyAllFetchCall.bind(this));
1159 footerSection.appendItem(Common.UIString('Copy all as cURL (cmd)'), this._copyAllCurlCommand.bind(this, 'win'));
1160 footerSection.appendItem(
1161 Common.UIString('Copy all as cURL (bash)'), this._copyAllCurlCommand.bind(this, 'unix'));
1162 } else {
1163 footerSection.appendItem(Common.UIString('Copy as fetch'), this._copyFetchCall.bind(this, request));
1164 footerSection.appendItem(Common.UIString('Copy as cURL'), this._copyCurlCommand.bind(this, request, 'unix'));
1165 footerSection.appendItem(Common.UIString('Copy all as fetch'), this._copyAllFetchCall.bind(this));
1166 footerSection.appendItem(Common.UIString('Copy all as cURL'), this._copyAllCurlCommand.bind(this, 'unix'));
1167 }
1168 } else {
1169 copyMenu = contextMenu.clipboardSection().appendSubMenuItem(Common.UIString('Copy'));
1170 }
1171 footerSection.appendItem(Common.UIString('Copy all as HAR'), this._copyAll.bind(this));
1172
1173 contextMenu.saveSection().appendItem(Common.UIString('Save as HAR with content'), this._exportAll.bind(this));
1174
1175 contextMenu.editSection().appendItem(Common.UIString('Clear browser cache'), this._clearBrowserCache.bind(this));
1176 contextMenu.editSection().appendItem(
1177 Common.UIString('Clear browser cookies'), this._clearBrowserCookies.bind(this));
1178
1179 if (request) {
1180 const maxBlockedURLLength = 20;
1181 const manager = SDK.multitargetNetworkManager;
1182 let patterns = manager.blockedPatterns();
1183
1184 const urlWithoutScheme = request.parsedURL.urlWithoutScheme();
1185 if (urlWithoutScheme && !patterns.find(pattern => pattern.url === urlWithoutScheme)) {
1186 contextMenu.debugSection().appendItem(
1187 Common.UIString('Block request URL'), addBlockedURL.bind(null, urlWithoutScheme));
1188 } else if (urlWithoutScheme) {
1189 const croppedURL = urlWithoutScheme.trimMiddle(maxBlockedURLLength);
1190 contextMenu.debugSection().appendItem(
1191 Common.UIString('Unblock %s', croppedURL), removeBlockedURL.bind(null, urlWithoutScheme));
1192 }
1193
1194 const domain = request.parsedURL.domain();
1195 if (domain && !patterns.find(pattern => pattern.url === domain)) {
1196 contextMenu.debugSection().appendItem(
1197 Common.UIString('Block request domain'), addBlockedURL.bind(null, domain));
1198 } else if (domain) {
1199 const croppedDomain = domain.trimMiddle(maxBlockedURLLength);
1200 contextMenu.debugSection().appendItem(
1201 Common.UIString('Unblock %s', croppedDomain), removeBlockedURL.bind(null, domain));
1202 }
1203
1204 if (SDK.NetworkManager.canReplayRequest(request)) {
1205 contextMenu.debugSection().appendItem(
1206 Common.UIString('Replay XHR'), SDK.NetworkManager.replayRequest.bind(null, request));
1207 }
1208
1209 /**
1210 * @param {string} url
1211 */
1212 function addBlockedURL(url) {
1213 patterns.push({enabled: true, url: url});
1214 manager.setBlockedPatterns(patterns);
1215 manager.setBlockingEnabled(true);
1216 UI.viewManager.showView('network.blocked-urls');
1217 }
1218
1219 /**
1220 * @param {string} url
1221 */
1222 function removeBlockedURL(url) {
1223 patterns = patterns.filter(pattern => pattern.url !== url);
1224 manager.setBlockedPatterns(patterns);
1225 UI.viewManager.showView('network.blocked-urls');
1226 }
1227 }
1228 }
1229
1230 _harRequests() {
Pavel Feldman18d13562018-07-31 03:31:181231 const httpRequests = SDK.networkLog.requests().filter(Network.NetworkLogView.HTTPRequestsFilter);
Blink Reformat4c46d092018-04-07 15:32:371232 return httpRequests.filter(Network.NetworkLogView.FinishedRequestsFilter);
1233 }
1234
1235 async _copyAll() {
Pavel Feldman18d13562018-07-31 03:31:181236 const harArchive = {log: await SDK.HARLog.build(this._harRequests())};
Blink Reformat4c46d092018-04-07 15:32:371237 InspectorFrontendHost.copyText(JSON.stringify(harArchive, null, 2));
1238 }
1239
1240 /**
1241 * @param {!SDK.NetworkRequest} request
1242 * @param {string} platform
1243 */
1244 async _copyCurlCommand(request, platform) {
1245 const command = await this._generateCurlCommand(request, platform);
1246 InspectorFrontendHost.copyText(command);
1247 }
1248
1249 /**
1250 * @param {string} platform
1251 */
1252 async _copyAllCurlCommand(platform) {
Pavel Feldman18d13562018-07-31 03:31:181253 const requests = SDK.networkLog.requests();
Blink Reformat4c46d092018-04-07 15:32:371254 const commands = await Promise.all(requests.map(request => this._generateCurlCommand(request, platform)));
1255 if (platform === 'win')
1256 InspectorFrontendHost.copyText(commands.join(' &\r\n'));
1257 else
1258 InspectorFrontendHost.copyText(commands.join(' ;\n'));
1259 }
1260
1261 /**
1262 * @param {!SDK.NetworkRequest} request
1263 * @param {string} platform
1264 */
1265 async _copyFetchCall(request, platform) {
1266 const command = await this._generateFetchCall(request);
1267 InspectorFrontendHost.copyText(command);
1268 }
1269
1270 async _copyAllFetchCall() {
Pavel Feldman18d13562018-07-31 03:31:181271 const requests = SDK.networkLog.requests();
Blink Reformat4c46d092018-04-07 15:32:371272 const commands = await Promise.all(requests.map(request => this._generateFetchCall(request)));
1273 InspectorFrontendHost.copyText(commands.join(' ;\n'));
1274 }
1275
1276 /**
1277 * @param {!SDK.NetworkRequest} request
1278 */
1279 async _copyPowerShellCommand(request) {
1280 const command = await this._generatePowerShellCommand(request);
1281 InspectorFrontendHost.copyText(command);
1282 }
1283
1284 async _copyAllPowerShellCommand() {
Pavel Feldman18d13562018-07-31 03:31:181285 const requests = SDK.networkLog.requests();
Blink Reformat4c46d092018-04-07 15:32:371286 const commands = await Promise.all(requests.map(request => this._generatePowerShellCommand(request)));
1287 InspectorFrontendHost.copyText(commands.join(';\r\n'));
1288 }
1289
1290 async _exportAll() {
1291 const url = SDK.targetManager.mainTarget().inspectedURL();
1292 const parsedURL = url.asParsedURL();
1293 const filename = parsedURL ? parsedURL.host : 'network-log';
1294 const stream = new Bindings.FileOutputStream();
1295
1296 if (!await stream.open(filename + '.har'))
1297 return;
1298
1299 const progressIndicator = new UI.ProgressIndicator();
1300 this._progressBarContainer.appendChild(progressIndicator.element);
1301 await Network.HARWriter.write(stream, this._harRequests(), progressIndicator);
1302 progressIndicator.done();
1303 stream.close();
1304 }
1305
1306 _clearBrowserCache() {
1307 if (confirm(Common.UIString('Are you sure you want to clear browser cache?')))
1308 SDK.multitargetNetworkManager.clearBrowserCache();
1309 }
1310
1311 _clearBrowserCookies() {
1312 if (confirm(Common.UIString('Are you sure you want to clear browser cookies?')))
1313 SDK.multitargetNetworkManager.clearBrowserCookies();
1314 }
1315
1316 _removeAllHighlights() {
1317 this.removeAllNodeHighlights();
1318 for (let i = 0; i < this._highlightedSubstringChanges.length; ++i)
1319 UI.revertDomChanges(this._highlightedSubstringChanges[i]);
1320 this._highlightedSubstringChanges = [];
1321 }
1322
1323 /**
1324 * @param {!Network.NetworkRequestNode} node
1325 * @return {boolean}
1326 */
1327 _applyFilter(node) {
1328 const request = node.request();
1329 if (this._timeFilter && !this._timeFilter(request))
1330 return false;
1331 const categoryName = request.resourceType().category().title;
1332 if (!this._resourceCategoryFilterUI.accept(categoryName))
1333 return false;
1334 if (this._dataURLFilterUI.checked() && request.parsedURL.isDataURL())
1335 return false;
1336 if (request.statusText === 'Service Worker Fallback Required')
1337 return false;
1338 for (let i = 0; i < this._filters.length; ++i) {
1339 if (!this._filters[i](request))
1340 return false;
1341 }
1342 return true;
1343 }
1344
1345 /**
1346 * @param {string} query
1347 */
1348 _parseFilterQuery(query) {
1349 const descriptors = this._filterParser.parse(query);
1350 this._filters = descriptors.map(descriptor => {
1351 const key = descriptor.key;
1352 const text = descriptor.text || '';
1353 const regex = descriptor.regex;
1354 let filter;
1355 if (key) {
1356 const defaultText = (key + ':' + text).escapeForRegExp();
1357 filter = this._createSpecialFilter(/** @type {!Network.NetworkLogView.FilterType} */ (key), text) ||
1358 Network.NetworkLogView._requestPathFilter.bind(null, new RegExp(defaultText, 'i'));
1359 } else if (descriptor.regex) {
1360 filter = Network.NetworkLogView._requestPathFilter.bind(null, /** @type {!RegExp} */ (regex));
1361 } else {
1362 filter = Network.NetworkLogView._requestPathFilter.bind(null, new RegExp(text.escapeForRegExp(), 'i'));
1363 }
1364 return descriptor.negative ? Network.NetworkLogView._negativeFilter.bind(null, filter) : filter;
1365 });
1366 }
1367
1368 /**
1369 * @param {!Network.NetworkLogView.FilterType} type
1370 * @param {string} value
1371 * @return {?Network.NetworkLogView.Filter}
1372 */
1373 _createSpecialFilter(type, value) {
1374 switch (type) {
1375 case Network.NetworkLogView.FilterType.Domain:
1376 return Network.NetworkLogView._createRequestDomainFilter(value);
1377
1378 case Network.NetworkLogView.FilterType.HasResponseHeader:
1379 return Network.NetworkLogView._requestResponseHeaderFilter.bind(null, value);
1380
1381 case Network.NetworkLogView.FilterType.Is:
1382 if (value.toLowerCase() === Network.NetworkLogView.IsFilterType.Running)
1383 return Network.NetworkLogView._runningRequestFilter;
1384 if (value.toLowerCase() === Network.NetworkLogView.IsFilterType.FromCache)
1385 return Network.NetworkLogView._fromCacheRequestFilter;
1386 break;
1387
1388 case Network.NetworkLogView.FilterType.LargerThan:
1389 return this._createSizeFilter(value.toLowerCase());
1390
1391 case Network.NetworkLogView.FilterType.Method:
1392 return Network.NetworkLogView._requestMethodFilter.bind(null, value);
1393
1394 case Network.NetworkLogView.FilterType.MimeType:
1395 return Network.NetworkLogView._requestMimeTypeFilter.bind(null, value);
1396
1397 case Network.NetworkLogView.FilterType.MixedContent:
1398 return Network.NetworkLogView._requestMixedContentFilter.bind(
1399 null, /** @type {!Network.NetworkLogView.MixedContentFilterValues} */ (value));
1400
1401 case Network.NetworkLogView.FilterType.Scheme:
1402 return Network.NetworkLogView._requestSchemeFilter.bind(null, value);
1403
1404 case Network.NetworkLogView.FilterType.SetCookieDomain:
1405 return Network.NetworkLogView._requestSetCookieDomainFilter.bind(null, value);
1406
1407 case Network.NetworkLogView.FilterType.SetCookieName:
1408 return Network.NetworkLogView._requestSetCookieNameFilter.bind(null, value);
1409
1410 case Network.NetworkLogView.FilterType.SetCookieValue:
1411 return Network.NetworkLogView._requestSetCookieValueFilter.bind(null, value);
1412
1413 case Network.NetworkLogView.FilterType.Priority:
1414 return Network.NetworkLogView._requestPriorityFilter.bind(null, PerfUI.uiLabelToNetworkPriority(value));
1415
1416 case Network.NetworkLogView.FilterType.StatusCode:
1417 return Network.NetworkLogView._statusCodeFilter.bind(null, value);
1418 }
1419 return null;
1420 }
1421
1422 /**
1423 * @param {string} value
1424 * @return {?Network.NetworkLogView.Filter}
1425 */
1426 _createSizeFilter(value) {
1427 let multiplier = 1;
1428 if (value.endsWith('k')) {
1429 multiplier = 1024;
1430 value = value.substring(0, value.length - 1);
1431 } else if (value.endsWith('m')) {
1432 multiplier = 1024 * 1024;
1433 value = value.substring(0, value.length - 1);
1434 }
1435 const quantity = Number(value);
1436 if (isNaN(quantity))
1437 return null;
1438 return Network.NetworkLogView._requestSizeLargerThanFilter.bind(null, quantity * multiplier);
1439 }
1440
1441 _filterRequests() {
1442 this._removeAllHighlights();
1443 this._invalidateAllItems();
1444 }
1445
1446 /**
1447 * @param {!SDK.NetworkRequest} request
1448 * @return {?Network.NetworkRequestNode}
1449 */
1450 _reveal(request) {
1451 this.removeAllNodeHighlights();
1452 const node = request[Network.NetworkLogView._networkNodeSymbol];
1453 if (!node || !node.dataGrid)
1454 return null;
1455 node.reveal();
1456 return node;
1457 }
1458
1459 /**
1460 * @param {!SDK.NetworkRequest} request
1461 */
1462 revealAndHighlightRequest(request) {
1463 const node = this._reveal(request);
1464 if (node)
1465 this._highlightNode(node);
1466 }
1467
1468 /**
1469 * @param {!SDK.NetworkRequest} request
1470 */
1471 selectRequest(request) {
Eugene Ostroukhovb600f662018-05-09 00:18:141472 this.setTextFilterValue('');
Blink Reformat4c46d092018-04-07 15:32:371473 const node = this._reveal(request);
1474 if (node)
1475 node.select();
1476 }
1477
1478 removeAllNodeHighlights() {
1479 if (this._highlightedNode) {
1480 this._highlightedNode.element().classList.remove('highlighted-row');
1481 this._highlightedNode = null;
1482 }
1483 }
1484
1485 /**
1486 * @param {!Network.NetworkRequestNode} node
1487 */
1488 _highlightNode(node) {
1489 UI.runCSSAnimationOnce(node.element(), 'highlighted-row');
1490 this._highlightedNode = node;
1491 }
1492
1493 /**
1494 * @param {!SDK.NetworkRequest} request
1495 * @return {!Promise<string>}
1496 */
1497 async _generateFetchCall(request) {
1498 const ignoredHeaders = {
1499 // Internal headers
1500 'method': 1,
1501 'path': 1,
1502 'scheme': 1,
1503 'version': 1,
1504
1505 // Unsafe headers
1506 // Keep this list synchronized with src/net/http/http_util.cc
1507 'accept-charset': 1,
1508 'accept-encoding': 1,
1509 'access-control-request-headers': 1,
1510 'access-control-request-method': 1,
1511 'connection': 1,
1512 'content-length': 1,
1513 'cookie': 1,
1514 'cookie2': 1,
1515 'date': 1,
1516 'dnt': 1,
1517 'expect': 1,
1518 'host': 1,
1519 'keep-alive': 1,
1520 'origin': 1,
1521 'referer': 1,
1522 'te': 1,
1523 'trailer': 1,
1524 'transfer-encoding': 1,
1525 'upgrade': 1,
1526 'via': 1,
1527 // TODO(phistuck) - remove this once crbug.com/571722 is fixed.
1528 'user-agent': 1
1529 };
1530
1531 const credentialHeaders = {'cookie': 1, 'authorization': 1};
1532
1533 const url = JSON.stringify(request.url());
1534
1535 const requestHeaders = request.requestHeaders();
1536 const headerData = requestHeaders.reduce((result, header) => {
1537 const name = header.name;
1538
1539 if (!ignoredHeaders[name.toLowerCase()] && !name.includes(':'))
1540 result.append(name, header.value);
1541
1542 return result;
1543 }, new Headers());
1544
1545 const headers = {};
1546 for (const headerArray of headerData)
PhistucK6ed0a3e2018-08-04 06:28:411547 headers[headerArray[0]] = headerArray[1];
Blink Reformat4c46d092018-04-07 15:32:371548
1549 const credentials =
1550 request.requestCookies || requestHeaders.some(({name}) => credentialHeaders[name.toLowerCase()]) ? 'include' :
1551 'omit';
1552
1553 const referrerHeader = requestHeaders.find(({name}) => name.toLowerCase() === 'referer');
1554
1555 const referrer = referrerHeader ? referrerHeader.value : void 0;
1556
1557 const referrerPolicy = request.referrerPolicy() || void 0;
1558
1559 const requestBody = await request.requestFormData();
1560
1561 const fetchOptions = {
1562 credentials,
PhistucK6ed0a3e2018-08-04 06:28:411563 headers: Object.keys(headers).length ? headers : void 0,
Blink Reformat4c46d092018-04-07 15:32:371564 referrer,
1565 referrerPolicy,
1566 body: requestBody,
1567 method: request.requestMethod,
1568 mode: 'cors'
1569 };
1570
1571 const options = JSON.stringify(fetchOptions);
1572 return `fetch(${url}, ${options});`;
1573 }
1574
1575 /**
1576 * @param {!SDK.NetworkRequest} request
1577 * @param {string} platform
1578 * @return {!Promise<string>}
1579 */
1580 async _generateCurlCommand(request, platform) {
1581 let command = ['curl'];
1582 // These headers are derived from URL (except "version") and would be added by cURL anyway.
1583 const ignoredHeaders = {'host': 1, 'method': 1, 'path': 1, 'scheme': 1, 'version': 1};
1584
1585 function escapeStringWin(str) {
1586 /* If there are no new line characters do not escape the " characters
1587 since it only uglifies the command.
1588
1589 Because cmd.exe parser and MS Crt arguments parsers use some of the
1590 same escape characters, they can interact with each other in
1591 horrible ways, the order of operations is critical.
1592
1593 Replace \ with \\ first because it is an escape character for certain
1594 conditions in both parsers.
1595
1596 Replace all " with \" to ensure the first parser does not remove it.
1597
1598 Then escape all characters we are not sure about with ^ to ensure it
1599 gets to MS Crt parser safely.
1600
1601 The % character is special because MS Crt parser will try and look for
1602 ENV variables and fill them in it's place. We cannot escape them with %
1603 and cannot escape them with ^ (because it's cmd.exe's escape not MS Crt
1604 parser); So we can get cmd.exe parser to escape the character after it,
1605 if it is followed by a valid beginning character of an ENV variable.
1606 This ensures we do not try and double escape another ^ if it was placed
1607 by the previous replace.
1608
1609 Lastly we replace new lines with ^ and TWO new lines because the first
1610 new line is there to enact the escape command the second is the character
1611 to escape (in this case new line).
1612 */
1613 const encapsChars = /[\r\n]/.test(str) ? '^"' : '"';
1614 return encapsChars +
1615 str.replace(/\\/g, '\\\\')
1616 .replace(/"/g, '\\"')
1617 .replace(/[^a-zA-Z0-9\s_\-:=+~'\/.',?;()*`]/g, '^$&')
1618 .replace(/%(?=[a-zA-Z0-9_])/g, '%^')
1619 .replace(/\r\n|[\n\r]/g, '^\n\n') +
1620 encapsChars;
1621 }
1622
1623 /**
1624 * @param {string} str
1625 * @return {string}
1626 */
1627 function escapeStringPosix(str) {
1628 /**
1629 * @param {string} x
1630 * @return {string}
1631 */
1632 function escapeCharacter(x) {
1633 let code = x.charCodeAt(0);
1634 if (code < 256) {
1635 // Add leading zero when needed to not care about the next character.
1636 return code < 16 ? '\\x0' + code.toString(16) : '\\x' + code.toString(16);
1637 }
1638 code = code.toString(16);
1639 return '\\u' + ('0000' + code).substr(code.length, 4);
1640 }
1641
1642 if (/[^\x20-\x7E]|\'/.test(str)) {
1643 // Use ANSI-C quoting syntax.
1644 return '$\'' +
1645 str.replace(/\\/g, '\\\\')
1646 .replace(/\'/g, '\\\'')
1647 .replace(/\n/g, '\\n')
1648 .replace(/\r/g, '\\r')
1649 .replace(/[^\x20-\x7E]/g, escapeCharacter) +
1650 '\'';
1651 } else {
1652 // Use single quote syntax.
1653 return '\'' + str + '\'';
1654 }
1655 }
1656
1657 // cURL command expected to run on the same platform that DevTools run
1658 // (it may be different from the inspected page platform).
1659 const escapeString = platform === 'win' ? escapeStringWin : escapeStringPosix;
1660
1661 command.push(escapeString(request.url()).replace(/[[{}\]]/g, '\\$&'));
1662
1663 let inferredMethod = 'GET';
1664 const data = [];
1665 const requestContentType = request.requestContentType();
1666 const formData = await request.requestFormData();
1667 if (requestContentType && requestContentType.startsWith('application/x-www-form-urlencoded') && formData) {
1668 data.push('--data');
1669 data.push(escapeString(formData));
1670 ignoredHeaders['content-length'] = true;
1671 inferredMethod = 'POST';
1672 } else if (formData) {
1673 data.push('--data-binary');
1674 data.push(escapeString(formData));
1675 ignoredHeaders['content-length'] = true;
1676 inferredMethod = 'POST';
1677 }
1678
1679 if (request.requestMethod !== inferredMethod) {
1680 command.push('-X');
1681 command.push(request.requestMethod);
1682 }
1683
1684 const requestHeaders = request.requestHeaders();
1685 for (let i = 0; i < requestHeaders.length; i++) {
1686 const header = requestHeaders[i];
1687 const name = header.name.replace(/^:/, ''); // Translate SPDY v3 headers to HTTP headers.
1688 if (name.toLowerCase() in ignoredHeaders)
1689 continue;
1690 command.push('-H');
1691 command.push(escapeString(name + ': ' + header.value));
1692 }
1693 command = command.concat(data);
1694 command.push('--compressed');
1695
1696 if (request.securityState() === Protocol.Security.SecurityState.Insecure)
1697 command.push('--insecure');
1698 return command.join(' ');
1699 }
1700
1701 /**
1702 * @param {!SDK.NetworkRequest} request
1703 * @return {!Promise<string>}
1704 */
1705 async _generatePowerShellCommand(request) {
1706 const command = ['Invoke-WebRequest'];
1707 const ignoredHeaders =
1708 new Set(['host', 'connection', 'proxy-connection', 'content-length', 'expect', 'range', 'content-type']);
1709
1710 /**
1711 * @param {string} str
1712 * @return {string}
1713 */
1714 function escapeString(str) {
1715 return '"' +
1716 str.replace(/[`\$"]/g, '`$&').replace(/[^\x20-\x7E]/g, char => '$([char]' + char.charCodeAt(0) + ')') + '"';
1717 }
1718
1719 command.push('-Uri');
1720 command.push(escapeString(request.url()));
1721
1722 if (request.requestMethod !== 'GET') {
1723 command.push('-Method');
1724 command.push(escapeString(request.requestMethod));
1725 }
1726
1727 const requestHeaders = request.requestHeaders();
1728 const headerNameValuePairs = [];
1729 for (const header of requestHeaders) {
1730 const name = header.name.replace(/^:/, ''); // Translate h2 headers to HTTP headers.
1731 if (ignoredHeaders.has(name.toLowerCase()))
1732 continue;
1733 headerNameValuePairs.push(escapeString(name) + '=' + escapeString(header.value));
1734 }
1735 if (headerNameValuePairs.length) {
1736 command.push('-Headers');
1737 command.push('@{' + headerNameValuePairs.join('; ') + '}');
1738 }
1739
1740 const contentTypeHeader = requestHeaders.find(({name}) => name.toLowerCase() === 'content-type');
1741 if (contentTypeHeader) {
1742 command.push('-ContentType');
1743 command.push(escapeString(contentTypeHeader.value));
1744 }
1745
1746 const formData = await request.requestFormData();
1747 if (formData) {
1748 command.push('-Body');
1749 const body = escapeString(formData);
1750 if (/[^\x20-\x7E]/.test(formData))
1751 command.push('([System.Text.Encoding]::UTF8.GetBytes(' + body + '))');
1752 else
1753 command.push(body);
1754 }
1755
1756 return command.join(' ');
1757 }
Blink Reformat4c46d092018-04-07 15:32:371758};
1759
1760Network.NetworkLogView._isFilteredOutSymbol = Symbol('isFilteredOut');
1761Network.NetworkLogView._networkNodeSymbol = Symbol('NetworkNode');
1762
1763Network.NetworkLogView.HTTPSchemas = {
1764 'http': true,
1765 'https': true,
1766 'ws': true,
1767 'wss': true
1768};
1769
1770/** @enum {symbol} */
1771Network.NetworkLogView.Events = {
1772 RequestSelected: Symbol('RequestSelected')
1773};
1774
1775/** @enum {string} */
1776Network.NetworkLogView.FilterType = {
1777 Domain: 'domain',
1778 HasResponseHeader: 'has-response-header',
1779 Is: 'is',
1780 LargerThan: 'larger-than',
1781 Method: 'method',
1782 MimeType: 'mime-type',
1783 MixedContent: 'mixed-content',
1784 Priority: 'priority',
1785 Scheme: 'scheme',
1786 SetCookieDomain: 'set-cookie-domain',
1787 SetCookieName: 'set-cookie-name',
1788 SetCookieValue: 'set-cookie-value',
1789 StatusCode: 'status-code'
1790};
1791
1792/** @enum {string} */
1793Network.NetworkLogView.MixedContentFilterValues = {
1794 All: 'all',
1795 Displayed: 'displayed',
1796 Blocked: 'blocked',
1797 BlockOverridden: 'block-overridden'
1798};
1799
1800/** @enum {string} */
1801Network.NetworkLogView.IsFilterType = {
1802 Running: 'running',
1803 FromCache: 'from-cache'
1804};
1805
1806/** @type {!Array<string>} */
1807Network.NetworkLogView._searchKeys =
1808 Object.keys(Network.NetworkLogView.FilterType).map(key => Network.NetworkLogView.FilterType[key]);
1809
1810/** @typedef {function(!SDK.NetworkRequest): boolean} */
1811Network.NetworkLogView.Filter;
1812
1813/**
1814 * @interface
1815 */
1816Network.GroupLookupInterface = function() {};
1817
1818Network.GroupLookupInterface.prototype = {
1819 /**
1820 * @param {!SDK.NetworkRequest} request
1821 * @return {?Network.NetworkGroupNode}
1822 */
1823 groupNodeForRequest: function(request) {},
1824
1825 reset: function() {}
1826};