⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 285711 in webkit


Ignore:
Timestamp:
Nov 12, 2021, 2:54:05 AM (5 years ago)
Author:
Razvan Caliman
Message:

Web Inspector: Extract reusable logic from ResourceQueryController, ResourceQueryResult and ResourceQueryMatch
https://bugs.webkit.org/show_bug.cgi?id=231604
<rdar://problem/84160281>

Reviewed by Devin Rousso.

Extract reusable logic from ResourceQueryController into a generic QueryController
to enable subclassing for other specialized use cases.

  • UserInterface/Controllers/QueryController.js: Added.

(WI.QueryController.prototype.executeQuery):
(WI.QueryController.prototype.findQueryMatches.pushMatch):
(WI.QueryController.prototype.findQueryMatches.matchNextSpecialCharacter):
(WI.QueryController.prototype.findQueryMatches.backtrack):
(WI.QueryController.prototype.findQueryMatches):
(WI.QueryController):

Keep only the reusable matching logic in QueryController.
Subclasses like ResourceQueryController are responsible for agregating
the data to be queried, customization for special characters and sorting results.

  • UserInterface/Controllers/ResourceQueryController.js:

(WI.ResourceQueryController.prototype.executeQuery):
(WI.ResourceQueryController.prototype._findQueryMatches.pushMatch): Deleted.
(WI.ResourceQueryController.prototype._findQueryMatches.matchNextSpecialCharacter): Deleted.
(WI.ResourceQueryController.prototype._findQueryMatches.backtrack): Deleted.
(WI.ResourceQueryController.prototype._findQueryMatches): Deleted.

  • UserInterface/Main.html:
  • UserInterface/Models/QueryMatch.js: Renamed from Source/WebInspectorUI/UserInterface/Models/ResourceQueryMatch.js.

ResourceQueryMatch doesn't contain any resource-specific logic. It can be generalized to QueryMatch.

  • UserInterface/Models/QueryResult.js: Copied from Source/WebInspectorUI/UserInterface/Models/ResourceQueryResult.js.

(WI.QueryResult):
(WI.QueryResult.prototype.get value):
(WI.QueryResult.prototype.get rank):
(WI.QueryResult.prototype.get matchingTextRanges):
(WI.QueryResult.prototype._calculateRank.getMultiplier):
(WI.QueryResult.prototype._calculateRank):
(WI.QueryResult.prototype._createMatchingTextRanges):

A generic QueryResult can be extracted from ResourceQueryResult containing
the reusable logic for ranking results and identifing matching text ranges.

  • UserInterface/Models/ResourceQueryResult.js:

(WI.ResourceQueryResult):
(WI.ResourceQueryResult.prototype.get resource):
(WI.ResourceQueryResult.prototype.test_createMatchesMask):
(WI.ResourceQueryResult.prototype.get rank): Deleted.
(WI.ResourceQueryResult.prototype.get matchingTextRanges): Deleted.
(WI.ResourceQueryResult.prototype._calculateRank.getMultiplier): Deleted.
(WI.ResourceQueryResult.prototype._calculateRank): Deleted.
(WI.ResourceQueryResult.prototype._createMatchingTextRanges): Deleted.

ResourceQueryResult extends QueryResult with resource-specifc members:

  • the cookie property which holds the optional line and column info used when jumping to matched files
  • the resource property which maps to the generic QueryResult.value; this is used in tests and when sorting in ResourceQueryController
  • UserInterface/Test.html:
