Changeset 239858 in webkit


Ignore:
Timestamp:
Jan 10, 2019 9:54:54 PM (5 years ago)
Author:
Devin Rousso
Message:

Web Inspector: Audit: allow audits to be enabled/disabled
https://bugs.webkit.org/show_bug.cgi?id=192210
<rdar://problem/46423583>

Reviewed by Joseph Pecoraro.

Source/WebInspectorUI:

  • UserInterface/Controllers/AuditManager.js:

(WI.AuditManager.prototype.get editing): Added.
(WI.AuditManager.prototype.set editing): Added.
(WI.AuditManager.prototype.stop):
(WI.AuditManager.prototype.addDefaultTestsIfNeeded):
Since default audits aren't stored, keep a list of disabled default tests in a WI.Setting.

  • UserInterface/Models/AuditTestBase.js:

(WI.AuditTestBase):
(WI.AuditTestBase.prototype.get disabled): Added.
(WI.AuditTestBase.prototype.set disabled): Added.
(WI.AuditTestBase.prototype.async start):
(WI.AuditTestBase.prototype.stop):
(WI.AuditTestBase.toJSON):

  • UserInterface/Models/AuditTestCase.js:

(WI.AuditTestCase):
(WI.AuditTestCase.async fromPayload):
(WI.AuditTestCase.prototype.toJSON):

  • UserInterface/Models/AuditTestGroup.js:

(WI.AuditTestGroup):
(WI.AuditTestGroup.async fromPayload):
(WI.AuditTestGroup.prototype.get disabled): Added.
(WI.AuditTestGroup.prototype.set disabled): Added.
(WI.AuditTestGroup.prototype.toJSON):
(WI.AuditTestGroup.prototype.async run):
(WI.AuditTestGroup.prototype._handleTestDisabledChanged): Added.
(WI.AuditTestGroup.prototype._handleTestProgress):
Propagate disabled changes to all sub-tests, unless the change was caused by one of the
sub-tests, in which case we are now in an intermediate state.

  • UserInterface/Views/AuditNavigationSidebarPanel.js:

(WI.AuditNavigationSidebarPanel):
(WI.AuditNavigationSidebarPanel.prototype.showDefaultContentView):
(WI.AuditNavigationSidebarPanel.prototype.initialLayout):
(WI.AuditNavigationSidebarPanel.prototype.hasCustomFilters): Added.
(WI.AuditNavigationSidebarPanel.prototype.matchTreeElementAgainstCustomFilters): Added.
(WI.AuditNavigationSidebarPanel.prototype._addTest):
(WI.AuditNavigationSidebarPanel.prototype._addResult):
(WI.AuditNavigationSidebarPanel.prototype._updateStartStopButtonNavigationItemState):
(WI.AuditNavigationSidebarPanel.prototype._updateEditButtonNavigationItemState): Added.
(WI.AuditNavigationSidebarPanel.prototype._handleAuditManagerEditingChanged): Added.
(WI.AuditNavigationSidebarPanel.prototype._handleAuditTestRemoved):
(WI.AuditNavigationSidebarPanel.prototype._handleAuditTestScheduled):
(WI.AuditNavigationSidebarPanel.prototype._treeSelectionDidChange):
(WI.AuditNavigationSidebarPanel.prototype._handleEditButtonNavigationItemClicked): Added.

  • UserInterface/Views/AuditNavigationSidebarPanel.css:

(.sidebar > .panel.navigation.audit > .content):
(.sidebar > .panel.navigation.audit > .content > .tree-outline): Added.
(.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled):active): Added.
(.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled).activated): Added.
(.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled).activated:active): Added.
(.sidebar > .panel.navigation.audit > .content .edit-audits.disabled): Added.
(.finish-editing-audits-placeholder.message-text-view .navigation-item-help .navigation-bar): Added.
Leverage custom filters to ensure that disabled audits arent shown when not editing and that
result tree elements aren't shown while editing.

  • UserInterface/Views/AuditTestGroupContentView.js:

(WI.AuditTestGroupContentView.prototype.shown):

  • UserInterface/Views/AuditTreeElement.js:

(WI.AuditTreeElement.prototype.onattach):
(WI.AuditTreeElement.prototype.canSelectOnMouseDown): Added.
(WI.AuditTreeElement.prototype._updateTestGroupDisabled): Added.
(WI.AuditTreeElement.prototype._handleTestDisabledChanged): Added.
(WI.AuditTreeElement.prototype._handleManagerEditingChanged): Added.

  • UserInterface/Views/AuditTreeElement.css:

(.tree-outline .item.audit > .status:not(:hover) > img.show-on-hover, .tree-outline .item.audit.test-group.expanded:not(.editing-audits) > .status:not(:hover)): Added.
(.tree-outline .item.audit.manager-active > .status > img.show-on-hover, .tree-outline .item.audit.test-group.expanded:not(.editing-audits) > .status:hover > :not(img), .tree-outline .item.audit.test-group-result.expanded > .status): Added.
(.tree-outline .item.audit > .status:not(:hover) > img.show-on-hover, .tree-outline .item.audit.test-group.expanded > .status:not(:hover)): Deleted.
(.tree-outline .item.audit.manager-active > .status > img.show-on-hover, .tree-outline .item.audit.test-group.expanded > .status:hover > :not(img), .tree-outline .item.audit.test-group-result.expanded > .status): Deleted.
Prevent selection and running when editing.

  • UserInterface/Views/TreeOutline.css:

