Changeset 223065 in webkit
- Timestamp:
- Oct 9, 2017 1:38:09 PM (7 years ago)
- Location:
- trunk/Source/WebInspectorUI
- Files:
-
- 2 added
- 10 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/WebInspectorUI/ChangeLog
r223059 r223065 1 2017-10-09 Joseph Pecoraro <pecoraro@apple.com> 2 3 Web Inspector: Network Tab - Filter resources based on URL / Text Content 4 https://bugs.webkit.org/show_bug.cgi?id=178071 5 <rdar://problem/34071562> 6 7 Reviewed by Brian Burg. 8 9 * Localizations/en.lproj/localizedStrings.js: 10 New strings. 11 12 * UserInterface/Views/FilterBar.css: 13 (.filter-bar.active > input[type="search"]::-webkit-search-decoration): 14 (.filter-bar.indicating-progress > input[type="search"]::-webkit-search-decoration): 15 New icon for progress / active states. 16 17 * UserInterface/Views/FilterBar.js: 18 (WI.FilterBar.prototype.get inputField): 19 (WI.FilterBar.prototype.get placeholder): 20 (WI.FilterBar.prototype.set placeholder): 21 (WI.FilterBar.prototype.get incremental): 22 (WI.FilterBar.prototype.set incremental): 23 (WI.FilterBar.prototype.get indicatingProgress): 24 (WI.FilterBar.prototype.set indicatingProgress): 25 (WI.FilterBar.prototype.get indicatingActive): 26 (WI.FilterBar.prototype.set indicatingActive): 27 (WI.FilterBar.prototype._handleFilterInputEvent): 28 When incremental is set to false on the FilterBar still dispatch an 29 event when the textfield clears. 30 31 * UserInterface/Images/FilterFieldActiveGlyph.svg: Added. 32 * UserInterface/Images/gtk/FilterFieldActiveGlyph.svg: Added. 33 New blue icon for active state. 34 35 * UserInterface/Controllers/FrameResourceManager.js: 36 (WI.FrameResourceManager.prototype.resourceForIdentifier): 37 Accessor for arbitrary resource. 38 39 * UserInterface/Views/NetworkTableContentView.css: 40 (.content-view.network .navigation-bar .filter-bar): 41 (.content-view.network .warning-banner): 42 (body[dir=ltr] .content-view.network .warning-banner): 43 (body[dir=rtl] .content-view.network .warning-banner): 44 (.content-view.network .warning-banner > a): 45 Warning banner when the filter produces no results. This matches the 46 warning in the Debugger tab when breakpoints are disabled. 47 48 * UserInterface/Views/ScopeBar.js: 49 (WI.ScopeBar.prototype.resetToDefault): 50 Provide a way to easily reset a scope bar to the default item. 51 52 * UserInterface/Views/RadioButtonNavigationItem.css: 53 (.navigation-bar .item.radio.button.text-only:active): 54 * UserInterface/Views/ScopeBar.css: 55 (.scope-bar > li:active): 56 Cleanup some styles that should be using a variable. 57 58 * UserInterface/Views/NetworkTableContentView.js: 59 (WI.NetworkTableContentView): 60 (WI.NetworkTableContentView.prototype.get filterNavigationItems): 61 (WI.NetworkTableContentView.prototype.layout): 62 (WI.NetworkTableContentView.prototype._processPendingEntries): 63 (WI.NetworkTableContentView.prototype._checkTextFilterAgainstFinishedResource): 64 (WI.NetworkTableContentView.prototype._checkTextFilterAgainstFailedResource): 65 (WI.NetworkTableContentView.prototype._updateTextFilterActiveIndicator): 66 (WI.NetworkTableContentView.prototype._updateEmptyFilterResultsWarning): 67 (WI.NetworkTableContentView.prototype._showEmptyFilterResultsWarning): 68 (WI.NetworkTableContentView.prototype._hideEmptyFilterResultsWarning): 69 (WI.NetworkTableContentView.prototype._positionEmptyFilterMessage): 70 (WI.NetworkTableContentView.prototype._resourceLoadingDidFinish): 71 (WI.NetworkTableContentView.prototype._resourceLoadingDidFail): 72 (WI.NetworkTableContentView.prototype._networkTimelineRecordAdded): 73 (WI.NetworkTableContentView.prototype._insertResourceAndReloadTable): 74 (WI.NetworkTableContentView.prototype._hasTypeFilter): 75 (WI.NetworkTableContentView.prototype._hasTextFilter): 76 (WI.NetworkTableContentView.prototype._hasActiveFilter): 77 (WI.NetworkTableContentView.prototype._passTypeFilter): 78 (WI.NetworkTableContentView.prototype._passTextFilter): 79 (WI.NetworkTableContentView.prototype._passFilter): 80 (WI.NetworkTableContentView.prototype._updateFilteredEntries): 81 (WI.NetworkTableContentView.prototype._resetFilters): 82 (WI.NetworkTableContentView.prototype._textFilterDidChange): 83 (WI.NetworkTableContentView.prototype._tableNameColumnDidChangeWidth): 84 There are now two filters. 85 86 - FilterBar - Filters URL and Full Text Content 87 - ScopeBar - Filters Resource Type 88 89 The text content filter is asynchronous. We reuse the existing Search 90 functionality when filtering on text. We need to defer text content 91 filtering until the resource finishes loading. 92 1 93 2017-10-09 Joseph Pecoraro <pecoraro@apple.com> 2 94 -
trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
r223058 r223065 182 182 localizedStrings["Clear Network Items (%s)"] = "Clear Network Items (%s)"; 183 183 localizedStrings["Clear Timeline (%s)"] = "Clear Timeline (%s)"; 184 localizedStrings["Clear filters"] = "Clear filters"; 184 185 localizedStrings["Clear focus"] = "Clear focus"; 185 186 localizedStrings["Clear log (%s or %s)"] = "Clear log (%s or %s)"; … … 404 405 localizedStrings["Fill Mode"] = "Fill Mode"; 405 406 localizedStrings["Filter"] = "Filter"; 407 localizedStrings["Filter Full URL and Text"] = "Filter Full URL and Text"; 406 408 localizedStrings["Flexbox"] = "Flexbox"; 407 409 localizedStrings["Float"] = "Float"; -
trunk/Source/WebInspectorUI/UserInterface/Controllers/FrameResourceManager.js
r222739 r223065 66 66 } 67 67 68 resourceForRequestIdentifier(requestIdentifier) 69 { 70 return this._resourceRequestIdentifierMap.get(requestIdentifier) || null; 71 } 72 68 73 frameDidNavigate(framePayload) 69 74 { -
trunk/Source/WebInspectorUI/UserInterface/Views/FilterBar.css
r220609 r223065 91 91 -webkit-appearance: none; 92 92 } 93 94 .filter-bar.active > input[type="search"]::-webkit-search-decoration { 95 background-image: url(../Images/FilterFieldActiveGlyph.svg); 96 } 97 98 .filter-bar.indicating-progress > input[type="search"]::-webkit-search-decoration { 99 background-image: url(../Images/IndeterminateProgressSpinner1.svg); 100 background-repeat: no-repeat; 101 background-size: 100% 100%; 102 103 animation-name: discrete-spinner; 104 animation-duration: 1s; 105 animation-iteration-count: infinite; 106 animation-timing-function: step-start; 107 } -
trunk/Source/WebInspectorUI/UserInterface/Views/FilterBar.js
r220609 r223065 43 43 this._inputField.spellcheck = false; 44 44 this._inputField.incremental = true; 45 this._inputField.addEventListener("search", this._handleFilterChanged.bind(this), false); 45 this._inputField.addEventListener("search", this._handleFilterChanged.bind(this)); 46 this._inputField.addEventListener("input", this._handleFilterInputEvent.bind(this)); 46 47 this._element.appendChild(this._inputField); 47 48 … … 56 57 } 57 58 59 get inputField() 60 { 61 return this._inputField; 62 } 63 58 64 get placeholder() 59 65 { 60 return this._inputField. getAttribute("placeholder");66 return this._inputField.placeholder; 61 67 } 62 68 63 69 set placeholder(text) 64 70 { 65 this._inputField. setAttribute("placeholder", text);71 this._inputField.placeholder = text; 66 72 } 67 73 68 get in putField()74 get incremental() 69 75 { 70 return this._inputField; 76 return this._inputField.incremental; 77 } 78 79 set incremental(incremental) 80 { 81 this._inputField.incremental = incremental; 71 82 } 72 83 … … 84 95 if (oldTextValue !== this._inputField.value) 85 96 this._handleFilterChanged(); 97 } 98 99 get indicatingProgress() 100 { 101 return this._element.classList.contains("indicating-progress"); 102 } 103 104 set indicatingProgress(progress) 105 { 106 this._element.classList.toggle("indicating-progress", !!progress); 107 } 108 109 get indicatingActive() 110 { 111 return this._element.classList.contains("active"); 112 } 113 114 set indicatingActive(active) 115 { 116 this._element.classList.toggle("active", !!active); 117 } 118 119 clear() 120 { 121 this._inputField.value = ""; 122 this._inputField.value = null; // Get the placeholder to show again. 123 this._lastFilterValue = this.filters; 86 124 } 87 125 … … 143 181 } 144 182 } 183 184 _handleFilterInputEvent(event) 185 { 186 // When not incremental we still want to detect if the field becomes empty. 187 188 if (this.incremental) 189 return; 190 191 if (!this._inputField.value) 192 this._handleFilterChanged(); 193 } 145 194 }; 146 195 -
trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.css
r223058 r223065 23 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 24 */ 25 26 .content-view.network .navigation-bar .filter-bar { 27 background: none; 28 } 25 29 26 30 .content-view.network .network-table .icon { … … 72 76 background: rgba(0, 0, 0, 0.07); 73 77 } 78 79 .content-view.network .empty-content-placeholder { 80 position: absolute; 81 top: var(--navigation-bar-height); 82 bottom: 0; 83 padding: 0; 84 padding-top: 15px; 85 padding-bottom: 15px; 86 display: flex; 87 justify-content: center; 88 align-items: center; 89 overflow: hidden; 90 background: var(--odd-zebra-stripe-row-background-color); 91 --empty-content-placeholder-start: 0; 92 } 93 94 body[dir=ltr] .content-view.network .empty-content-placeholder { 95 left: var(--empty-content-placeholder-start); 96 } 97 98 body[dir=rtl] .content-view.network .empty-content-placeholder { 99 right: var(--empty-content-placeholder-start); 100 } 101 102 .content-view.network .empty-content-placeholder > .message { 103 display: inline-block; 104 white-space: nowrap; 105 106 font-size: var(--sidebar-no-results-message-font-size); 107 color: var(--text-color-gray-medium); 108 109 padding: 5px 15px 6px; 110 line-height: 25px; 111 text-align: center; 112 } -
trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.js
r223006 r223065 35 35 this._pendingInsertions = []; 36 36 this._pendingUpdates = []; 37 this._pendingFilter = false; 37 38 38 39 this._table = null; … … 44 45 45 46 // FIXME: Network Timeline. 46 // FIXME: Filter text field.47 47 // FIXME: Throttling. 48 48 // FIXME: HAR Export. … … 71 71 this._typeFilterScopeBar.addEventListener(WI.ScopeBar.Event.SelectionChanged, this._typeFilterScopeBarSelectionChanged, this); 72 72 73 this._textFilterSearchId = 0; 74 this._textFilterSearchText = null; 75 this._textFilterIsActive = false; 76 77 this._textFilterNavigationItem = new WI.FilterBarNavigationItem; 78 this._textFilterNavigationItem.filterBar.incremental = false; 79 this._textFilterNavigationItem.filterBar.addEventListener(WI.FilterBar.Event.FilterDidChange, this._textFilterDidChange, this); 80 this._textFilterNavigationItem.filterBar.placeholder = WI.UIString("Filter Full URL and Text"); 81 73 82 this._activeTypeFilters = this._generateTypeFilter(); 83 this._activeTextFilterResources = new Set; 84 85 this._emptyFilterResultsMessageElement = null; 74 86 75 87 // COMPATIBILITY (iOS 10.3): Network.setDisableResourceCaching did not exist. … … 143 155 get filterNavigationItems() 144 156 { 145 return [this._typeFilterScopeBar]; 157 let items = []; 158 if (window.PageAgent) 159 items.push(this._textFilterNavigationItem); 160 items.push(this._typeFilterScopeBar); 161 return items; 146 162 } 147 163 … … 582 598 this._processPendingEntries(); 583 599 this._positionDetailView(); 600 this._positionEmptyFilterMessage(); 584 601 } 585 602 … … 594 611 { 595 612 let needsSort = this._pendingUpdates.length > 0; 596 597 // No global sort is needed, so just insert new records into their sorted position. 598 if (!needsSort) { 613 let needsFilter = this._pendingFilter; 614 615 // No global sort or filter is needed, so just insert new records into their sorted position. 616 if (!needsSort && !needsFilter) { 599 617 let originalLength = this._pendingInsertions.length; 600 618 for (let resource of this._pendingInsertions) … … 613 631 this._pendingUpdates = []; 614 632 633 this._pendingFilter = false; 634 615 635 this._updateSortAndFilteredEntries(); 616 636 this._table.reloadData(); 637 } 638 639 _checkTextFilterAgainstFinishedResource(resource) 640 { 641 let frame = resource.parentFrame; 642 if (!frame) 643 return; 644 645 let searchQuery = this._textFilterSearchText; 646 if (resource.url.includes(searchQuery)) { 647 this._activeTextFilterResources.add(resource); 648 return; 649 } 650 651 let searchId = this._textFilterSearchId; 652 653 const isCaseSensitive = true; 654 const isRegex = false; 655 PageAgent.searchInResource(frame.id, resource.url, searchQuery, isCaseSensitive, isRegex, resource.requestIdentifier, (error, searchResults) => { 656 if (searchId !== this._textFilterSearchId) 657 return; 658 659 if (error || !searchResults || !searchResults.length) 660 return; 661 662 this._activeTextFilterResources.add(resource); 663 664 this._pendingFilter = true; 665 this.needsLayout(); 666 }); 667 } 668 669 _checkTextFilterAgainstFailedResource(resource) 670 { 671 let searchQuery = this._textFilterSearchText; 672 if (resource.url.includes(searchQuery)) 673 this._activeTextFilterResources.add(resource); 617 674 } 618 675 … … 690 747 } 691 748 749 _updateTextFilterActiveIndicator() 750 { 751 this._textFilterNavigationItem.filterBar.indicatingActive = this._hasTextFilter(); 752 } 753 754 _updateEmptyFilterResultsMessage() 755 { 756 if (this._hasActiveFilter() && !this._filteredEntries.length) 757 this._showEmptyFilterResultsMessage(); 758 else 759 this._hideEmptyFilterResultsMessage(); 760 } 761 762 _showEmptyFilterResultsMessage() 763 { 764 if (!this._emptyFilterResultsMessageElement) { 765 let message = WI.UIString("No Filter Results"); 766 let buttonElement = document.createElement("button"); 767 buttonElement.textContent = WI.UIString("Clear filters"); 768 buttonElement.addEventListener("click", () => { this._resetFilters(); }); 769 770 this._emptyFilterResultsMessageElement = document.createElement("div"); 771 this._emptyFilterResultsMessageElement.className = "empty-content-placeholder"; 772 773 let messageElement = this._emptyFilterResultsMessageElement.appendChild(document.createElement("div")); 774 messageElement.className = "message"; 775 messageElement.append(message, document.createElement("br"), buttonElement); 776 } 777 778 this.element.appendChild(this._emptyFilterResultsMessageElement); 779 this._positionEmptyFilterMessage(); 780 } 781 782 _hideEmptyFilterResultsMessage() 783 { 784 if (!this._emptyFilterResultsMessageElement) 785 return; 786 787 this._emptyFilterResultsMessageElement.remove(); 788 } 789 790 _positionEmptyFilterMessage() 791 { 792 if (!this._emptyFilterResultsMessageElement) 793 return; 794 795 let width = this._nameColumn.width - 1; // For the 1px border. 796 this._emptyFilterResultsMessageElement.style.width = width + "px"; 797 } 798 692 799 _resourceCachingDisabledSettingChanged() 693 800 { … … 715 822 let resource = event.target; 716 823 this._pendingUpdates.push(resource); 824 825 if (this._hasTextFilter()) 826 this._checkTextFilterAgainstFinishedResource(resource); 827 717 828 this.needsLayout(); 718 829 } … … 722 833 let resource = event.target; 723 834 this._pendingUpdates.push(resource); 835 836 if (this._hasTextFilter()) 837 this._checkTextFilterAgainstFailedResource(resource); 838 724 839 this.needsLayout(); 725 840 } … … 759 874 760 875 let resource = resourceTimelineRecord.resource; 761 this._insertResourceAndReloadTable(resource) 876 this._insertResourceAndReloadTable(resource); 762 877 } 763 878 … … 771 886 if (!(WI.tabBrowser.selectedTabContentView instanceof WI.NetworkTabContentView)) { 772 887 this._pendingInsertions.push(resource); 888 this.needsLayout(); 773 889 return; 774 890 } … … 846 962 } 847 963 964 _hasTypeFilter() 965 { 966 return !!this._activeTypeFilters; 967 } 968 969 _hasTextFilter() 970 { 971 return this._textFilterIsActive; 972 } 973 974 _hasActiveFilter() 975 { 976 return this._hasTypeFilter() 977 || this._hasTextFilter(); 978 } 979 980 _passTypeFilter(entry) 981 { 982 if (!this._hasTypeFilter()) 983 return true; 984 return this._activeTypeFilters.some((checker) => checker(entry.resource.type)); 985 } 986 987 _passTextFilter(entry) 988 { 989 if (!this._hasTextFilter()) 990 return true; 991 return this._activeTextFilterResources.has(entry.resource); 992 } 993 848 994 _passFilter(entry) 849 995 { 850 if (!this._activeTypeFilters) 851 return true; 852 853 return this._activeTypeFilters.some((checker) => checker(entry.resource.type)); 996 return this._passTypeFilter(entry) 997 && this._passTextFilter(entry); 854 998 } 855 999 … … 862 1006 _updateFilteredEntries() 863 1007 { 864 if (this._ activeTypeFilters)1008 if (this._hasActiveFilter()) 865 1009 this._filteredEntries = this._entries.filter(this._passFilter, this); 866 1010 else … … 868 1012 869 1013 this._restoreSelectedRow(); 1014 1015 this._updateTextFilterActiveIndicator(); 1016 this._updateEmptyFilterResultsMessage(); 870 1017 } 871 1018 … … 877 1024 878 1025 return selectedItems.map((item) => item.__checker); 1026 } 1027 1028 _resetFilters() 1029 { 1030 console.assert(this._hasActiveFilter()); 1031 1032 // Clear text filter. 1033 this._textFilterSearchId++; 1034 this._textFilterNavigationItem.filterBar.indicatingProgress = false; 1035 this._textFilterSearchText = null; 1036 this._textFilterIsActive = false; 1037 this._activeTextFilterResources.clear(); 1038 this._textFilterNavigationItem.filterBar.clear(); 1039 console.assert(!this._hasTextFilter()); 1040 1041 // Clear type filter. 1042 this._typeFilterScopeBar.resetToDefault(); 1043 console.assert(!this._hasTypeFilter()); 1044 1045 console.assert(!this._hasActiveFilter()); 1046 1047 this._updateFilteredEntries(); 1048 this._table.reloadData(); 879 1049 } 880 1050 … … 913 1083 } 914 1084 1085 _textFilterDidChange(event) 1086 { 1087 let searchQuery = this._textFilterNavigationItem.filterBar.filters.text; 1088 if (searchQuery === this._textFilterSearchText) 1089 return; 1090 1091 // Even if the selected resource would still be visible, lets close the detail view if a filter changes. 1092 this._hideResourceDetailView(); 1093 1094 let searchId = ++this._textFilterSearchId; 1095 1096 // Search cleared. 1097 if (!searchQuery) { 1098 this._textFilterNavigationItem.filterBar.indicatingProgress = false; 1099 this._textFilterSearchText = null; 1100 this._textFilterIsActive = false; 1101 this._activeTextFilterResources.clear(); 1102 1103 this._updateFilteredEntries(); 1104 this._table.reloadData(); 1105 return; 1106 } 1107 1108 this._textFilterSearchText = searchQuery; 1109 this._textFilterNavigationItem.filterBar.indicatingProgress = true; 1110 1111 // NetworkTable text filter currently searches: 1112 // - Resource URL 1113 // - Resource Text Content 1114 // It does not search all the content in the table (like mimeType, headers, etc). 1115 // For those we should provide more custom filters. 1116 1117 const isCaseSensitive = true; 1118 const isRegex = false; 1119 PageAgent.searchInResources(searchQuery, isCaseSensitive, isRegex, (error, searchResults) => { 1120 if (searchId !== this._textFilterSearchId) 1121 return; 1122 1123 this._textFilterIsActive = true; 1124 this._activeTextFilterResources.clear(); 1125 this._textFilterNavigationItem.filterBar.indicatingProgress = false; 1126 1127 // Add resources based on URL. 1128 for (let entry of this._entries) { 1129 let resource = entry.resource; 1130 if (resource.url.includes(searchQuery)) 1131 this._activeTextFilterResources.add(resource); 1132 } 1133 1134 // Add resources based on content. 1135 if (!error) { 1136 for (let {url, frameId, requestId} of searchResults) { 1137 if (requestId) { 1138 let resource = WI.frameResourceManager.resourceForRequestIdentifier(requestId); 1139 if (resource) { 1140 this._activeTextFilterResources.add(resource); 1141 continue; 1142 } 1143 } 1144 1145 if (frameId && url) { 1146 let frame = WI.frameResourceManager.frameForIdentifier(frameId); 1147 if (frame) { 1148 if (frame.mainResource.url === url) { 1149 this._activeTextFilterResources.add(frame.mainResource); 1150 continue; 1151 } 1152 let resource = frame.resourceForURL(url); 1153 if (resource) { 1154 this._activeTextFilterResources.add(resource); 1155 continue; 1156 } 1157 } 1158 } 1159 } 1160 } 1161 1162 // Apply. 1163 this._updateFilteredEntries(); 1164 this._table.reloadData(); 1165 }); 1166 } 1167 915 1168 _restoreSelectedRow() 916 1169 { … … 933 1186 934 1187 this._positionDetailView(); 1188 this._positionEmptyFilterMessage(); 935 1189 } 936 1190 }; -
trunk/Source/WebInspectorUI/UserInterface/Views/RadioButtonNavigationItem.css
r197029 r223065 44 44 .navigation-bar .item.radio.button.text-only:active { 45 45 color: var(--selected-foreground-color); 46 background-color: hsla(212, 92%, 54%, 0.55);46 background-color: var(--selected-background-color-active); 47 47 } 48 48 -
trunk/Source/WebInspectorUI/UserInterface/Views/ScopeBar.css
r212662 r223065 110 110 .scope-bar > li:active { 111 111 color: var(--selected-foreground-color); 112 background-color: hsla(212, 92%, 54%, 0.55);112 background-color: var(--selected-background-color-active); 113 113 } 114 114 -
trunk/Source/WebInspectorUI/UserInterface/Views/ScopeBar.js
r220119 r223065 90 90 } 91 91 92 resetToDefault() 93 { 94 let selectedItems = this.selectedItems; 95 if (selectedItems.length === 1 && selectedItems[0] === this._defaultItem) 96 return; 97 98 for (let item of this._items) 99 item.selected = false; 100 this._defaultItem.selected = true; 101 102 this.dispatchEventToListeners(WI.ScopeBar.Event.SelectionChanged); 103 } 104 92 105 // Private 93 106
Note: See TracChangeset
for help on using the changeset viewer.