Location:
trunk/Source/WebInspectorUI
Files:
1 added
5 edited
1 copied
1 moved

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebInspectorUI/ChangeLog

    r285615 r285711  
     12021-11-12  Razvan Caliman  <rcaliman@apple.com>
     2
     3        Web Inspector: Extract reusable logic from ResourceQueryController, ResourceQueryResult and ResourceQueryMatch
     4        https://bugs.webkit.org/show_bug.cgi?id=231604
     5        <rdar://problem/84160281>
     6
     7        Reviewed by Devin Rousso.
     8
     9        Extract reusable logic from `ResourceQueryController` into a generic `QueryController`
     10        to enable subclassing for other specialized use cases.
     11
     12        * UserInterface/Controllers/QueryController.js: Added.
     13        (WI.QueryController.prototype.executeQuery):
     14        (WI.QueryController.prototype.findQueryMatches.pushMatch):
     15        (WI.QueryController.prototype.findQueryMatches.matchNextSpecialCharacter):
     16        (WI.QueryController.prototype.findQueryMatches.backtrack):
     17        (WI.QueryController.prototype.findQueryMatches):
     18        (WI.QueryController):
     19
     20        Keep only the reusable matching logic in `QueryController`.
     21        Subclasses like `ResourceQueryController` are responsible for agregating
     22        the data to be queried, customization for special characters and sorting results.
     23
     24        * UserInterface/Controllers/ResourceQueryController.js:
     25        (WI.ResourceQueryController.prototype.executeQuery):
     26        (WI.ResourceQueryController.prototype._findQueryMatches.pushMatch): Deleted.
     27        (WI.ResourceQueryController.prototype._findQueryMatches.matchNextSpecialCharacter): Deleted.
     28        (WI.ResourceQueryController.prototype._findQueryMatches.backtrack): Deleted.
     29        (WI.ResourceQueryController.prototype._findQueryMatches): Deleted.
     30
     31        * UserInterface/Main.html:
     32
     33        * UserInterface/Models/QueryMatch.js: Renamed from Source/WebInspectorUI/UserInterface/Models/ResourceQueryMatch.js.
     34
     35        `ResourceQueryMatch` doesn't contain any resource-specific logic. It can be generalized to `QueryMatch`.
     36
     37        * UserInterface/Models/QueryResult.js: Copied from Source/WebInspectorUI/UserInterface/Models/ResourceQueryResult.js.
     38        (WI.QueryResult):
     39        (WI.QueryResult.prototype.get value):
     40        (WI.QueryResult.prototype.get rank):
     41        (WI.QueryResult.prototype.get matchingTextRanges):
     42        (WI.QueryResult.prototype._calculateRank.getMultiplier):
     43        (WI.QueryResult.prototype._calculateRank):
     44        (WI.QueryResult.prototype._createMatchingTextRanges):
     45
     46        A generic `QueryResult` can be extracted from `ResourceQueryResult` containing
     47        the reusable logic for ranking results and identifing matching text ranges.
     48
     49        * UserInterface/Models/ResourceQueryResult.js:
     50        (WI.ResourceQueryResult):
     51        (WI.ResourceQueryResult.prototype.get resource):
     52        (WI.ResourceQueryResult.prototype.__test_createMatchesMask):
     53        (WI.ResourceQueryResult.prototype.get rank): Deleted.
     54        (WI.ResourceQueryResult.prototype.get matchingTextRanges): Deleted.
     55        (WI.ResourceQueryResult.prototype._calculateRank.getMultiplier): Deleted.
     56        (WI.ResourceQueryResult.prototype._calculateRank): Deleted.
     57        (WI.ResourceQueryResult.prototype._createMatchingTextRanges): Deleted.
     58
     59        `ResourceQueryResult` extends `QueryResult` with resource-specifc members:
     60        - the `cookie` property which holds the optional line and column info used when jumping to matched files
     61        - the `resource` property which maps to the generic `QueryResult.value`; this is used in tests and when sorting in `ResourceQueryController`
     62
     63        * UserInterface/Test.html:
     64
    1652021-11-10  Tim Nguyen  <ntim@apple.com>
    266
  • trunk/Source/WebInspectorUI/UserInterface/Controllers/ResourceQueryController.js

    r238023 r285711  
    2424 */
    2525
    26 WI.ResourceQueryController = class ResourceQueryController extends WI.Object
     26WI.ResourceQueryController = class ResourceQueryController extends WI.QueryController
    2727{
    2828    constructor()
     
    7474            }
    7575
    76             let matches = this._findQueryMatches(query, cachedData.searchString, cachedData.specialCharacterIndices);
     76            let matches = this.findQueryMatches(query, cachedData.searchString, cachedData.specialCharacterIndices);
    7777            if (matches.length)
    7878                results.push(new WI.ResourceQueryResult(resource, matches, cookie));
     
    8989
    9090    // Private
    91 
    92     _findQueryMatches(query, searchString, specialCharacterIndices)
    93     {
    94         if (query.length > searchString.length)
    95             return [];
    96 
    97         let matches = [];
    98         let queryIndex = 0;
    99         let searchIndex = 0;
    100         let specialIndex = 0;
    101         let deadBranches = new Array(query.length).fill(Infinity);
    102         let type = WI.ResourceQueryMatch.Type.Special;
    103 
    104         function pushMatch(index)
    105         {
    106             matches.push(new WI.ResourceQueryMatch(type, index, queryIndex));
    107             searchIndex = index + 1;
    108             queryIndex++;
    109         }
    110 
    111         function matchNextSpecialCharacter()
    112         {
    113             if (specialIndex >= specialCharacterIndices.length)
    114                 return false;
    115 
    116             let originalSpecialIndex = specialIndex;
    117             while (specialIndex < specialCharacterIndices.length) {
    118                 // Normal character matching can move past special characters,
    119                 // so advance the special character index if it's before the
    120                 // current search string position.
    121                 let index = specialCharacterIndices[specialIndex++];
    122                 if (index < searchIndex)
    123                     continue;
    124 
    125                 if (query[queryIndex] === searchString[index]) {
    126                     pushMatch(index);
    127                     return true;
    128                 }
    129             }
    130 
    131             specialIndex = originalSpecialIndex;
    132             return false;
    133         }
    134 
    135         function backtrack()
    136         {
    137             while (matches.length) {
    138                 queryIndex--;
    139 
    140                 let lastMatch = matches.pop();
    141                 if (lastMatch.type !== WI.ResourceQueryMatch.Type.Special)
    142                     continue;
    143 
    144                 deadBranches[lastMatch.queryIndex] = lastMatch.index;
    145                 searchIndex = matches.lastValue ? matches.lastValue.index + 1 : 0;
    146                 return true;
    147             }
    148 
    149             return false;
    150         }
    151 
    152         while (queryIndex < query.length && searchIndex <= searchString.length) {
    153             if (type === WI.ResourceQueryMatch.Type.Special && !matchNextSpecialCharacter())
    154                 type = WI.ResourceQueryMatch.Type.Normal;
    155 
    156             if (type === WI.ResourceQueryMatch.Type.Normal) {
    157                 let index = searchString.indexOf(query[queryIndex], searchIndex);
    158                 if (index >= 0 && index < deadBranches[queryIndex]) {
    159                     pushMatch(index);
    160                     type = WI.ResourceQueryMatch.Type.Special;
    161                 } else if (!backtrack())
    162                     return [];
    163             }
    164         }
    165 
    166         if (queryIndex < query.length)
    167             return [];
    168 
    169         return matches;
    170     }
    17191
    17292    _findSpecialCharacterIndices(string)
  • trunk/Source/WebInspectorUI/UserInterface/Main.html

    r283276 r285711  
    469469    <script src="Models/PropertyPath.js"></script>
    470470    <script src="Models/PropertyPreview.js"></script>
     471    <script src="Models/QueryMatch.js"></script>
     472    <script src="Models/QueryResult.js"></script>
    471473    <script src="Models/Recording.js"></script>
    472474    <script src="Models/RecordingAction.js"></script>
     
    477479    <script src="Models/RenderingFrameTimelineRecord.js"></script>
    478480    <script src="Models/ResourceCollection.js"></script>
    479     <script src="Models/ResourceQueryMatch.js"></script>
    480481    <script src="Models/ResourceQueryResult.js"></script>
    481482    <script src="Models/ResourceTimelineRecord.js"></script>
     
    888889    <script src="Controllers/Annotator.js"></script>
    889890    <script src="Controllers/CodeMirrorEditingController.js"></script>
     891    <script src="Controllers/QueryController.js"></script>
    890892
    891893    <script src="Controllers/AnimationManager.js"></script>
  • trunk/Source/WebInspectorUI/UserInterface/Models/QueryMatch.js

    r285710 r285711  
    11/*
    2  * Copyright (C) 2016 Apple Inc. All rights reserved.
     2 * Copyright (C) 2021 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    2424 */
    2525
    26 WI.ResourceQueryMatch = class ResourceQueryMatch
     26WI.QueryMatch = class QueryMatch
    2727{
    2828    constructor(type, index, queryIndex)
    2929    {
     30        console.assert(Object.values(WI.QueryMatch.Type).includes(type), type);
     31        console.assert(index >= 0, index);
     32        console.assert(queryIndex >= 0, queryIndex);
    3033        this._type = type;
    3134        this._index = index;
     
    4043};
    4144
    42 WI.ResourceQueryMatch.Type = {
     45WI.QueryMatch.Type = {
    4346    Normal: Symbol("normal"),
    44     Special: Symbol("special")
     47    Special: Symbol("special"),
    4548};
  • trunk/Source/WebInspectorUI/UserInterface/Models/QueryResult.js

    r285710 r285711  
    11/*
    2  * Copyright (C) 2016 Apple Inc. All rights reserved.
     2 * Copyright (C) 2021 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    2424 */
    2525
    26 WI.ResourceQueryResult = class ResourceQueryResult
     26WI.QueryResult = class QueryResult
    2727{
    28     constructor(resource, matches, cookie)
     28    constructor(value, matches)
    2929    {
    3030        console.assert(matches.length, "Query matches list can't be empty.");
    3131
    32         this._resource = resource;
     32        this._value = value;
    3333        this._matches = matches;
    34         this._cookie = cookie || null;
    3534    }
    3635
    3736    // Public
    3837
    39     get resource() { return this._resource; }
    40     get cookie() { return this._cookie; }
     38    get value() { return this._value; }
    4139
    4240    get rank()
     
    5048    get matchingTextRanges()
    5149    {
    52         if (!this._matchingTextRanges)
    53             this._matchingTextRanges = this._createMatchingTextRanges();
     50        this._matchingTextRanges ??= this._createMatchingTextRanges();
    5451
    5552        return this._matchingTextRanges;
     
    6562
    6663        function getMultiplier(match) {
    67             if (match.type === WI.ResourceQueryMatch.Type.Special)
     64            if (match.type === WI.QueryMatch.Type.Special)
    6865                return specialMultiplier;
    6966
     
    127124        return ranges;
    128125    }
    129 
    130     // Testing
    131 
    132     __test_createMatchesMask()
    133     {
    134         let filename = this._resource.displayName;
    135         let lastIndex = -1;
    136         let result = "";
    137 
    138         for (let match of this._matches) {
    139             let gap = " ".repeat(match.index - lastIndex - 1);
    140             result += gap;
    141             result += filename[match.index];
    142             lastIndex = match.index;
    143         }
    144 
    145         return result;
    146     }
    147126};
  • trunk/Source/WebInspectorUI/UserInterface/Models/ResourceQueryResult.js

    r220119 r285711  
    2424 */
    2525
    26 WI.ResourceQueryResult = class ResourceQueryResult
     26WI.ResourceQueryResult = class ResourceQueryResult extends WI.QueryResult
    2727{
    2828    constructor(resource, matches, cookie)
    2929    {
    30         console.assert(matches.length, "Query matches list can't be empty.");
     30        console.assert(resource instanceof WI.Resource, resource);
     31        super(resource, matches);
    3132
    32         this._resource = resource;
    33         this._matches = matches;
    3433        this._cookie = cookie || null;
    3534    }
     
    3736    // Public
    3837
    39     get resource() { return this._resource; }
     38    get resource() { return this.value; }
    4039    get cookie() { return this._cookie; }
    41 
    42     get rank()
    43     {
    44         if (this._rank === undefined)
    45             this._calculateRank();
    46 
    47         return this._rank;
    48     }
    49 
    50     get matchingTextRanges()
    51     {
    52         if (!this._matchingTextRanges)
    53             this._matchingTextRanges = this._createMatchingTextRanges();
    54 
    55         return this._matchingTextRanges;
    56     }
    57 
    58     // Private
    59 
    60     _calculateRank()
    61     {
    62         const normalWeight = 10;
    63         const consecutiveWeight = 5;
    64         const specialMultiplier = 5;
    65 
    66         function getMultiplier(match) {
    67             if (match.type === WI.ResourceQueryMatch.Type.Special)
    68                 return specialMultiplier;
    69 
    70             return 1;
    71         }
    72 
    73         this._rank = 0;
    74 
    75         let previousMatch = null;
    76         let consecutiveMatchStart = null;
    77         for (let match of this._matches) {
    78             this._rank += normalWeight * getMultiplier(match);
    79 
    80             let consecutive = previousMatch && previousMatch.index === match.index - 1;
    81             if (consecutive) {
    82                 if (!consecutiveMatchStart)
    83                     consecutiveMatchStart = previousMatch;
    84 
    85                 // If the first match in this consecutive series was a special character, give a
    86                 // bonus (more likely to match a specific word in the text).  Otherwise, multiply
    87                 // by the current length of the consecutive sequence (gives priority to fewer
    88                 // longer sequences instead of more short sequences).
    89                 this._rank += consecutiveWeight * getMultiplier(consecutiveMatchStart) * (match.index - consecutiveMatchStart.index);
    90             } else if (consecutiveMatchStart)
    91                 consecutiveMatchStart = null;
    92 
    93             previousMatch = match;
    94 
    95             // The match index is deducted from the total rank, so matches that occur closer to
    96             // the beginning of the string are ranked higher.  Increase the amount subtracted if
    97             // the match is special, so as to favor matches towards the beginning of the string.
    98             if (!consecutive)
    99                 this._rank -= match.index * getMultiplier(match);
    100         }
    101     }
    102 
    103     _createMatchingTextRanges()
    104     {
    105         if (!this._matches.length)
    106             return [];
    107 
    108         let ranges = [];
    109         let startIndex = this._matches[0].index;
    110         let endIndex = startIndex;
    111         for (let i = 1; i < this._matches.length; ++i) {
    112             let match = this._matches[i];
    113 
    114             // Increment endIndex for consecutive match.
    115             if (match.index === endIndex + 1) {
    116                 endIndex++;
    117                 continue;
    118             }
    119 
    120             // Begin a new range when a gap between this match and the previous match is found.
    121             ranges.push(new WI.TextRange(0, startIndex, 0, endIndex + 1));
    122             startIndex = match.index;
    123             endIndex = startIndex;
    124         }
    125 
    126         ranges.push(new WI.TextRange(0, startIndex, 0, endIndex + 1));
    127         return ranges;
    128     }
    12940
    13041    // Testing
     
    13243    __test_createMatchesMask()
    13344    {
    134         let filename = this._resource.displayName;
     45        let filename = this.resource.displayName;
    13546        let lastIndex = -1;
    13647        let result = "";
  • trunk/Source/WebInspectorUI/UserInterface/Test.html

    r276680 r285711  
    197197    <script src="Models/PropertyDescriptor.js"></script>
    198198    <script src="Models/PropertyPreview.js"></script>
     199    <script src="Models/QueryMatch.js"></script>
     200    <script src="Models/QueryResult.js"></script>
    199201    <script src="Models/Recording.js"></script>
    200202    <script src="Models/RecordingAction.js"></script>
     
    205207    <script src="Models/RenderingFrameTimelineRecord.js"></script>
    206208    <script src="Models/ResourceCollection.js"></script>
    207     <script src="Models/ResourceQueryMatch.js"></script>
    208209    <script src="Models/ResourceQueryResult.js"></script>
    209210    <script src="Models/ResourceTimelineRecord.js"></script>
     
    244245    <script src="Proxies/HeapSnapshotProxy.js"></script>
    245246    <script src="Proxies/HeapSnapshotWorkerProxy.js"></script>
     247
     248    <script src="Controllers/QueryController.js"></script>
    246249
    247250    <script src="Controllers/AnimationManager.js"></script>
Note: See TracChangeset for help on using the changeset viewer.