(.tree-outline .children.expanded:not([hidden])): Added.
(.tree-outline .children.expanded): Deleted.

  • UserInterface/Base/ObjectStore.js:

(WI.ObjectStore._open):
Batch operations together to help avoid multiple simultaneous indexedDB.open calls. This
should also help preserve the order of operations, as once the database is open, operations
are executed in the order they were enqueued.

(WI.ObjectStore.prototype.async.addObject):
Pass a unique Symbol to the toJSON call on the given object so that the object can save
additional values that wouldn't normally be saved. This doesn't conflict with normal usage
of toJSON (e.g. JSON.stringify) because that case also passes in a value:

  • undefined, if it was called directly on the object
  • the key for this object in the containing object
  • the index of this object in the containing array

In any case, the value can never equal the unique Symbol, so it's guaranteed that the code
will only run for WI.ObjectStore operations.

(WI.ObjectStore.prototype.async.clear): Added.

  • Localizations/en.lproj/localizedStrings.js:

LayoutTests:

  • inspector/unit-tests/objectStore/clear.html: Added.
  • inspector/unit-tests/objectStore/clear-expected.txt: Added.
Location:
trunk
Files:
2 added
14 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r239853 r239858  
     12019-01-10  Devin Rousso  <drousso@apple.com>
     2
     3        Web Inspector: Audit: allow audits to be enabled/disabled
     4        https://bugs.webkit.org/show_bug.cgi?id=192210
     5        <rdar://problem/46423583>
     6
     7        Reviewed by Joseph Pecoraro.
     8
     9        * inspector/unit-tests/objectStore/clear.html: Added.
     10        * inspector/unit-tests/objectStore/clear-expected.txt: Added.
     11
    1122019-01-10  Justin Fan  <justin_fan@apple.com>
    213
  • trunk/Source/WebInspectorUI/ChangeLog

    r239779 r239858  
     12019-01-10  Devin Rousso  <drousso@apple.com>
     2
     3        Web Inspector: Audit: allow audits to be enabled/disabled
     4        https://bugs.webkit.org/show_bug.cgi?id=192210
     5        <rdar://problem/46423583>
     6
     7        Reviewed by Joseph Pecoraro.
     8
     9        * UserInterface/Controllers/AuditManager.js:
     10        (WI.AuditManager.prototype.get editing): Added.
     11        (WI.AuditManager.prototype.set editing): Added.
     12        (WI.AuditManager.prototype.stop):
     13        (WI.AuditManager.prototype.addDefaultTestsIfNeeded):
     14        Since default audits aren't stored, keep a list of disabled default tests in a `WI.Setting`.
     15
     16        * UserInterface/Models/AuditTestBase.js:
     17        (WI.AuditTestBase):
     18        (WI.AuditTestBase.prototype.get disabled): Added.
     19        (WI.AuditTestBase.prototype.set disabled): Added.
     20        (WI.AuditTestBase.prototype.async start):
     21        (WI.AuditTestBase.prototype.stop):
     22        (WI.AuditTestBase.toJSON):
     23
     24        * UserInterface/Models/AuditTestCase.js:
     25        (WI.AuditTestCase):
     26        (WI.AuditTestCase.async fromPayload):
     27        (WI.AuditTestCase.prototype.toJSON):
     28
     29        * UserInterface/Models/AuditTestGroup.js:
     30        (WI.AuditTestGroup):
     31        (WI.AuditTestGroup.async fromPayload):
     32        (WI.AuditTestGroup.prototype.get disabled): Added.
     33        (WI.AuditTestGroup.prototype.set disabled): Added.
     34        (WI.AuditTestGroup.prototype.toJSON):
     35        (WI.AuditTestGroup.prototype.async run):
     36        (WI.AuditTestGroup.prototype._handleTestDisabledChanged): Added.
     37        (WI.AuditTestGroup.prototype._handleTestProgress):
     38        Propagate `disabled` changes to all sub-tests, unless the change was caused by one of the
     39        sub-tests, in which case we are now in an intermediate state.
     40
     41        * UserInterface/Views/AuditNavigationSidebarPanel.js:
     42        (WI.AuditNavigationSidebarPanel):
     43        (WI.AuditNavigationSidebarPanel.prototype.showDefaultContentView):
     44        (WI.AuditNavigationSidebarPanel.prototype.initialLayout):
     45        (WI.AuditNavigationSidebarPanel.prototype.hasCustomFilters): Added.
     46        (WI.AuditNavigationSidebarPanel.prototype.matchTreeElementAgainstCustomFilters): Added.
     47        (WI.AuditNavigationSidebarPanel.prototype._addTest):
     48        (WI.AuditNavigationSidebarPanel.prototype._addResult):
     49        (WI.AuditNavigationSidebarPanel.prototype._updateStartStopButtonNavigationItemState):
     50        (WI.AuditNavigationSidebarPanel.prototype._updateEditButtonNavigationItemState): Added.
     51        (WI.AuditNavigationSidebarPanel.prototype._handleAuditManagerEditingChanged): Added.
     52        (WI.AuditNavigationSidebarPanel.prototype._handleAuditTestRemoved):
     53        (WI.AuditNavigationSidebarPanel.prototype._handleAuditTestScheduled):
     54        (WI.AuditNavigationSidebarPanel.prototype._treeSelectionDidChange):
     55        (WI.AuditNavigationSidebarPanel.prototype._handleEditButtonNavigationItemClicked): Added.
     56        * UserInterface/Views/AuditNavigationSidebarPanel.css:
     57        (.sidebar > .panel.navigation.audit > .content):
     58        (.sidebar > .panel.navigation.audit > .content > .tree-outline): Added.
     59        (.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled):active): Added.
     60        (.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled).activated): Added.
     61        (.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled).activated:active): Added.
     62        (.sidebar > .panel.navigation.audit > .content .edit-audits.disabled): Added.
     63        (.finish-editing-audits-placeholder.message-text-view .navigation-item-help .navigation-bar): Added.
     64        Leverage custom filters to ensure that disabled audits arent shown when not editing and that
     65        result tree elements aren't shown while editing.
     66
     67        * UserInterface/Views/AuditTestGroupContentView.js:
     68        (WI.AuditTestGroupContentView.prototype.shown):
     69
     70        * UserInterface/Views/AuditTreeElement.js:
     71        (WI.AuditTreeElement.prototype.onattach):
     72        (WI.AuditTreeElement.prototype.canSelectOnMouseDown): Added.
     73        (WI.AuditTreeElement.prototype._updateTestGroupDisabled): Added.
     74        (WI.AuditTreeElement.prototype._handleTestDisabledChanged): Added.
     75        (WI.AuditTreeElement.prototype._handleManagerEditingChanged): Added.
     76        * UserInterface/Views/AuditTreeElement.css:
     77        (.tree-outline .item.audit > .status:not(:hover) > img.show-on-hover, .tree-outline .item.audit.test-group.expanded:not(.editing-audits) > .status:not(:hover)): Added.
     78        (.tree-outline .item.audit.manager-active > .status > img.show-on-hover, .tree-outline .item.audit.test-group.expanded:not(.editing-audits) > .status:hover > :not(img), .tree-outline .item.audit.test-group-result.expanded > .status): Added.
     79        (.tree-outline .item.audit > .status:not(:hover) > img.show-on-hover, .tree-outline .item.audit.test-group.expanded > .status:not(:hover)): Deleted.
     80        (.tree-outline .item.audit.manager-active > .status > img.show-on-hover, .tree-outline .item.audit.test-group.expanded > .status:hover > :not(img), .tree-outline .item.audit.test-group-result.expanded > .status): Deleted.
     81        Prevent selection and running when editing.
     82
     83        * UserInterface/Views/TreeOutline.css:
     84        (.tree-outline .children.expanded:not([hidden])): Added.
     85        (.tree-outline .children.expanded): Deleted.
     86
     87        * UserInterface/Base/ObjectStore.js:
     88        (WI.ObjectStore._open):
     89        Batch operations together to help avoid multiple simultaneous `indexedDB.open` calls. This
     90        should also help preserve the order of operations, as once the database is open, operations
     91        are executed in the order they were enqueued.
     92
     93        (WI.ObjectStore.prototype.async.addObject):
     94        Pass a unique `Symbol` to the `toJSON` call on the given object so that the object can save
     95        additional values that wouldn't normally be saved. This doesn't conflict with normal usage
     96        of `toJSON` (e.g. `JSON.stringify`) because that case also passes in a value:
     97         - `undefined`, if it was called directly on the object
     98         - the key for this object in the containing object
     99         - the index of this object in the containing array
     100        In any case, the value can never equal the unique `Symbol`, so it's guaranteed that the code
     101        will only run for `WI.ObjectStore` operations.
     102
     103        (WI.ObjectStore.prototype.async.clear): Added.
     104
     105        * Localizations/en.lproj/localizedStrings.js:
     106
    11072019-01-09  Devin Rousso  <drousso@apple.com>
    2108
  • trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js

    r239703 r239858  
    325325localizedStrings["Edit configuration"] = "Edit configuration";
    326326localizedStrings["Edit custom gradient"] = "Edit custom gradient";
     327localizedStrings["Editing audits"] = "Editing audits";
    327328localizedStrings["Element"] = "Element";
    328329localizedStrings["Element clips compositing descendants"] = "Element clips compositing descendants";
     
    689690localizedStrings["Press %s to load a recording from file."] = "Press %s to load a recording from file.";
    690691localizedStrings["Press %s to start running the audit"] = "Press %s to start running the audit";
     692localizedStrings["Press %s to stop editing"] = "Press %s to stop editing";
    691693localizedStrings["Pressed"] = "Pressed";
    692694localizedStrings["Pretty print"] = "Pretty print";
  • trunk/Source/WebInspectorUI/UserInterface/Base/ObjectStore.js

    r237665 r239858  
    5353        }
    5454
     55        if (Array.isArray(WI.ObjectStore._databaseCallbacks)) {
     56            WI.ObjectStore._databaseCallbacks.push(callback);
     57            return;
     58        }
     59
     60        WI.ObjectStore._databaseCallbacks = [callback];
     61
    5562        const version = 1; // Increment this for every edit to `WI.objectStores`.
    5663
     
    8289            });
    8390
    84             callback(WI.ObjectStore._database);
     91            for (let databaseCallback of WI.ObjectStore._databaseCallbacks)
     92                databaseCallback(WI.ObjectStore._database);
     93
     94            WI.ObjectStore._databaseCallbacks = null;
    8595        });
    8696    }
     
    119129
    120130        console.assert(typeof object.toJSON === "function", "ObjectStore cannot store an object without JSON serialization", object.constructor.name);
    121         let result = await this.add(object.toJSON(), ...args);
     131        let result = await this.add(object.toJSON(WI.ObjectStore.toJSONSymbol), ...args);
    122132        this.associateObject(object, args[0], result);
    123133        return result;
     
    138148
    139149        return this.delete(this._resolveKeyPath(object).value, ...args);
     150    }
     151
     152    async clear(...args)
     153    {
     154        if (!WI.ObjectStore.supported())
     155            return undefined;
     156
     157        return this._operation("readwrite", (objectStore) => objectStore.clear(...args));
    140158    }
    141159
     
    204222
    205223WI.ObjectStore._database = null;
     224WI.ObjectStore._databaseCallbacks = null;
     225
     226WI.ObjectStore.toJSONSymbol = Symbol("ObjectStore-toJSON");
    206227
    207228// Be sure to update the `version` above when making changes.
  • trunk/Source/WebInspectorUI/UserInterface/Controllers/AuditManager.js

    r239449 r239858  
    3636        this._runningTests = [];
    3737
     38        this._disabledDefaultTestsSetting = new WI.Setting("audit-disabled-default-tests", []);
     39
    3840        WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._handleFrameMainResourceDidChange, this);
    3941    }
     
    5254    get results() { return this._results; }
    5355    get runningState() { return this._runningState; }
     56
     57    get editing()
     58    {
     59        return this._runningState === WI.AuditManager.RunningState.Disabled;
     60    }
     61
     62    set editing(editing)
     63    {
     64        console.assert(this._runningState === WI.AuditManager.RunningState.Disabled || this._runningState === WI.AuditManager.RunningState.Inactive);
     65        if (this._runningState !== WI.AuditManager.RunningState.Disabled && this._runningState !== WI.AuditManager.RunningState.Inactive)
     66            return;
     67
     68        let runningState = editing ? WI.AuditManager.RunningState.Disabled : WI.AuditManager.RunningState.Inactive;
     69        console.assert(runningState !== this._runningState);
     70        if (runningState === this._runningState)
     71            return;
     72
     73        this._runningState = runningState;
     74
     75        this.dispatchEventToListeners(WI.AuditManager.Event.EditingChanged);
     76
     77        if (!this.editing) {
     78            WI.objectStores.audits.clear();
     79
     80            let disabledDefaultTests = [];
     81            let saveDisabledDefaultTest = (test) => {
     82                if (test.disabled)
     83                    disabledDefaultTests.push(test.name);
     84
     85                if (test instanceof WI.AuditTestGroup) {
     86                    for (let child of test.tests)
     87                        saveDisabledDefaultTest(child);
     88                }
     89            };
     90
     91            for (let test of this._tests) {
     92                if (test.__default)
     93                    saveDisabledDefaultTest(test);
     94                else
     95                    WI.objectStores.audits.addObject(test);
     96            }
     97
     98            this._disabledDefaultTestsSetting.value = disabledDefaultTests;
     99        }
     100    }
    54101
    55102    async start(tests)
     
    98145            return;
    99146
     147        this._runningState = WI.AuditManager.RunningState.Stopping;
     148
    100149        for (let test of this._runningTests)
    101150            test.stop();
    102 
    103         this._runningState = WI.AuditManager.RunningState.Stopping;
    104151    }
    105152
     
    254301        ];
    255302
     303        let checkDisabledDefaultTest = (test) => {
     304            if (this._disabledDefaultTestsSetting.value.includes(test.name))
     305                test.disabled = true;
     306
     307            if (test instanceof WI.AuditTestGroup) {
     308                for (let child of test.tests)
     309                    checkDisabledDefaultTest(child);
     310            }
     311        };
     312
    256313        for (let test of defaultTests) {
     314            checkDisabledDefaultTest(test);
     315
    257316            test.__default = true;
    258317            this._addTest(test);
     
    262321
    263322WI.AuditManager.RunningState = {
     323    Disabled: "disabled",
    264324    Inactive: "inactive",
    265325    Active: "active",
     
    268328
    269329WI.AuditManager.Event = {
     330    EditingChanged: "audit-manager-editing-changed",
    270331    TestAdded: "audit-manager-test-added",
    271332    TestCompleted: "audit-manager-test-completed",
  • trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestBase.js

    r237613 r239858  
    2626WI.AuditTestBase = class AuditTestBase extends WI.Object
    2727{
    28     constructor(name, {description} = {})
     28    constructor(name, {description, disabled} = {})
    2929    {
    3030        console.assert(typeof name === "string");
    3131        console.assert(!description || typeof description === "string");
     32        console.assert(disabled === undefined || typeof disabled === "boolean");
    3233
    3334        super();
     35
     36        // This class should not be instantiated directly. Create a concrete subclass instead.
     37        console.assert(this.constructor !== WI.AuditTestBase && this instanceof WI.AuditTestBase);
    3438
    3539        this._name = name;
    3640        this._description = description || null;
    3741
    38         this._runningState = WI.AuditManager.RunningState.Inactive;
     42        this._runningState = disabled ? WI.AuditManager.RunningState.Disabled : WI.AuditManager.RunningState.Inactive;
    3943        this._result = null;
    4044    }
     
    4751    get result() { return this._result; }
    4852
     53    get disabled()
     54    {
     55        return this._runningState === WI.AuditManager.RunningState.Disabled;
     56    }
     57
     58    set disabled(disabled)
     59    {
     60        console.assert(this._runningState === WI.AuditManager.RunningState.Disabled || this._runningState === WI.AuditManager.RunningState.Inactive);
     61        if (this._runningState !== WI.AuditManager.RunningState.Disabled && this._runningState !== WI.AuditManager.RunningState.Inactive)
     62            return;
     63
     64        let runningState = disabled ? WI.AuditManager.RunningState.Disabled : WI.AuditManager.RunningState.Inactive;
     65        if (runningState === this._runningState)
     66            return;
     67
     68        this._runningState = runningState;
     69
     70        this.dispatchEventToListeners(WI.AuditTestBase.Event.DisabledChanged);
     71    }
     72
    4973    async start()
    5074    {
    5175        // Called from WI.AuditManager.
     76
     77        if (this.disabled)
     78            return;
    5279
    5380        console.assert(WI.auditManager.runningState === WI.AuditManager.RunningState.Active);
     
    7097        // Called from WI.AuditManager.
    7198
    72         console.assert(this._runningState !== WI.AuditManager.RunningState.Inactive);
     99        if (this.disabled)
     100            return;
     101
     102        console.assert(WI.auditManager.runningState === WI.AuditManager.RunningState.Stopping);
    73103
    74104        if (this._runningState !== WI.AuditManager.RunningState.Active)
     
    97127    }
    98128
    99     toJSON()
     129    toJSON(key)
    100130    {
    101131        let json = {
     
    105135        if (this._description)
    106136            json.description = this._description;
     137        if (key === WI.ObjectStore.toJSONSymbol)
     138            json.disabled = this.disabled;
    107139        return json;
    108140    }
     
    118150WI.AuditTestBase.Event = {
    119151    Completed: "audit-test-base-completed",
     152    DisabledChanged: "audit-test-base-disabled-changed",
    120153    Progress: "audit-test-base-progress",
    121154    ResultCleared: "audit-test-base-result-cleared",
  • trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js

    r238850 r239858  
    2626WI.AuditTestCase = class AuditTestCase extends WI.AuditTestBase
    2727{
    28     constructor(name, test, {description} = {})
     28    constructor(name, test, options = {})
    2929    {
    3030        console.assert(typeof test === "string");
    3131
    32         super(name, {description});
     32        super(name, options);
    3333
    3434        this._test = test;
     
    4242            return null;
    4343
    44         let {type, name, test, description} = payload;
     44        let {type, name, test, description, disabled} = payload;
    4545
    4646        if (type !== WI.AuditTestCase.TypeIdentifier)
     
    5656        if (typeof description === "string")
    5757            options.description = description;
     58        if (typeof disabled === "boolean")
     59            options.disabled = disabled;
    5860
    5961        return new WI.AuditTestCase(name, test, options);
     
    6466    get test() { return this._test; }
    6567
    66     toJSON()
    67     {
    68         let json = super.toJSON();
     68    toJSON(key)
     69    {
     70        let json = super.toJSON(key);
    6971        json.test = this._test;
    7072        return json;
  • trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestGroup.js

    r237656 r239858  
    2626WI.AuditTestGroup = class AuditTestGroup extends WI.AuditTestBase
    2727{
    28     constructor(name, tests, {description} = {})
     28    constructor(name, tests, options = {})
    2929    {
    3030        console.assert(Array.isArray(tests));
    3131
    32         super(name, {description});
     32        // Set disabled once `_tests` is set so that it propagates.
     33        let disabled = options.disabled;
     34        options.disabled = false;
     35
     36        super(name, options);
    3337
    3438        this._tests = tests;
     39        this._preventDisabledPropagation = false;
     40
     41        if (disabled)
     42            this.disabled = disabled;
    3543
    3644        for (let test of this._tests) {
    3745            test.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestCompleted, this);
     46            test.addEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
    3847            test.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestProgress, this);
    3948        }
     
    4756            return null;
    4857
    49         let {type, name, tests, description} = payload;
     58        let {type, name, tests, description, disabled} = payload;
    5059
    5160        if (type !== WI.AuditTestGroup.TypeIdentifier)
     
    7685        if (typeof description === "string")
    7786            options.description = description;
     87        if (typeof disabled === "boolean")
     88            options.disabled = disabled;
    7889
    7990        return new WI.AuditTestGroup(name, tests, options);
     
    8394
    8495    get tests() { return this._tests; }
     96
     97    get disabled()
     98    {
     99        return super.disabled;
     100    }
     101
     102    set disabled(disabled)
     103    {
     104        if (!this._preventDisabledPropagation) {
     105            for (let test of this._tests)
     106                test.disabled = disabled;
     107        }
     108
     109        super.disabled = disabled;
     110    }
    85111
    86112    stop()
     
    108134    }
    109135
    110     toJSON()
    111     {
    112         let json = super.toJSON();
    113         json.tests = this._tests.map((testCase) => testCase.toJSON());
     136    toJSON(key)
     137    {
     138        let json = super.toJSON(key);
     139        json.tests = this._tests.map((testCase) => testCase.toJSON(key));
    114140        return json;
    115141    }
     
    122148        for (let index = 0; index < count && this._runningState === WI.AuditManager.RunningState.Active; ++index) {
    123149            let test = this._tests[index];
     150            if (test.disabled)
     151                continue;
    124152
    125153            await test.start();
     
    154182    }
    155183
     184    _handleTestDisabledChanged(event)
     185    {
     186        let enabledTestCount = this._tests.filter((test) => !test.disabled).length;
     187        if (event.target.disabled && !enabledTestCount)
     188            this.disabled = true;
     189        else if (!event.target.disabled && enabledTestCount === 1) {
     190            this._preventDisabledPropagation = true;
     191            this.disabled = false;
     192            this._preventDisabledPropagation = false;
     193        } else {
     194            // Don't change `disabled`, as we're currently in an "indeterminate" state.
     195            this.dispatchEventToListeners(WI.AuditTestBase.Event.DisabledChanged);
     196        }
     197    }
     198
    156199    _handleTestProgress(event)
    157200    {
     
    162205            let count = 0;
    163206            for (let test of tests) {
     207                if (test.disabled)
     208                    continue;
     209
    164210                if (test instanceof WI.AuditTestCase)
    165211                    ++count;
     
    171217
    172218        this.dispatchEventToListeners(WI.AuditTestBase.Event.Progress, {
    173             index: event.data.index + walk(this.tests.slice(0, this.tests.indexOf(event.target))),
    174             count: walk(this.tests),
     219            index: event.data.index + walk(this._tests.slice(0, this._tests.indexOf(event.target))),
     220            count: walk(this._tests),
    175221        });
    176222    }
  • trunk/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.css

    r238334 r239858  
    2525
    2626.sidebar > .panel.navigation.audit > .content {
     27    display: flex;
     28    flex-direction: column;
    2729    top: var(--navigation-bar-height);
     30}
     31
     32.sidebar > .panel.navigation.audit > .content > .tree-outline {
     33    flex-grow: 1;
     34}
     35
     36.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled):active {
     37    color: var(--glyph-color-pressed);
     38}
     39
     40.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled).activated {
     41    color: var(--glyph-color-active);
     42}
     43
     44.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled).activated:active {
     45    color: var(--glyph-color-active-pressed);
     46}
     47
     48.sidebar > .panel.navigation.audit > .content .edit-audits.disabled {
     49    color: var(--glyph-color-disabled);
    2850}
    2951
     
    4062    margin: 8px 0 7px;
    4163}
     64
     65.finish-editing-audits-placeholder.message-text-view .navigation-item-help .navigation-bar {
     66    padding: 0;
     67    vertical-align: 0.5px;
     68}
  • trunk/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.js

    r238842 r239858  
    3737        let contentView = new WI.ContentView;
    3838
    39         let contentPlaceholder = WI.createMessageTextView(WI.UIString("No audit selected"));
    40         contentView.element.appendChild(contentPlaceholder);
    41 
    42         let importNavigationItem = new WI.ButtonNavigationItem("import-audit", WI.UIString("Import"), "Images/Import.svg", 15, 15);
    43         importNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
    44         importNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleImportButtonNavigationItemClicked, this);
    45 
    46         let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to import a test or result file"), importNavigationItem);
    47         contentPlaceholder.appendChild(importHelpElement);
     39        if (WI.auditManager.editing) {
     40            let contentPlaceholder = WI.createMessageTextView(WI.UIString("Editing audits"));
     41            contentPlaceholder.classList.add("finish-editing-audits-placeholder");
     42            contentView.element.appendChild(contentPlaceholder);
     43
     44            let finishEditingNavigationItem = new WI.ButtonNavigationItem("finish-editing-audits", WI.UIString("Done"));
     45            finishEditingNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, (event) => {
     46                WI.auditManager.editing = false;
     47            });
     48
     49            let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to stop editing"), finishEditingNavigationItem);
     50            contentPlaceholder.appendChild(importHelpElement);
     51        } else {
     52            let contentPlaceholder = WI.createMessageTextView(WI.UIString("No audit selected"));
     53            contentView.element.appendChild(contentPlaceholder);
     54
     55             let importNavigationItem = new WI.ButtonNavigationItem("import-audit", WI.UIString("Import"), "Images/Import.svg", 15, 15);
     56            importNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
     57            importNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleImportButtonNavigationItemClicked, this);
     58
     59             let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to import a test or result file"), importNavigationItem);
     60            contentPlaceholder.appendChild(importHelpElement);
     61        }
    4862
    4963        this.contentBrowser.showContentView(contentView);
     
    5872        this.contentTreeOutline.allowsRepeatSelection = false;
    5973
    60         let navigationBar = new WI.NavigationBar;
     74        let controlsNavigationBar = new WI.NavigationBar;
    6175
    6276        this._startStopButtonNavigationItem = new WI.ToggleButtonNavigationItem("audit-start-stop", WI.UIString("Start"), WI.UIString("Stop"), "Images/AuditStart.svg", "Images/AuditStop.svg", 13, 13);
     
    6478        this._updateStartStopButtonNavigationItemState();
    6579        this._startStopButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleStartStopButtonNavigationItemClicked, this);
    66         navigationBar.addNavigationItem(this._startStopButtonNavigationItem);
    67 
    68         navigationBar.addNavigationItem(new WI.DividerNavigationItem);
     80        controlsNavigationBar.addNavigationItem(this._startStopButtonNavigationItem);
     81
     82        controlsNavigationBar.addNavigationItem(new WI.DividerNavigationItem);
    6983
    7084        let importButtonNavigationItem = new WI.ButtonNavigationItem("audit-import", WI.UIString("Import"), "Images/Import.svg", 15, 15);
     
    7286        importButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
    7387        importButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleImportButtonNavigationItemClicked, this);
    74         navigationBar.addNavigationItem(importButtonNavigationItem);
    75 
    76         this.addSubview(navigationBar);
     88        controlsNavigationBar.addNavigationItem(importButtonNavigationItem);
     89
     90        this.addSubview(controlsNavigationBar);
     91
     92        let editNavigationbar = new WI.NavigationBar;
     93
     94        this._editButtonNavigationItem = new WI.ActivateButtonNavigationItem("edit-audits", WI.UIString("Edit"), WI.UIString("Done"));
     95        this._editButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleEditButtonNavigationItemClicked, this);
     96        editNavigationbar.addNavigationItem(this._editButtonNavigationItem);
     97
     98        this.contentView.addSubview(editNavigationbar);
    7799
    78100        for (let test of WI.auditManager.tests)
     
    83105        });
    84106
     107        WI.auditManager.addEventListener(WI.AuditManager.Event.EditingChanged, this._handleAuditManagerEditingChanged, this);
    85108        WI.auditManager.addEventListener(WI.AuditManager.Event.TestAdded, this._handleAuditTestAdded, this);
    86109        WI.auditManager.addEventListener(WI.AuditManager.Event.TestCompleted, this._handleAuditTestCompleted, this);
     
    106129    }
    107130
     131    hasCustomFilters()
     132    {
     133        return true;
     134    }
     135
     136    matchTreeElementAgainstCustomFilters(treeElement, flags)
     137    {
     138        if (WI.auditManager.editing) {
     139            if (treeElement.representedObject instanceof WI.AuditTestResultBase || treeElement.hasAncestor(this._resultsFolderTreeElement) || treeElement === this._resultsFolderTreeElement)
     140                return false;
     141        } else {
     142            if (treeElement.representedObject instanceof WI.AuditTestBase && treeElement.representedObject.disabled)
     143                return false;
     144        }
     145
     146        return super.matchTreeElementAgainstCustomFilters(treeElement, flags);
     147    }
     148
    108149    // Private
    109150
     
    111152    {
    112153        this.element.classList.add("has-tests");
    113 
    114         this._updateStartStopButtonNavigationItemState();
    115154
    116155        let treeElement = new WI.AuditTreeElement(test);
     
    122161            this.contentTreeOutline.appendChild(treeElement);
    123162
     163        this._updateStartStopButtonNavigationItemState();
     164        this._updateEditButtonNavigationItemState();
     165
    124166        this.hideEmptyContentPlaceholder();
    125167    }
     
    128170    {
    129171        this.element.classList.add("has-results");
    130 
    131         this._updateStartStopButtonNavigationItemState();
    132172
    133173        if (!this._resultsFolderTreeElement) {
     
    145185        this._resultsFolderTreeElement.appendChild(resultFolderTreeElement);
    146186
     187        console.assert(this._resultsFolderTreeElement.children.length === WI.auditManager.results.length);
     188
    147189        for (let resultItem of result)
    148190            resultFolderTreeElement.appendChild(new WI.AuditTreeElement(resultItem));
     191
     192        this._updateStartStopButtonNavigationItemState();
     193        this._updateEditButtonNavigationItemState();
    149194    }
    150195
    151196    _updateStartStopButtonNavigationItemState()
    152197    {
    153         this._startStopButtonNavigationItem.toggled = WI.auditManager.runningState !== WI.AuditManager.RunningState.Inactive;
    154         this._startStopButtonNavigationItem.enabled = WI.auditManager.tests.length && WI.auditManager.runningState !== WI.AuditManager.RunningState.Stopping;
     198        this._startStopButtonNavigationItem.toggled = WI.auditManager.runningState === WI.AuditManager.RunningState.Active || WI.auditManager.runningState === WI.AuditManager.RunningState.Stopping;
     199        this._startStopButtonNavigationItem.enabled = WI.auditManager.tests.length && (WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive || WI.auditManager.runningState === WI.AuditManager.RunningState.Active);
     200    }
     201
     202     _updateEditButtonNavigationItemState()
     203    {
     204        this._editButtonNavigationItem.label = WI.auditManager.editing ? this._editButtonNavigationItem.activatedToolTip : this._editButtonNavigationItem.defaultToolTip;
     205        this._editButtonNavigationItem.activated = WI.auditManager.editing;
     206        this._editButtonNavigationItem.enabled = WI.auditManager.tests.length && (WI.auditManager.editing || WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive);
    155207    }
    156208
     
    177229            this.contentView.element.insertBefore(contentPlaceholder, this.contentView.element.firstChild);
    178230        }
     231
     232        this._updateEditButtonNavigationItemState();
     233    }
     234
     235    _handleAuditManagerEditingChanged(event)
     236    {
     237        if (WI.auditManager.editing) {
     238            console.assert(!this._selectedTreeElementBeforeEditing);
     239            this._selectedTreeElementBeforeEditing = this.contentTreeOutline.selectedTreeElement;
     240            if (this._selectedTreeElementBeforeEditing)
     241                this._selectedTreeElementBeforeEditing.deselect();
     242        } else if (this._selectedTreeElementBeforeEditing) {
     243            if (!(this._selectedTreeElementBeforeEditing.representedObject instanceof WI.AuditTestBase) || !this._selectedTreeElementBeforeEditing.representedObject.disabled)
     244                this._selectedTreeElementBeforeEditing.select();
     245            this._selectedTreeElementBeforeEditing = null;
     246        }
     247
     248        if (!this.contentTreeOutline.selectedTreeElement)
     249            this.showDefaultContentView();
     250
     251        this._updateStartStopButtonNavigationItemState();
     252        this._updateEditButtonNavigationItemState();
     253
     254        this.updateFilter();
    179255    }
    180256
     
    205281    {
    206282        this._updateStartStopButtonNavigationItemState();
     283        this._updateEditButtonNavigationItemState();
    207284    }
    208285
     
    217294            return;
    218295        }
     296
     297        console.assert(!WI.auditManager.editing);
     298        if (WI.auditManager.editing)
     299            return;
    219300
    220301        let representedObject = treeElement.representedObject;
     
    242323        WI.FileUtilities.importJSON((result) => WI.auditManager.processJSON(result));
    243324    }
     325
     326    _handleEditButtonNavigationItemClicked(event)
     327    {
     328        WI.auditManager.editing = !WI.auditManager.editing;
     329    }
    244330};
  • trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.js

    r239452 r239858  
    150150
    151151        for (let subobject of this._subobjects()) {
     152            if (subobject instanceof WI.AuditTestBase && subobject.disabled)
     153                continue;
     154
    152155            let view = WI.ContentView.contentViewForRepresentedObject(subobject);
    153156            this.contentView.addSubview(view);
  • trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.css

    r239705 r239858  
    4444
    4545.tree-outline .item.audit > .status:not(:hover) > img.show-on-hover,
    46 .tree-outline .item.audit.test-group.expanded > .status:not(:hover) {
     46.tree-outline .item.audit.test-group.expanded:not(.editing-audits) > .status:not(:hover) {
    4747    opacity: 0;
    4848}
    4949
    5050.tree-outline .item.audit.manager-active > .status > img.show-on-hover,
    51 .tree-outline .item.audit.test-group.expanded > .status:hover > :not(img),
     51.tree-outline .item.audit.test-group.expanded:not(.editing-audits) > .status:hover > :not(img),
    5252.tree-outline .item.audit.test-group-result.expanded > .status {
    5353    display: none;
  • trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.js

    r239705 r239858  
    6262
    6363        if (this.representedObject instanceof WI.AuditTestBase) {
     64            this.representedObject.addEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
    6465            this.representedObject.addEventListener(WI.AuditTestBase.Event.ResultCleared, this._handleTestResultCleared, this);
    6566
     
    6970                this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestGroupScheduled, this);
    7071
     72            WI.auditManager.addEventListener(WI.AuditManager.Event.EditingChanged, this._handleManagerEditingChanged, this);
    7173            WI.auditManager.addEventListener(WI.AuditManager.Event.TestScheduled, this._handleAuditManagerTestScheduled, this);
    7274            WI.auditManager.addEventListener(WI.AuditManager.Event.TestCompleted, this._handleAuditManagerTestCompleted, this);
     
    161163
    162164        super.populateContextMenu(contextMenu, event);
     165    }
     166
     167    canSelectOnMouseDown(event)
     168    {
     169        return !WI.auditManager.editing;
    163170    }
    164171
     
    233240    }
    234241
     242    _updateTestGroupDisabled()
     243    {
     244        this.status.checked = !this.representedObject.disabled;
     245
     246        if (this.representedObject instanceof WI.AuditTestGroup)
     247            this.status.indeterminate = this.representedObject.tests.some((test) => test.disabled !== this.representedObject.tests[0].disabled);
     248    }
     249
    235250    _handleTestCaseCompleted(event)
    236251    {
     
    238253
    239254        this._updateLevel();
     255    }
     256
     257    _handleTestDisabledChanged(event)
     258    {
     259        if (this.status instanceof HTMLInputElement && this.status.type === "checkbox")
     260            this._updateTestGroupDisabled();
    240261    }
    241262
     
    274295    }
    275296
     297    _handleManagerEditingChanged(event)
     298    {
     299        if (WI.auditManager.editing) {
     300            this.status = document.createElement("input");
     301            this.status.type = "checkbox";
     302            this._updateTestGroupDisabled();
     303            this.status.addEventListener("change", () => {
     304                this.representedObject.disabled = !this.representedObject.disabled;
     305            });
     306
     307            this.addClassName("editing-audits");
     308        } else {
     309            this.removeClassName("editing-audits");
     310
     311            this._updateLevel();
     312        }
     313    }
     314
    276315    _handleAuditManagerTestScheduled(event)
    277316    {
  • trunk/Source/WebInspectorUI/UserInterface/Views/TreeOutline.css

    r239760 r239858  
    4141}
    4242
    43 .tree-outline .children.expanded {
     43.tree-outline .children.expanded:not([hidden]) {
    4444    display: block;
    4545}
Note: See TracChangeset for help on using the changeset viewer.