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