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