blob: 361196b10ea7201f6c823d181083ced0a96cba99 [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();
Ingvar Stepanyan1c771842018-10-10 14:35:08428 let content = contentData.content || '';
429 if (!request.contentType().isTextType())
430 content = Common.ContentProvider.contentAsDataURL(content, request.mimeType, contentData.encoded);
431 else if (contentData.encoded)
432 content = window.atob(content);
433 InspectorFrontendHost.copyText(content);
Blink Reformat4c46d092018-04-07 15:32:37434 }
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');
Joey Arhar0585e6f2018-10-30 23:11:18595
596 let reloadShortcutNode = null;
597 const reloadShortcutDescriptor = UI.shortcutRegistry.shortcutDescriptorsForAction('inspector_main.reload')[0];
598 if (reloadShortcutDescriptor) {
599 reloadShortcutNode = this._recordingHint.createChild('b');
600 reloadShortcutNode.textContent = reloadShortcutDescriptor.name;
601 }
Blink Reformat4c46d092018-04-07 15:32:37602
603 if (this._recording) {
604 const recordingText = hintText.createChild('span');
605 recordingText.textContent = Common.UIString('Recording network activity\u2026');
Joey Arhar0585e6f2018-10-30 23:11:18606 if (reloadShortcutNode) {
607 hintText.createChild('br');
608 hintText.appendChild(
609 UI.formatLocalized('Perform a request or hit %s to record the reload.', [reloadShortcutNode]));
610 }
Blink Reformat4c46d092018-04-07 15:32:37611 } else {
612 const recordNode = hintText.createChild('b');
613 recordNode.textContent = UI.shortcutRegistry.shortcutTitleForAction('network.toggle-recording');
Joey Arhar0585e6f2018-10-30 23:11:18614 if (reloadShortcutNode) {
615 hintText.appendChild(UI.formatLocalized(
616 'Record (%s) or reload (%s) to display network activity.', [recordNode, reloadShortcutNode]));
617 } else {
618 hintText.appendChild(UI.formatLocalized('Record (%s) to display network activity.', [recordNode]));
619 }
Blink Reformat4c46d092018-04-07 15:32:37620 }
621 }
622
623 _hideRecordingHint() {
624 if (this._recordingHint)
625 this._recordingHint.remove();
626 this._recordingHint = null;
627 }
628
629 /**
630 * @override
631 * @return {!Array.<!Element>}
632 */
633 elementsToRestoreScrollPositionsFor() {
634 if (!this._dataGrid) // Not initialized yet.
635 return [];
636 return [this._dataGrid.scrollContainer];
637 }
638
639 columnExtensionResolved() {
640 this._invalidateAllItems(true);
641 }
642
643 _setupDataGrid() {
644 this._dataGrid.setRowContextMenuCallback((contextMenu, node) => {
645 const request = node.request();
646 if (request)
647 this.handleContextMenuForRequest(contextMenu, request);
648 });
649 this._dataGrid.setStickToBottom(true);
650 this._dataGrid.setName('networkLog');
651 this._dataGrid.setResizeMethod(DataGrid.DataGrid.ResizeMethod.Last);
652 this._dataGrid.element.classList.add('network-log-grid');
653 this._dataGrid.element.addEventListener('mousedown', this._dataGridMouseDown.bind(this), true);
654 this._dataGrid.element.addEventListener('mousemove', this._dataGridMouseMove.bind(this), true);
655 this._dataGrid.element.addEventListener('mouseleave', () => this._setHoveredNode(null), true);
656 return this._dataGrid;
657 }
658
659 /**
660 * @param {!Event} event
661 */
662 _dataGridMouseMove(event) {
663 const node = (this._dataGrid.dataGridNodeFromNode(/** @type {!Node} */ (event.target)));
664 const highlightInitiatorChain = event.shiftKey;
665 this._setHoveredNode(node, highlightInitiatorChain);
666 }
667
668 /**
669 * @return {?Network.NetworkNode}
670 */
671 hoveredNode() {
672 return this._hoveredNode;
673 }
674
675 /**
676 * @param {?Network.NetworkNode} node
677 * @param {boolean=} highlightInitiatorChain
678 */
679 _setHoveredNode(node, highlightInitiatorChain) {
680 if (this._hoveredNode)
681 this._hoveredNode.setHovered(false, false);
682 this._hoveredNode = node;
683 if (this._hoveredNode)
684 this._hoveredNode.setHovered(true, !!highlightInitiatorChain);
685 }
686
687 /**
688 * @param {!Event} event
689 */
690 _dataGridMouseDown(event) {
691 if (!this._dataGrid.selectedNode && event.button)
692 event.consume();
693 }
694
695 _updateSummaryBar() {
696 this._hideRecordingHint();
697
698 let transferSize = 0;
Dan Beam87466b52018-12-01 18:41:20699 let resourceSize = 0;
Blink Reformat4c46d092018-04-07 15:32:37700 let selectedNodeNumber = 0;
701 let selectedTransferSize = 0;
Dan Beam87466b52018-12-01 18:41:20702 let selectedResourceSize = 0;
Blink Reformat4c46d092018-04-07 15:32:37703 let baseTime = -1;
704 let maxTime = -1;
705
706 let nodeCount = 0;
Pavel Feldman18d13562018-07-31 03:31:18707 for (const request of SDK.networkLog.requests()) {
Blink Reformat4c46d092018-04-07 15:32:37708 const node = request[Network.NetworkLogView._networkNodeSymbol];
709 if (!node)
710 continue;
711 nodeCount++;
712 const requestTransferSize = request.transferSize;
713 transferSize += requestTransferSize;
Dan Beam87466b52018-12-01 18:41:20714 const requestResourceSize = request.resourceSize;
715 resourceSize += requestResourceSize;
Blink Reformat4c46d092018-04-07 15:32:37716 if (!node[Network.NetworkLogView._isFilteredOutSymbol]) {
717 selectedNodeNumber++;
718 selectedTransferSize += requestTransferSize;
Dan Beam87466b52018-12-01 18:41:20719 selectedResourceSize += requestResourceSize;
Blink Reformat4c46d092018-04-07 15:32:37720 }
721 const networkManager = SDK.NetworkManager.forRequest(request);
722 // TODO(allada) inspectedURL should be stored in PageLoad used instead of target so HAR requests can have an
723 // inspected url.
724 if (networkManager && request.url() === networkManager.target().inspectedURL() &&
725 request.resourceType() === Common.resourceTypes.Document && !networkManager.target().parentTarget())
726 baseTime = request.startTime;
727 if (request.endTime > maxTime)
728 maxTime = request.endTime;
729 }
730
731 if (!nodeCount) {
732 this._showRecordingHint();
733 return;
734 }
735
736 const summaryBar = this._summaryBarElement;
737 summaryBar.removeChildren();
738 const separator = '\u2002\u2758\u2002';
739 let text = '';
740 /**
741 * @param {string} chunk
742 * @return {!Element}
743 */
744 function appendChunk(chunk) {
745 const span = summaryBar.createChild('span');
746 span.textContent = chunk;
747 text += chunk;
748 return span;
749 }
750
751 if (selectedNodeNumber !== nodeCount) {
752 appendChunk(Common.UIString('%d / %d requests', selectedNodeNumber, nodeCount));
753 appendChunk(separator);
754 appendChunk(Common.UIString(
755 '%s / %s transferred', Number.bytesToString(selectedTransferSize), Number.bytesToString(transferSize)));
Dan Beam87466b52018-12-01 18:41:20756 appendChunk(separator);
757 appendChunk(Common.UIString(
758 '%s / %s resources', Number.bytesToString(selectedResourceSize), Number.bytesToString(resourceSize)));
Blink Reformat4c46d092018-04-07 15:32:37759 } else {
760 appendChunk(Common.UIString('%d requests', nodeCount));
761 appendChunk(separator);
762 appendChunk(Common.UIString('%s transferred', Number.bytesToString(transferSize)));
Dan Beam87466b52018-12-01 18:41:20763 appendChunk(separator);
764 appendChunk(Common.UIString('%s resources', Number.bytesToString(resourceSize)));
Blink Reformat4c46d092018-04-07 15:32:37765 }
Dan Beam87466b52018-12-01 18:41:20766
Blink Reformat4c46d092018-04-07 15:32:37767 if (baseTime !== -1 && maxTime !== -1) {
768 appendChunk(separator);
769 appendChunk(Common.UIString('Finish: %s', Number.secondsToString(maxTime - baseTime)));
770 if (this._mainRequestDOMContentLoadedTime !== -1 && this._mainRequestDOMContentLoadedTime > baseTime) {
771 appendChunk(separator);
772 const domContentLoadedText = Common.UIString(
773 'DOMContentLoaded: %s', Number.secondsToString(this._mainRequestDOMContentLoadedTime - baseTime));
774 appendChunk(domContentLoadedText).classList.add('summary-blue');
775 }
776 if (this._mainRequestLoadTime !== -1) {
777 appendChunk(separator);
778 const loadText = Common.UIString('Load: %s', Number.secondsToString(this._mainRequestLoadTime - baseTime));
779 appendChunk(loadText).classList.add('summary-red');
780 }
781 }
782 summaryBar.title = text;
783 }
784
785 scheduleRefresh() {
786 if (this._needsRefresh)
787 return;
788
789 this._needsRefresh = true;
790
791 if (this.isShowing() && !this._refreshRequestId)
792 this._refreshRequestId = this.element.window().requestAnimationFrame(this._refresh.bind(this));
793 }
794
795 /**
796 * @param {!Array<number>} times
797 */
798 addFilmStripFrames(times) {
799 this._columns.addEventDividers(times, 'network-frame-divider');
800 }
801
802 /**
803 * @param {number} time
804 */
805 selectFilmStripFrame(time) {
806 this._columns.selectFilmStripFrame(time);
807 }
808
809 clearFilmStripFrame() {
810 this._columns.clearFilmStripFrame();
811 }
812
813 _refreshIfNeeded() {
814 if (this._needsRefresh)
815 this._refresh();
816 }
817
818 /**
819 * @param {boolean=} deferUpdate
820 */
821 _invalidateAllItems(deferUpdate) {
Pavel Feldman18d13562018-07-31 03:31:18822 this._staleRequests = new Set(SDK.networkLog.requests());
Blink Reformat4c46d092018-04-07 15:32:37823 if (deferUpdate)
824 this.scheduleRefresh();
825 else
826 this._refresh();
827 }
828
829 /**
830 * @return {!Network.NetworkTimeCalculator}
831 */
832 timeCalculator() {
833 return this._timeCalculator;
834 }
835
836 /**
837 * @return {!Network.NetworkTimeCalculator}
838 */
839 calculator() {
840 return this._calculator;
841 }
842
843 /**
844 * @param {!Network.NetworkTimeCalculator} x
845 */
846 setCalculator(x) {
847 if (!x || this._calculator === x)
848 return;
849
850 if (this._calculator !== x) {
851 this._calculator = x;
852 this._columns.setCalculator(this._calculator);
853 }
854 this._calculator.reset();
855
856 if (this._calculator.startAtZero)
857 this._columns.hideEventDividers();
858 else
859 this._columns.showEventDividers();
860
861 this._invalidateAllItems();
862 }
863
864 /**
865 * @param {!Common.Event} event
866 */
867 _loadEventFired(event) {
868 if (!this._recording)
869 return;
870
871 const time = /** @type {number} */ (event.data.loadTime);
872 if (time) {
873 this._mainRequestLoadTime = time;
874 this._columns.addEventDividers([time], 'network-red-divider');
875 }
876 }
877
878 /**
879 * @param {!Common.Event} event
880 */
881 _domContentLoadedEventFired(event) {
882 if (!this._recording)
883 return;
884 const data = /** @type {number} */ (event.data);
885 if (data) {
886 this._mainRequestDOMContentLoadedTime = data;
887 this._columns.addEventDividers([data], 'network-blue-divider');
888 }
889 }
890
891 /**
892 * @override
893 */
894 wasShown() {
895 this._refreshIfNeeded();
896 this._columns.wasShown();
897 }
898
899 /**
900 * @override
901 */
902 willHide() {
903 this._columns.willHide();
904 }
905
906 /**
907 * @override
908 */
909 onResize() {
910 this._rowHeight = this._computeRowHeight();
911 }
912
913 /**
914 * @return {!Array<!Network.NetworkNode>}
915 */
916 flatNodesList() {
917 return this._dataGrid.rootNode().flatChildren();
918 }
919
920 stylesChanged() {
921 this._columns.scheduleRefresh();
922 }
923
924 _refresh() {
925 this._needsRefresh = false;
926
927 if (this._refreshRequestId) {
928 this.element.window().cancelAnimationFrame(this._refreshRequestId);
929 this._refreshRequestId = null;
930 }
931
932 this.removeAllNodeHighlights();
933
934 this._timeCalculator.updateBoundariesForEventTime(this._mainRequestLoadTime);
935 this._durationCalculator.updateBoundariesForEventTime(this._mainRequestLoadTime);
936 this._timeCalculator.updateBoundariesForEventTime(this._mainRequestDOMContentLoadedTime);
937 this._durationCalculator.updateBoundariesForEventTime(this._mainRequestDOMContentLoadedTime);
938
939 /** @type {!Map<!Network.NetworkNode, !Network.NetworkNode>} */
940 const nodesToInsert = new Map();
941 /** @type {!Array<!Network.NetworkNode>} */
942 const nodesToRefresh = [];
943
944 /** @type {!Set<!Network.NetworkRequestNode>} */
945 const staleNodes = new Set();
946
947 // While creating nodes it may add more entries into _staleRequests because redirect request nodes update the parent
948 // node so we loop until we have no more stale requests.
949 while (this._staleRequests.size) {
950 const request = this._staleRequests.firstValue();
951 this._staleRequests.delete(request);
952 let node = request[Network.NetworkLogView._networkNodeSymbol];
953 if (!node)
954 node = this._createNodeForRequest(request);
955 staleNodes.add(node);
956 }
957
958 for (const node of staleNodes) {
959 const isFilteredOut = !this._applyFilter(node);
960 if (isFilteredOut && node === this._hoveredNode)
961 this._setHoveredNode(null);
962
963 if (!isFilteredOut)
964 nodesToRefresh.push(node);
965 const request = node.request();
966 this._timeCalculator.updateBoundaries(request);
967 this._durationCalculator.updateBoundaries(request);
968 const newParent = this._parentNodeForInsert(node);
969 if (node[Network.NetworkLogView._isFilteredOutSymbol] === isFilteredOut && node.parent === newParent)
970 continue;
971 node[Network.NetworkLogView._isFilteredOutSymbol] = isFilteredOut;
972 const removeFromParent = node.parent && (isFilteredOut || node.parent !== newParent);
973 if (removeFromParent) {
974 let parent = node.parent;
975 parent.removeChild(node);
976 while (parent && !parent.hasChildren() && parent.dataGrid && parent.dataGrid.rootNode() !== parent) {
977 const grandparent = parent.parent;
978 grandparent.removeChild(parent);
979 parent = grandparent;
980 }
981 }
982
983 if (!newParent || isFilteredOut)
984 continue;
985
986 if (!newParent.dataGrid && !nodesToInsert.has(newParent)) {
987 nodesToInsert.set(newParent, this._dataGrid.rootNode());
988 nodesToRefresh.push(newParent);
989 }
990 nodesToInsert.set(node, newParent);
991 }
992
993 for (const node of nodesToInsert.keys())
994 nodesToInsert.get(node).appendChild(node);
995
996 for (const node of nodesToRefresh)
997 node.refresh();
998
999 this._updateSummaryBar();
1000
1001 if (nodesToInsert.size)
1002 this._columns.sortByCurrentColumn();
1003
1004 this._dataGrid.updateInstantly();
1005 this._didRefreshForTest();
1006 }
1007
1008 _didRefreshForTest() {
1009 }
1010
1011 /**
1012 * @param {!Network.NetworkRequestNode} node
1013 * @return {?Network.NetworkNode}
1014 */
1015 _parentNodeForInsert(node) {
1016 if (!this._activeGroupLookup)
1017 return this._dataGrid.rootNode();
1018
1019 const groupNode = this._activeGroupLookup.groupNodeForRequest(node.request());
1020 if (!groupNode)
1021 return this._dataGrid.rootNode();
1022 return groupNode;
1023 }
1024
1025 _reset() {
1026 this.dispatchEventToListeners(Network.NetworkLogView.Events.RequestSelected, null);
1027
1028 this._setHoveredNode(null);
1029 this._columns.reset();
1030
1031 this._timeFilter = null;
1032 this._calculator.reset();
1033
1034 this._timeCalculator.setWindow(null);
1035 this.linkifier.reset();
1036 this.badgePool.reset();
1037
1038 if (this._activeGroupLookup)
1039 this._activeGroupLookup.reset();
1040 this._staleRequests.clear();
1041 this._resetSuggestionBuilder();
1042
1043 this._mainRequestLoadTime = -1;
1044 this._mainRequestDOMContentLoadedTime = -1;
1045
1046 this._dataGrid.rootNode().removeChildren();
1047 this._updateSummaryBar();
1048 this._dataGrid.setStickToBottom(true);
1049 this.scheduleRefresh();
1050 }
1051
1052 /**
1053 * @param {string} filterString
1054 */
1055 setTextFilterValue(filterString) {
1056 this._textFilterUI.setValue(filterString);
1057 this._dataURLFilterUI.setChecked(false);
1058 this._resourceCategoryFilterUI.reset();
1059 }
1060
1061 /**
1062 * @param {!SDK.NetworkRequest} request
1063 */
1064 _createNodeForRequest(request) {
1065 const node = new Network.NetworkRequestNode(this, request);
1066 request[Network.NetworkLogView._networkNodeSymbol] = node;
1067 node[Network.NetworkLogView._isFilteredOutSymbol] = true;
1068
1069 for (let redirect = request.redirectSource(); redirect; redirect = redirect.redirectSource())
1070 this._refreshRequest(redirect);
1071 return node;
1072 }
1073
1074 /**
1075 * @param {!Common.Event} event
1076 */
1077 _onRequestUpdated(event) {
1078 const request = /** @type {!SDK.NetworkRequest} */ (event.data);
1079 this._refreshRequest(request);
1080 }
1081
1082 /**
1083 * @param {!SDK.NetworkRequest} request
1084 */
1085 _refreshRequest(request) {
1086 Network.NetworkLogView._subdomains(request.domain)
1087 .forEach(
1088 this._suggestionBuilder.addItem.bind(this._suggestionBuilder, Network.NetworkLogView.FilterType.Domain));
1089 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.Method, request.requestMethod);
1090 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.MimeType, request.mimeType);
1091 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.Scheme, '' + request.scheme);
1092 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.StatusCode, '' + request.statusCode);
1093
1094 const priority = request.priority();
1095 if (priority) {
1096 this._suggestionBuilder.addItem(
1097 Network.NetworkLogView.FilterType.Priority, PerfUI.uiLabelForNetworkPriority(priority));
1098 }
1099
1100 if (request.mixedContentType !== Protocol.Security.MixedContentType.None) {
1101 this._suggestionBuilder.addItem(
1102 Network.NetworkLogView.FilterType.MixedContent, Network.NetworkLogView.MixedContentFilterValues.All);
1103 }
1104
1105 if (request.mixedContentType === Protocol.Security.MixedContentType.OptionallyBlockable) {
1106 this._suggestionBuilder.addItem(
1107 Network.NetworkLogView.FilterType.MixedContent, Network.NetworkLogView.MixedContentFilterValues.Displayed);
1108 }
1109
1110 if (request.mixedContentType === Protocol.Security.MixedContentType.Blockable) {
1111 const suggestion = request.wasBlocked() ? Network.NetworkLogView.MixedContentFilterValues.Blocked :
1112 Network.NetworkLogView.MixedContentFilterValues.BlockOverridden;
1113 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.MixedContent, suggestion);
1114 }
1115
1116 const responseHeaders = request.responseHeaders;
1117 for (let i = 0, l = responseHeaders.length; i < l; ++i)
1118 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.HasResponseHeader, responseHeaders[i].name);
1119 const cookies = request.responseCookies;
1120 for (let i = 0, l = cookies ? cookies.length : 0; i < l; ++i) {
1121 const cookie = cookies[i];
1122 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.SetCookieDomain, cookie.domain());
1123 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.SetCookieName, cookie.name());
1124 this._suggestionBuilder.addItem(Network.NetworkLogView.FilterType.SetCookieValue, cookie.value());
1125 }
1126
1127 this._staleRequests.add(request);
1128 this.scheduleRefresh();
1129 }
1130
1131 /**
1132 * @return {number}
1133 */
1134 rowHeight() {
1135 return this._rowHeight;
1136 }
1137
1138 /**
1139 * @param {boolean} gridMode
1140 */
1141 switchViewMode(gridMode) {
1142 this._columns.switchViewMode(gridMode);
1143 }
1144
1145 /**
1146 * @param {!UI.ContextMenu} contextMenu
1147 * @param {!SDK.NetworkRequest} request
1148 */
1149 handleContextMenuForRequest(contextMenu, request) {
1150 contextMenu.appendApplicableItems(request);
1151 let copyMenu = contextMenu.clipboardSection().appendSubMenuItem(Common.UIString('Copy'));
1152 const footerSection = copyMenu.footerSection();
1153 if (request) {
1154 copyMenu.defaultSection().appendItem(
1155 UI.copyLinkAddressLabel(), InspectorFrontendHost.copyText.bind(InspectorFrontendHost, request.contentURL()));
1156 if (request.requestHeadersText()) {
1157 copyMenu.defaultSection().appendItem(
1158 Common.UIString('Copy request headers'), Network.NetworkLogView._copyRequestHeaders.bind(null, request));
1159 }
1160
1161 if (request.responseHeadersText) {
1162 copyMenu.defaultSection().appendItem(
1163 Common.UIString('Copy response headers'), Network.NetworkLogView._copyResponseHeaders.bind(null, request));
1164 }
1165
1166 if (request.finished) {
1167 copyMenu.defaultSection().appendItem(
1168 Common.UIString('Copy response'), Network.NetworkLogView._copyResponse.bind(null, request));
1169 }
1170
Harley Libcf41f92018-09-10 18:01:131171 const disableIfBlob = request.isBlobRequest();
Blink Reformat4c46d092018-04-07 15:32:371172 if (Host.isWin()) {
1173 footerSection.appendItem(
Harley Libcf41f92018-09-10 18:01:131174 Common.UIString('Copy as PowerShell'), this._copyPowerShellCommand.bind(this, request), disableIfBlob);
Blink Reformat4c46d092018-04-07 15:32:371175 footerSection.appendItem(
Harley Libcf41f92018-09-10 18:01:131176 Common.UIString('Copy as fetch'), this._copyFetchCall.bind(this, request), disableIfBlob);
Blink Reformat4c46d092018-04-07 15:32:371177 footerSection.appendItem(
Harley Libcf41f92018-09-10 18:01:131178 Common.UIString('Copy as cURL (cmd)'), this._copyCurlCommand.bind(this, request, 'win'), disableIfBlob);
1179 footerSection.appendItem(
1180 Common.UIString('Copy as cURL (bash)'), this._copyCurlCommand.bind(this, request, 'unix'), disableIfBlob);
Blink Reformat4c46d092018-04-07 15:32:371181 footerSection.appendItem(Common.UIString('Copy all as PowerShell'), this._copyAllPowerShellCommand.bind(this));
1182 footerSection.appendItem(Common.UIString('Copy all as fetch'), this._copyAllFetchCall.bind(this));
1183 footerSection.appendItem(Common.UIString('Copy all as cURL (cmd)'), this._copyAllCurlCommand.bind(this, 'win'));
1184 footerSection.appendItem(
1185 Common.UIString('Copy all as cURL (bash)'), this._copyAllCurlCommand.bind(this, 'unix'));
1186 } else {
Harley Libcf41f92018-09-10 18:01:131187 footerSection.appendItem(
1188 Common.UIString('Copy as fetch'), this._copyFetchCall.bind(this, request), disableIfBlob);
1189 footerSection.appendItem(
1190 Common.UIString('Copy as cURL'), this._copyCurlCommand.bind(this, request, 'unix'), disableIfBlob);
Blink Reformat4c46d092018-04-07 15:32:371191 footerSection.appendItem(Common.UIString('Copy all as fetch'), this._copyAllFetchCall.bind(this));
1192 footerSection.appendItem(Common.UIString('Copy all as cURL'), this._copyAllCurlCommand.bind(this, 'unix'));
1193 }
1194 } else {
1195 copyMenu = contextMenu.clipboardSection().appendSubMenuItem(Common.UIString('Copy'));
1196 }
1197 footerSection.appendItem(Common.UIString('Copy all as HAR'), this._copyAll.bind(this));
1198
Joey Arhar076efbc2018-11-06 00:24:201199 contextMenu.saveSection().appendItem(Common.UIString('Save all as HAR with content'), this._exportAll.bind(this));
Blink Reformat4c46d092018-04-07 15:32:371200
1201 contextMenu.editSection().appendItem(Common.UIString('Clear browser cache'), this._clearBrowserCache.bind(this));
1202 contextMenu.editSection().appendItem(
1203 Common.UIString('Clear browser cookies'), this._clearBrowserCookies.bind(this));
1204
1205 if (request) {
1206 const maxBlockedURLLength = 20;
1207 const manager = SDK.multitargetNetworkManager;
1208 let patterns = manager.blockedPatterns();
1209
1210 const urlWithoutScheme = request.parsedURL.urlWithoutScheme();
1211 if (urlWithoutScheme && !patterns.find(pattern => pattern.url === urlWithoutScheme)) {
1212 contextMenu.debugSection().appendItem(
1213 Common.UIString('Block request URL'), addBlockedURL.bind(null, urlWithoutScheme));
1214 } else if (urlWithoutScheme) {
1215 const croppedURL = urlWithoutScheme.trimMiddle(maxBlockedURLLength);
1216 contextMenu.debugSection().appendItem(
1217 Common.UIString('Unblock %s', croppedURL), removeBlockedURL.bind(null, urlWithoutScheme));
1218 }
1219
1220 const domain = request.parsedURL.domain();
1221 if (domain && !patterns.find(pattern => pattern.url === domain)) {
1222 contextMenu.debugSection().appendItem(
1223 Common.UIString('Block request domain'), addBlockedURL.bind(null, domain));
1224 } else if (domain) {
1225 const croppedDomain = domain.trimMiddle(maxBlockedURLLength);
1226 contextMenu.debugSection().appendItem(
1227 Common.UIString('Unblock %s', croppedDomain), removeBlockedURL.bind(null, domain));
1228 }
1229
1230 if (SDK.NetworkManager.canReplayRequest(request)) {
1231 contextMenu.debugSection().appendItem(
1232 Common.UIString('Replay XHR'), SDK.NetworkManager.replayRequest.bind(null, request));
1233 }
1234
1235 /**
1236 * @param {string} url
1237 */
1238 function addBlockedURL(url) {
1239 patterns.push({enabled: true, url: url});
1240 manager.setBlockedPatterns(patterns);
1241 manager.setBlockingEnabled(true);
1242 UI.viewManager.showView('network.blocked-urls');
1243 }
1244
1245 /**
1246 * @param {string} url
1247 */
1248 function removeBlockedURL(url) {
1249 patterns = patterns.filter(pattern => pattern.url !== url);
1250 manager.setBlockedPatterns(patterns);
1251 UI.viewManager.showView('network.blocked-urls');
1252 }
1253 }
1254 }
1255
1256 _harRequests() {
Pavel Feldman18d13562018-07-31 03:31:181257 const httpRequests = SDK.networkLog.requests().filter(Network.NetworkLogView.HTTPRequestsFilter);
Blink Reformat4c46d092018-04-07 15:32:371258 return httpRequests.filter(Network.NetworkLogView.FinishedRequestsFilter);
1259 }
1260
1261 async _copyAll() {
Pavel Feldman18d13562018-07-31 03:31:181262 const harArchive = {log: await SDK.HARLog.build(this._harRequests())};
Blink Reformat4c46d092018-04-07 15:32:371263 InspectorFrontendHost.copyText(JSON.stringify(harArchive, null, 2));
1264 }
1265
1266 /**
1267 * @param {!SDK.NetworkRequest} request
1268 * @param {string} platform
1269 */
1270 async _copyCurlCommand(request, platform) {
1271 const command = await this._generateCurlCommand(request, platform);
1272 InspectorFrontendHost.copyText(command);
1273 }
1274
1275 /**
1276 * @param {string} platform
1277 */
1278 async _copyAllCurlCommand(platform) {
Harley Libcf41f92018-09-10 18:01:131279 const commands = await this._generateAllCurlCommand(SDK.networkLog.requests(), platform);
1280 InspectorFrontendHost.copyText(commands);
Blink Reformat4c46d092018-04-07 15:32:371281 }
1282
1283 /**
1284 * @param {!SDK.NetworkRequest} request
1285 * @param {string} platform
1286 */
1287 async _copyFetchCall(request, platform) {
1288 const command = await this._generateFetchCall(request);
1289 InspectorFrontendHost.copyText(command);
1290 }
1291
1292 async _copyAllFetchCall() {
Harley Libcf41f92018-09-10 18:01:131293 const commands = await this._generateAllFetchCall(SDK.networkLog.requests());
1294 InspectorFrontendHost.copyText(commands);
Blink Reformat4c46d092018-04-07 15:32:371295 }
1296
1297 /**
1298 * @param {!SDK.NetworkRequest} request
1299 */
1300 async _copyPowerShellCommand(request) {
1301 const command = await this._generatePowerShellCommand(request);
1302 InspectorFrontendHost.copyText(command);
1303 }
1304
1305 async _copyAllPowerShellCommand() {
Harley Libcf41f92018-09-10 18:01:131306 const commands = this._generateAllPowerShellCommand(SDK.networkLog.requests());
1307 InspectorFrontendHost.copyText(commands);
Blink Reformat4c46d092018-04-07 15:32:371308 }
1309
1310 async _exportAll() {
1311 const url = SDK.targetManager.mainTarget().inspectedURL();
1312 const parsedURL = url.asParsedURL();
1313 const filename = parsedURL ? parsedURL.host : 'network-log';
1314 const stream = new Bindings.FileOutputStream();
1315
1316 if (!await stream.open(filename + '.har'))
1317 return;
1318
1319 const progressIndicator = new UI.ProgressIndicator();
1320 this._progressBarContainer.appendChild(progressIndicator.element);
1321 await Network.HARWriter.write(stream, this._harRequests(), progressIndicator);
1322 progressIndicator.done();
1323 stream.close();
1324 }
1325
1326 _clearBrowserCache() {
1327 if (confirm(Common.UIString('Are you sure you want to clear browser cache?')))
1328 SDK.multitargetNetworkManager.clearBrowserCache();
1329 }
1330
1331 _clearBrowserCookies() {
1332 if (confirm(Common.UIString('Are you sure you want to clear browser cookies?')))
1333 SDK.multitargetNetworkManager.clearBrowserCookies();
1334 }
1335
1336 _removeAllHighlights() {
1337 this.removeAllNodeHighlights();
1338 for (let i = 0; i < this._highlightedSubstringChanges.length; ++i)
1339 UI.revertDomChanges(this._highlightedSubstringChanges[i]);
1340 this._highlightedSubstringChanges = [];
1341 }
1342
1343 /**
1344 * @param {!Network.NetworkRequestNode} node
1345 * @return {boolean}
1346 */
1347 _applyFilter(node) {
1348 const request = node.request();
1349 if (this._timeFilter && !this._timeFilter(request))
1350 return false;
1351 const categoryName = request.resourceType().category().title;
1352 if (!this._resourceCategoryFilterUI.accept(categoryName))
1353 return false;
1354 if (this._dataURLFilterUI.checked() && request.parsedURL.isDataURL())
1355 return false;
1356 if (request.statusText === 'Service Worker Fallback Required')
1357 return false;
1358 for (let i = 0; i < this._filters.length; ++i) {
1359 if (!this._filters[i](request))
1360 return false;
1361 }
1362 return true;
1363 }
1364
1365 /**
1366 * @param {string} query
1367 */
1368 _parseFilterQuery(query) {
1369 const descriptors = this._filterParser.parse(query);
1370 this._filters = descriptors.map(descriptor => {
1371 const key = descriptor.key;
1372 const text = descriptor.text || '';
1373 const regex = descriptor.regex;
1374 let filter;
1375 if (key) {
1376 const defaultText = (key + ':' + text).escapeForRegExp();
1377 filter = this._createSpecialFilter(/** @type {!Network.NetworkLogView.FilterType} */ (key), text) ||
1378 Network.NetworkLogView._requestPathFilter.bind(null, new RegExp(defaultText, 'i'));
1379 } else if (descriptor.regex) {
1380 filter = Network.NetworkLogView._requestPathFilter.bind(null, /** @type {!RegExp} */ (regex));
1381 } else {
1382 filter = Network.NetworkLogView._requestPathFilter.bind(null, new RegExp(text.escapeForRegExp(), 'i'));
1383 }
1384 return descriptor.negative ? Network.NetworkLogView._negativeFilter.bind(null, filter) : filter;
1385 });
1386 }
1387
1388 /**
1389 * @param {!Network.NetworkLogView.FilterType} type
1390 * @param {string} value
1391 * @return {?Network.NetworkLogView.Filter}
1392 */
1393 _createSpecialFilter(type, value) {
1394 switch (type) {
1395 case Network.NetworkLogView.FilterType.Domain:
1396 return Network.NetworkLogView._createRequestDomainFilter(value);
1397
1398 case Network.NetworkLogView.FilterType.HasResponseHeader:
1399 return Network.NetworkLogView._requestResponseHeaderFilter.bind(null, value);
1400
1401 case Network.NetworkLogView.FilterType.Is:
1402 if (value.toLowerCase() === Network.NetworkLogView.IsFilterType.Running)
1403 return Network.NetworkLogView._runningRequestFilter;
1404 if (value.toLowerCase() === Network.NetworkLogView.IsFilterType.FromCache)
1405 return Network.NetworkLogView._fromCacheRequestFilter;
1406 break;
1407
1408 case Network.NetworkLogView.FilterType.LargerThan:
1409 return this._createSizeFilter(value.toLowerCase());
1410
1411 case Network.NetworkLogView.FilterType.Method:
1412 return Network.NetworkLogView._requestMethodFilter.bind(null, value);
1413
1414 case Network.NetworkLogView.FilterType.MimeType:
1415 return Network.NetworkLogView._requestMimeTypeFilter.bind(null, value);
1416
1417 case Network.NetworkLogView.FilterType.MixedContent:
1418 return Network.NetworkLogView._requestMixedContentFilter.bind(
1419 null, /** @type {!Network.NetworkLogView.MixedContentFilterValues} */ (value));
1420
1421 case Network.NetworkLogView.FilterType.Scheme:
1422 return Network.NetworkLogView._requestSchemeFilter.bind(null, value);
1423
1424 case Network.NetworkLogView.FilterType.SetCookieDomain:
1425 return Network.NetworkLogView._requestSetCookieDomainFilter.bind(null, value);
1426
1427 case Network.NetworkLogView.FilterType.SetCookieName:
1428 return Network.NetworkLogView._requestSetCookieNameFilter.bind(null, value);
1429
1430 case Network.NetworkLogView.FilterType.SetCookieValue:
1431 return Network.NetworkLogView._requestSetCookieValueFilter.bind(null, value);
1432
1433 case Network.NetworkLogView.FilterType.Priority:
1434 return Network.NetworkLogView._requestPriorityFilter.bind(null, PerfUI.uiLabelToNetworkPriority(value));
1435
1436 case Network.NetworkLogView.FilterType.StatusCode:
1437 return Network.NetworkLogView._statusCodeFilter.bind(null, value);
1438 }
1439 return null;
1440 }
1441
1442 /**
1443 * @param {string} value
1444 * @return {?Network.NetworkLogView.Filter}
1445 */
1446 _createSizeFilter(value) {
1447 let multiplier = 1;
1448 if (value.endsWith('k')) {
1449 multiplier = 1024;
1450 value = value.substring(0, value.length - 1);
1451 } else if (value.endsWith('m')) {
1452 multiplier = 1024 * 1024;
1453 value = value.substring(0, value.length - 1);
1454 }
1455 const quantity = Number(value);
1456 if (isNaN(quantity))
1457 return null;
1458 return Network.NetworkLogView._requestSizeLargerThanFilter.bind(null, quantity * multiplier);
1459 }
1460
1461 _filterRequests() {
1462 this._removeAllHighlights();
1463 this._invalidateAllItems();
1464 }
1465
1466 /**
1467 * @param {!SDK.NetworkRequest} request
1468 * @return {?Network.NetworkRequestNode}
1469 */
1470 _reveal(request) {
1471 this.removeAllNodeHighlights();
1472 const node = request[Network.NetworkLogView._networkNodeSymbol];
1473 if (!node || !node.dataGrid)
1474 return null;
1475 node.reveal();
1476 return node;
1477 }
1478
1479 /**
1480 * @param {!SDK.NetworkRequest} request
1481 */
1482 revealAndHighlightRequest(request) {
1483 const node = this._reveal(request);
1484 if (node)
1485 this._highlightNode(node);
1486 }
1487
1488 /**
1489 * @param {!SDK.NetworkRequest} request
1490 */
1491 selectRequest(request) {
Eugene Ostroukhovb600f662018-05-09 00:18:141492 this.setTextFilterValue('');
Blink Reformat4c46d092018-04-07 15:32:371493 const node = this._reveal(request);
1494 if (node)
1495 node.select();
1496 }
1497
1498 removeAllNodeHighlights() {
1499 if (this._highlightedNode) {
1500 this._highlightedNode.element().classList.remove('highlighted-row');
1501 this._highlightedNode = null;
1502 }
1503 }
1504
1505 /**
1506 * @param {!Network.NetworkRequestNode} node
1507 */
1508 _highlightNode(node) {
1509 UI.runCSSAnimationOnce(node.element(), 'highlighted-row');
1510 this._highlightedNode = node;
1511 }
1512
1513 /**
Harley Libcf41f92018-09-10 18:01:131514 * @param {!Array<!SDK.NetworkRequest>} requests
1515 * @return {!Array<!SDK.NetworkRequest>}
1516 */
1517 _filterOutBlobRequests(requests) {
1518 return requests.filter(request => !request.isBlobRequest());
1519 }
1520
1521 /**
Blink Reformat4c46d092018-04-07 15:32:371522 * @param {!SDK.NetworkRequest} request
1523 * @return {!Promise<string>}
1524 */
1525 async _generateFetchCall(request) {
1526 const ignoredHeaders = {
1527 // Internal headers
1528 'method': 1,
1529 'path': 1,
1530 'scheme': 1,
1531 'version': 1,
1532
1533 // Unsafe headers
1534 // Keep this list synchronized with src/net/http/http_util.cc
1535 'accept-charset': 1,
1536 'accept-encoding': 1,
1537 'access-control-request-headers': 1,
1538 'access-control-request-method': 1,
1539 'connection': 1,
1540 'content-length': 1,
1541 'cookie': 1,
1542 'cookie2': 1,
1543 'date': 1,
1544 'dnt': 1,
1545 'expect': 1,
1546 'host': 1,
1547 'keep-alive': 1,
1548 'origin': 1,
1549 'referer': 1,
1550 'te': 1,
1551 'trailer': 1,
1552 'transfer-encoding': 1,
1553 'upgrade': 1,
1554 'via': 1,
1555 // TODO(phistuck) - remove this once crbug.com/571722 is fixed.
1556 'user-agent': 1
1557 };
1558
1559 const credentialHeaders = {'cookie': 1, 'authorization': 1};
1560
1561 const url = JSON.stringify(request.url());
1562
1563 const requestHeaders = request.requestHeaders();
1564 const headerData = requestHeaders.reduce((result, header) => {
1565 const name = header.name;
1566
1567 if (!ignoredHeaders[name.toLowerCase()] && !name.includes(':'))
1568 result.append(name, header.value);
1569
1570 return result;
1571 }, new Headers());
1572
1573 const headers = {};
1574 for (const headerArray of headerData)
PhistucK6ed0a3e2018-08-04 06:28:411575 headers[headerArray[0]] = headerArray[1];
Blink Reformat4c46d092018-04-07 15:32:371576
1577 const credentials =
1578 request.requestCookies || requestHeaders.some(({name}) => credentialHeaders[name.toLowerCase()]) ? 'include' :
1579 'omit';
1580
1581 const referrerHeader = requestHeaders.find(({name}) => name.toLowerCase() === 'referer');
1582
1583 const referrer = referrerHeader ? referrerHeader.value : void 0;
1584
1585 const referrerPolicy = request.referrerPolicy() || void 0;
1586
1587 const requestBody = await request.requestFormData();
1588
1589 const fetchOptions = {
1590 credentials,
PhistucK6ed0a3e2018-08-04 06:28:411591 headers: Object.keys(headers).length ? headers : void 0,
Blink Reformat4c46d092018-04-07 15:32:371592 referrer,
1593 referrerPolicy,
1594 body: requestBody,
1595 method: request.requestMethod,
1596 mode: 'cors'
1597 };
1598
1599 const options = JSON.stringify(fetchOptions);
1600 return `fetch(${url}, ${options});`;
1601 }
1602
1603 /**
Harley Libcf41f92018-09-10 18:01:131604 * @param {!Array<!SDK.NetworkRequest>} requests
1605 * @return {!Promise<string>}
1606 */
1607 async _generateAllFetchCall(requests) {
1608 const nonBlobRequests = this._filterOutBlobRequests(requests);
1609 const commands = await Promise.all(nonBlobRequests.map(request => this._generateFetchCall(request)));
1610 return commands.join(' ;\n');
1611 }
1612
1613 /**
Blink Reformat4c46d092018-04-07 15:32:371614 * @param {!SDK.NetworkRequest} request
1615 * @param {string} platform
1616 * @return {!Promise<string>}
1617 */
1618 async _generateCurlCommand(request, platform) {
1619 let command = ['curl'];
1620 // These headers are derived from URL (except "version") and would be added by cURL anyway.
1621 const ignoredHeaders = {'host': 1, 'method': 1, 'path': 1, 'scheme': 1, 'version': 1};
1622
1623 function escapeStringWin(str) {
1624 /* If there are no new line characters do not escape the " characters
1625 since it only uglifies the command.
1626
1627 Because cmd.exe parser and MS Crt arguments parsers use some of the
1628 same escape characters, they can interact with each other in
1629 horrible ways, the order of operations is critical.
1630
1631 Replace \ with \\ first because it is an escape character for certain
1632 conditions in both parsers.
1633
1634 Replace all " with \" to ensure the first parser does not remove it.
1635
1636 Then escape all characters we are not sure about with ^ to ensure it
1637 gets to MS Crt parser safely.
1638
1639 The % character is special because MS Crt parser will try and look for
1640 ENV variables and fill them in it's place. We cannot escape them with %
1641 and cannot escape them with ^ (because it's cmd.exe's escape not MS Crt
1642 parser); So we can get cmd.exe parser to escape the character after it,
1643 if it is followed by a valid beginning character of an ENV variable.
1644 This ensures we do not try and double escape another ^ if it was placed
1645 by the previous replace.
1646
1647 Lastly we replace new lines with ^ and TWO new lines because the first
1648 new line is there to enact the escape command the second is the character
1649 to escape (in this case new line).
1650 */
1651 const encapsChars = /[\r\n]/.test(str) ? '^"' : '"';
1652 return encapsChars +
1653 str.replace(/\\/g, '\\\\')
1654 .replace(/"/g, '\\"')
1655 .replace(/[^a-zA-Z0-9\s_\-:=+~'\/.',?;()*`]/g, '^$&')
1656 .replace(/%(?=[a-zA-Z0-9_])/g, '%^')
1657 .replace(/\r\n|[\n\r]/g, '^\n\n') +
1658 encapsChars;
1659 }
1660
1661 /**
1662 * @param {string} str
1663 * @return {string}
1664 */
1665 function escapeStringPosix(str) {
1666 /**
1667 * @param {string} x
1668 * @return {string}
1669 */
1670 function escapeCharacter(x) {
Erik Luoaa676752018-08-21 05:52:221671 const code = x.charCodeAt(0);
1672 // Add leading zero when needed to not care about the next character.
1673 return code < 16 ? '\\u0' + code.toString(16) : '\\u' + code.toString(16);
Blink Reformat4c46d092018-04-07 15:32:371674 }
1675
Erik Luoaa676752018-08-21 05:52:221676 if (/[\u0000-\u001f\u007f-\u009f]|\'/.test(str)) {
Blink Reformat4c46d092018-04-07 15:32:371677 // Use ANSI-C quoting syntax.
1678 return '$\'' +
1679 str.replace(/\\/g, '\\\\')
1680 .replace(/\'/g, '\\\'')
1681 .replace(/\n/g, '\\n')
1682 .replace(/\r/g, '\\r')
Erik Luoaa676752018-08-21 05:52:221683 .replace(/[\u0000-\u001f\u007f-\u009f]/g, escapeCharacter) +
Blink Reformat4c46d092018-04-07 15:32:371684 '\'';
1685 } else {
1686 // Use single quote syntax.
1687 return '\'' + str + '\'';
1688 }
1689 }
1690
1691 // cURL command expected to run on the same platform that DevTools run
1692 // (it may be different from the inspected page platform).
1693 const escapeString = platform === 'win' ? escapeStringWin : escapeStringPosix;
1694
1695 command.push(escapeString(request.url()).replace(/[[{}\]]/g, '\\$&'));
1696
1697 let inferredMethod = 'GET';
1698 const data = [];
1699 const requestContentType = request.requestContentType();
1700 const formData = await request.requestFormData();
1701 if (requestContentType && requestContentType.startsWith('application/x-www-form-urlencoded') && formData) {
1702 data.push('--data');
1703 data.push(escapeString(formData));
1704 ignoredHeaders['content-length'] = true;
1705 inferredMethod = 'POST';
1706 } else if (formData) {
1707 data.push('--data-binary');
1708 data.push(escapeString(formData));
1709 ignoredHeaders['content-length'] = true;
1710 inferredMethod = 'POST';
1711 }
1712
1713 if (request.requestMethod !== inferredMethod) {
1714 command.push('-X');
1715 command.push(request.requestMethod);
1716 }
1717
1718 const requestHeaders = request.requestHeaders();
1719 for (let i = 0; i < requestHeaders.length; i++) {
1720 const header = requestHeaders[i];
1721 const name = header.name.replace(/^:/, ''); // Translate SPDY v3 headers to HTTP headers.
1722 if (name.toLowerCase() in ignoredHeaders)
1723 continue;
1724 command.push('-H');
1725 command.push(escapeString(name + ': ' + header.value));
1726 }
1727 command = command.concat(data);
1728 command.push('--compressed');
1729
1730 if (request.securityState() === Protocol.Security.SecurityState.Insecure)
1731 command.push('--insecure');
1732 return command.join(' ');
1733 }
1734
1735 /**
Harley Libcf41f92018-09-10 18:01:131736 * @param {!Array<!SDK.NetworkRequest>} requests
1737 * @param {string} platform
1738 * @return {!Promise<string>}
1739 */
1740 async _generateAllCurlCommand(requests, platform) {
1741 const nonBlobRequests = this._filterOutBlobRequests(requests);
1742 const commands = await Promise.all(nonBlobRequests.map(request => this._generateCurlCommand(request, platform)));
1743 if (platform === 'win')
1744 return commands.join(' &\r\n');
1745 else
1746 return commands.join(' ;\n');
1747 }
1748
1749 /**
Blink Reformat4c46d092018-04-07 15:32:371750 * @param {!SDK.NetworkRequest} request
1751 * @return {!Promise<string>}
1752 */
1753 async _generatePowerShellCommand(request) {
1754 const command = ['Invoke-WebRequest'];
1755 const ignoredHeaders =
1756 new Set(['host', 'connection', 'proxy-connection', 'content-length', 'expect', 'range', 'content-type']);
1757
1758 /**
1759 * @param {string} str
1760 * @return {string}
1761 */
1762 function escapeString(str) {
1763 return '"' +
1764 str.replace(/[`\$"]/g, '`$&').replace(/[^\x20-\x7E]/g, char => '$([char]' + char.charCodeAt(0) + ')') + '"';
1765 }
1766
1767 command.push('-Uri');
1768 command.push(escapeString(request.url()));
1769
1770 if (request.requestMethod !== 'GET') {
1771 command.push('-Method');
1772 command.push(escapeString(request.requestMethod));
1773 }
1774
1775 const requestHeaders = request.requestHeaders();
1776 const headerNameValuePairs = [];
1777 for (const header of requestHeaders) {
1778 const name = header.name.replace(/^:/, ''); // Translate h2 headers to HTTP headers.
1779 if (ignoredHeaders.has(name.toLowerCase()))
1780 continue;
1781 headerNameValuePairs.push(escapeString(name) + '=' + escapeString(header.value));
1782 }
1783 if (headerNameValuePairs.length) {
1784 command.push('-Headers');
1785 command.push('@{' + headerNameValuePairs.join('; ') + '}');
1786 }
1787
1788 const contentTypeHeader = requestHeaders.find(({name}) => name.toLowerCase() === 'content-type');
1789 if (contentTypeHeader) {
1790 command.push('-ContentType');
1791 command.push(escapeString(contentTypeHeader.value));
1792 }
1793
1794 const formData = await request.requestFormData();
1795 if (formData) {
1796 command.push('-Body');
1797 const body = escapeString(formData);
1798 if (/[^\x20-\x7E]/.test(formData))
1799 command.push('([System.Text.Encoding]::UTF8.GetBytes(' + body + '))');
1800 else
1801 command.push(body);
1802 }
1803
1804 return command.join(' ');
1805 }
Harley Libcf41f92018-09-10 18:01:131806
1807 /**
1808 * @param {!Array<!SDK.NetworkRequest>} requests
1809 * @return {!Promise<string>}
1810 */
1811 async _generateAllPowerShellCommand(requests) {
1812 const nonBlobRequests = this._filterOutBlobRequests(requests);
1813 const commands = await Promise.all(nonBlobRequests.map(request => this._generatePowerShellCommand(request)));
1814 return commands.join(';\r\n');
1815 }
Blink Reformat4c46d092018-04-07 15:32:371816};
1817
1818Network.NetworkLogView._isFilteredOutSymbol = Symbol('isFilteredOut');
1819Network.NetworkLogView._networkNodeSymbol = Symbol('NetworkNode');
1820
1821Network.NetworkLogView.HTTPSchemas = {
1822 'http': true,
1823 'https': true,
1824 'ws': true,
1825 'wss': true
1826};
1827
1828/** @enum {symbol} */
1829Network.NetworkLogView.Events = {
1830 RequestSelected: Symbol('RequestSelected')
1831};
1832
1833/** @enum {string} */
1834Network.NetworkLogView.FilterType = {
1835 Domain: 'domain',
1836 HasResponseHeader: 'has-response-header',
1837 Is: 'is',
1838 LargerThan: 'larger-than',
1839 Method: 'method',
1840 MimeType: 'mime-type',
1841 MixedContent: 'mixed-content',
1842 Priority: 'priority',
1843 Scheme: 'scheme',
1844 SetCookieDomain: 'set-cookie-domain',
1845 SetCookieName: 'set-cookie-name',
1846 SetCookieValue: 'set-cookie-value',
1847 StatusCode: 'status-code'
1848};
1849
1850/** @enum {string} */
1851Network.NetworkLogView.MixedContentFilterValues = {
1852 All: 'all',
1853 Displayed: 'displayed',
1854 Blocked: 'blocked',
1855 BlockOverridden: 'block-overridden'
1856};
1857
1858/** @enum {string} */
1859Network.NetworkLogView.IsFilterType = {
1860 Running: 'running',
1861 FromCache: 'from-cache'
1862};
1863
1864/** @type {!Array<string>} */
1865Network.NetworkLogView._searchKeys =
1866 Object.keys(Network.NetworkLogView.FilterType).map(key => Network.NetworkLogView.FilterType[key]);
1867
1868/** @typedef {function(!SDK.NetworkRequest): boolean} */
1869Network.NetworkLogView.Filter;
1870
1871/**
1872 * @interface
1873 */
1874Network.GroupLookupInterface = function() {};
1875
1876Network.GroupLookupInterface.prototype = {
1877 /**
1878 * @param {!SDK.NetworkRequest} request
1879 * @return {?Network.NetworkGroupNode}
1880 */
1881 groupNodeForRequest: function(request) {},
1882
1883 reset: function() {}
1884};