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

Changeset 245914 in webkit


Ignore:
Timestamp:
May 30, 2019, 5:12:03 PM (7 years ago)
Author:
Devin Rousso
Message:

Web Inspector: Audit: there should be a default test for WebInspectorAudit.Resources functionality
https://bugs.webkit.org/show_bug.cgi?id=196710
<rdar://problem/49712348>

Reviewed by Joseph Pecoraro.

Source/JavaScriptCore:

  • inspector/protocol/Audit.json:

Increment Audit version.

Source/WebInspectorUI:

Previously, there was no way to return data from Audit that wasn't a DOM node, a DOM
attribute (which wasn't "shown" anywhere, as it would highlight that attribute on any
returned DOM nodes), or an error string.

In order for Audits to be more flexible with the types of things they test, there needs to
be a way for other types of data to be sent back and displayed in the Audit tab.

This patch makes it so that an Audit result can now contain other keys/values that are all
expected to be JSON serializable. It will take all of the non-"special" (e.g. "domNodes" or
"errors") keys/values and display them as object trees. This way, any JSON serializable data
can be sent with the result and be displayed in the Audit tab.

  • UserInterface/Models/AuditTestBase.js:

(WI.AuditTestBase.prototype.async setup):
(WI.AuditTestBase.prototype.clearResult):

  • UserInterface/Models/AuditTestGroup.js:

(WI.AuditTestCase.prototype.clearResult):
(WI.AuditTestCase.prototype._updateResult):

  • UserInterface/Models/AuditTestCase.js:

(WI.AuditTestCase.prototype.async run):
(WI.AuditTestCase.prototype.async run.async parseResponse):
(WI.AuditTestCase.prototype.async run.async parseResponse.checkResultProperty):
(WI.AuditTestCase.prototype.async run.async parseResponse.checkResultProperty.addErrorForValueType):
(WI.AuditTestCase.prototype.async run.async parseResponse.async resultArrayForEach):
(WI.AuditTestCase.prototype.async run.async parseResponse.inspectedPage_stringify): Added.
Rename ResultCleared to ResultChanged so that it can (semantically) be used whenever a
new result is set in addition to when an existing one is cleared. This is needed so that
AuditTestCaseContentView will refresh each time the result changes, instead of only in the
first layout() after the last result was cleared.

  • UserInterface/Models/AuditTestCaseResult.js:

(WI.AuditTestCaseResult.async fromPayload):
(WI.AuditTestCaseResult.prototype.toJSON):
Drive-by: fix the check for optional data values to still warn if the value is null.

  • UserInterface/Controllers/AuditManager.js:

(WI.AuditManager.prototype._addDefaultTests):
Add a default test "Demo Audit > Result Data > data-custom" as an example how to write an
Audit that returns custom data, as well as how that custom data is shown in the Audit tab.

  • UserInterface/Views/AuditTestContentView.js:

(WI.AuditTestContentView.prototype.shown):
(WI.AuditTestContentView.prototype.handleResultChanged): Added.

  • UserInterface/Views/AuditTestCaseContentView.js:

(WI.AuditTestCaseContentView):
(WI.AuditTestCaseContentView.prototype.layout):
(WI.AuditTestCaseContentView.prototype.handleResultChanged): Added.
Preserve the UI for each section across layout()s, so that expand/collapse states aren't
reset each time the user changes the selected Audit.

  • UserInterface/Views/AuditTestCaseContentView.css:

(.content-view.audit-test-case > section table > tr > td + td): Added.
Drive-by: ensure that the "index" table column is never larger than it needs to be.

  • UserInterface/Views/AuditTreeElement.js:

(WI.AuditTreeElement.prototype.onattach):
(WI.AuditTreeElement.prototype._handleTestResultChanged): Added.
(WI.AuditTreeElement.prototype._handleTestResultCleared): Deleted.

  • Localizations/en.lproj/localizedStrings.js:

LayoutTests:

  • inspector/audit/manager-start-setup.html:
  • inspector/audit/manager-start-setup-expected.txt:
  • inspector/model/auditTestCase-expected.txt:
  • inspector/model/auditTestCaseResult-expected.txt:
  • inspector/model/auditTestGroup-expected.txt:
  • inspector/unit-tests/object-utilities.html:
  • inspector/unit-tests/object-utilities-expected.txt:
Location:
trunk
Files:
22 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r245912 r245914  
     12019-05-30  Devin Rousso  <drousso@apple.com>
     2
     3        Web Inspector: Audit: there should be a default test for WebInspectorAudit.Resources functionality
     4        https://bugs.webkit.org/show_bug.cgi?id=196710
     5        <rdar://problem/49712348>
     6
     7        Reviewed by Joseph Pecoraro.
     8
     9        * inspector/audit/manager-start-setup.html:
     10        * inspector/audit/manager-start-setup-expected.txt:
     11        * inspector/model/auditTestCase-expected.txt:
     12        * inspector/model/auditTestCaseResult-expected.txt:
     13        * inspector/model/auditTestGroup-expected.txt:
     14        * inspector/unit-tests/object-utilities.html:
     15        * inspector/unit-tests/object-utilities-expected.txt:
     16
    1172019-05-30  Andres Gonzalez  <andresg_22@apple.com>
    218
  • trunk/LayoutTests/inspector/audit/manager-start-setup-expected.txt

    r242808 r245914  
    44== Running test suite: AuditManager.prototype.start
    55-- Running test case: AuditManager.prototype.start.SyncSetup
    6 PASS: The setup function should have set __test to 42.
     6PASS: The setup function should have set test to 42.
    77
    88-- Running test case: AuditManager.prototype.start.AsyncSetup
    9 PASS: The setup function should have set __test to 42.
     9PASS: The setup function should have set test to 42.
    1010
    1111-- Running test case: AuditManager.prototype.start.SubLevelSetup
    12 PASS: The setup function should have set __test to undefined.
     12PASS: The setup function should have set test to undefined.
    1313
    1414-- Running test case: AuditManager.prototype.start.OverriddenSetup
    15 PASS: The setup function should have set __test to B.
     15PASS: The setup function should have set test to B.
    1616
    1717-- Running test case: AuditManager.prototype.start.MultipleTopLevel
    18 PASS: The setup function should have set __test to A.
    19 PASS: The setup function should have set __test to B.
     18PASS: The setup function should have set test to A.
     19PASS: The setup function should have set test to B.
    2020
  • trunk/LayoutTests/inspector/audit/manager-start-setup.html

    r242808 r245914  
    88{
    99    const auditTestString = (function() {
    10         return {
    11             level: "pass",
    12             __test: WebInspectorAudit.__test,
    13         };
    14     }).toString();
     10    return {
     11        level: "pass",
     12        test: WebInspectorAudit.test,
     13    };
     14}).toString();
    1515
    1616    async function wrapTest(audit, expected, {getResultCallback} = {}) {
     
    2323        InspectorTest.assert(result.didPass, "The test should pass.");
    2424        InspectorTest.assert(!result.data.errors, "There should be no errors.");
    25         InspectorTest.expectEqual(result.data.__test.value, expected, `The setup function should have set __test to ${expected}.`);
     25        if (result.data.errors)
     26            InspectorTest.json(result.data.errors);
     27
     28        InspectorTest.expectEqual(result.data.test, expected, `The setup function should have set test to ${expected}.`);
    2629
    2730        WI.auditManager.removeTest(audit);
     
    3538        async test() {
    3639            const setup = (function() {
    37                 WebInspectorAudit.__test = 42;
     40                WebInspectorAudit.test = 42;
    3841            }).toString();
    3942
     
    5356                });
    5457
    55                 WebInspectorAudit.__test = 42;
     58                WebInspectorAudit.test = 42;
    5659            }).toString();
    5760
     
    6770        async test() {
    6871            const setup = (function() {
    69                 WebInspectorAudit.__test = 42;
     72                WebInspectorAudit.test = 42;
    7073            }).toString();
    7174
     
    8790        async test() {
    8891            const setupA = (function() {
    89                 if (!WebInspectorAudit.__test)
    90                     WebInspectorAudit.__test = "";
    91                 WebInspectorAudit.__test += "A";
     92                if (!WebInspectorAudit.test)
     93                    WebInspectorAudit.test = "";
     94                WebInspectorAudit.test += "A";
    9295            }).toString();
    9396
    9497            const setupB = (function() {
    95                 if (!WebInspectorAudit.__test)
    96                     WebInspectorAudit.__test = "";
    97                 WebInspectorAudit.__test += "B";
     98                if (!WebInspectorAudit.test)
     99                    WebInspectorAudit.test = "";
     100                WebInspectorAudit.test += "B";
    98101            }).toString();
    99102
     
    115118        async test() {
    116119            const setupA = (function() {
    117                 if (!WebInspectorAudit.__test)
    118                     WebInspectorAudit.__test = "";
    119                 WebInspectorAudit.__test += "A";
     120                if (!WebInspectorAudit.test)
     121                    WebInspectorAudit.test = "";
     122                WebInspectorAudit.test += "A";
    120123            }).toString();
    121124
     
    125128
    126129            const setupB = (function() {
    127                 if (!WebInspectorAudit.__test)
    128                     WebInspectorAudit.__test = "";
    129                 WebInspectorAudit.__test += "B";
     130                if (!WebInspectorAudit.test)
     131                    WebInspectorAudit.test = "";
     132                WebInspectorAudit.test += "B";
    130133            }).toString();
    131134
  • trunk/LayoutTests/inspector/model/auditTestCase-expected.txt

    r242808 r245914  
    2929  "type": "test-case",
    3030  "name": "validWithInvalidOptionals test name",
    31   "supports": 3,
     31  "supports": 4,
    3232  "test": "validWithInvalidOptionals test function"
    3333}
     
    3838  "name": "validWithValidOptionals test name",
    3939  "description": "validWithValidOptionals test description",
    40   "supports": 1,
     40  "supports": 2,
    4141  "setup": "validWithValidOptionals test setup",
    4242  "test": "validWithValidOptionals test function"
  • trunk/LayoutTests/inspector/model/auditTestCaseResult-expected.txt

    r240471 r245914  
    3333
    3434-- Running test case: AuditTestCaseResult.fromPayload.validWithInvalidSubOptionals
     35WARN: Audit Warning: "validWithInvalidSubOptionals test result name" has a non-array "data.domNodes" value
     36WARN: Audit Warning: "validWithInvalidSubOptionals test result name" has a non-array "data.domAttributes" value
     37WARN: Audit Warning: "validWithInvalidSubOptionals test result name" has a non-array "data.errors" value
    3538WARN: Audit Warning: "validWithInvalidSubOptionals test result name" has a non-object "metadata.startTimestamp" value
    3639WARN: Audit Warning: "validWithInvalidSubOptionals test result name" has a non-object "metadata.asyncTimestamp" value
  • trunk/LayoutTests/inspector/model/auditTestGroup-expected.txt

    r242808 r245914  
    4444  "type": "test-group",
    4545  "name": "validWithInvalidOptionals group name",
    46   "supports": 3,
     46  "supports": 4,
    4747  "tests": [
    4848    {
    4949      "type": "test-case",
    5050      "name": "validWithInvalidOptionals test name",
    51       "supports": 4,
     51      "supports": 5,
    5252      "test": "validWithInvalidOptionals test function"
    5353    }
     
    6060  "name": "validWithValidOptionals group name",
    6161  "description": "validWithValidOptionals group description",
    62   "supports": 1,
     62  "supports": 2,
    6363  "setup": "validWithValidOptionals group setup",
    6464  "tests": [
     
    6767      "name": "validWithValidOptionals test name",
    6868      "description": "validWithValidOptionals test description",
    69       "supports": 0,
     69      "supports": 1,
    7070      "setup": "validWithValidOptionals test setup",
    7171      "test": "validWithValidOptionals test function"
     
    7979  "name": "validNested group name",
    8080  "description": "validNested group description",
    81   "supports": 1,
     81  "supports": 2,
    8282  "setup": "validNested group setup",
    8383  "tests": [
     
    8686      "name": "validNested nested group name",
    8787      "description": "validNested nested group description",
    88       "supports": 0,
     88      "supports": 1,
    8989      "setup": "validNested nested group setup",
    9090      "tests": [
     
    9393          "name": "validNested nested test name",
    9494          "description": "validNested nested test description",
    95           "supports": -1,
     95          "supports": 0,
    9696          "setup": "validNested nested test setup",
    9797          "test": "validNested nested test function"
     
    103103      "name": "validNested test name",
    104104      "description": "validNested test description",
    105       "supports": -2,
     105      "supports": -1,
    106106      "setup": "validNested test setup",
    107107      "test": "validNested test function"
  • trunk/LayoutTests/inspector/unit-tests/object-utilities-expected.txt

    r228336 r245914  
    2323PASS: shallowEqual of objects with different constructors should be false.
    2424
     25-- Running test case: Object.filter
     26PASS: filter should remove all entries where the key isn't in ["a","b","c"].
     27PASS: filter should remove all entries where the key isn't in ["a"].
     28PASS: filter should remove all entries where the key isn't in ["b"].
     29PASS: filter should remove all entries where the key isn't in ["c"].
     30PASS: filter should remove all entries where the value isn't in [1,2,3].
     31PASS: filter should remove all entries where the value isn't in [1].
     32PASS: filter should remove all entries where the value isn't in [2].
     33PASS: filter should remove all entries where the value isn't in [3].
     34
  • trunk/LayoutTests/inspector/unit-tests/object-utilities.html

    r243355 r245914  
    5252    });
    5353
     54    suite.addTestCase({
     55        name: "Object.filter",
     56        test() {
     57            const object = {a: 1, b: 2};
     58
     59            function checkKey(keys, expected) {
     60                InspectorTest.expectShallowEqual(Object.filter(object, (key, value) => keys.includes(key)), expected, `filter should remove all entries where the key isn't in ${JSON.stringify(keys)}.`);
     61            }
     62            checkKey(["a", "b", "c"], {a: 1, b: 2});
     63            checkKey(["a"], {a: 1});
     64            checkKey(["b"], {b: 2});
     65            checkKey(["c"], {});
     66
     67            function checkValue(values, expected) {
     68                InspectorTest.expectShallowEqual(Object.filter(object, (key, value) => values.includes(value)), expected, `filter should remove all entries where the value isn't in ${JSON.stringify(values)}.`);
     69            }
     70            checkValue([1, 2, 3], {a: 1, b: 2});
     71            checkValue([1], {a: 1});
     72            checkValue([2], {b: 2});
     73            checkValue([3], {});
     74        }
     75    });
     76
    5477    suite.runTestCasesAndFinish();
    5578}
  • trunk/Source/JavaScriptCore/ChangeLog

    r245909 r245914  
     12019-05-30  Devin Rousso  <drousso@apple.com>
     2
     3        Web Inspector: Audit: there should be a default test for WebInspectorAudit.Resources functionality
     4        https://bugs.webkit.org/show_bug.cgi?id=196710
     5        <rdar://problem/49712348>
     6
     7        Reviewed by Joseph Pecoraro.
     8
     9        * inspector/protocol/Audit.json:
     10        Increment Audit version.
     11
    1122019-05-30  Devin Rousso  <drousso@apple.com>
    213
  • trunk/Source/JavaScriptCore/inspector/protocol/Audit.json

    r242808 r245914  
    22    "domain": "Audit",
    33    "description": "",
    4     "version": 2,
     4    "version": 3,
    55    "commands": [
    66        {
  • trunk/Source/WebInspectorUI/ChangeLog

    r245833 r245914  
     12019-05-30  Devin Rousso  <drousso@apple.com>
     2
     3        Web Inspector: Audit: there should be a default test for WebInspectorAudit.Resources functionality
     4        https://bugs.webkit.org/show_bug.cgi?id=196710
     5        <rdar://problem/49712348>
     6
     7        Reviewed by Joseph Pecoraro.
     8
     9        Previously, there was no way to return data from Audit that wasn't a DOM node, a DOM
     10        attribute (which wasn't "shown" anywhere, as it would highlight that attribute on any
     11        returned DOM nodes), or an error string.
     12
     13        In order for Audits to be more flexible with the types of things they test, there needs to
     14        be a way for other types of data to be sent back and displayed in the Audit tab.
     15
     16        This patch makes it so that an Audit result can now contain other keys/values that are all
     17        expected to be JSON serializable. It will take all of the non-"special" (e.g. "domNodes" or
     18        "errors") keys/values and display them as object trees. This way, any JSON serializable data
     19        can be sent with the result and be displayed in the Audit tab.
     20
     21        * UserInterface/Models/AuditTestBase.js:
     22        (WI.AuditTestBase.prototype.async setup):
     23        (WI.AuditTestBase.prototype.clearResult):
     24        * UserInterface/Models/AuditTestGroup.js:
     25        (WI.AuditTestCase.prototype.clearResult):
     26        (WI.AuditTestCase.prototype._updateResult):
     27        * UserInterface/Models/AuditTestCase.js:
     28        (WI.AuditTestCase.prototype.async run):
     29        (WI.AuditTestCase.prototype.async run.async parseResponse):
     30        (WI.AuditTestCase.prototype.async run.async parseResponse.checkResultProperty):
     31        (WI.AuditTestCase.prototype.async run.async parseResponse.checkResultProperty.addErrorForValueType):
     32        (WI.AuditTestCase.prototype.async run.async parseResponse.async resultArrayForEach):
     33        (WI.AuditTestCase.prototype.async run.async parseResponse.inspectedPage_stringify): Added.
     34        Rename `ResultCleared` to `ResultChanged` so that it can (semantically) be used whenever a
     35        new result is set in addition to when an existing one is cleared. This is needed so that
     36        `AuditTestCaseContentView` will refresh each time the result changes, instead of only in the
     37        first `layout()` after the last result was cleared.
     38
     39        * UserInterface/Models/AuditTestCaseResult.js:
     40        (WI.AuditTestCaseResult.async fromPayload):
     41        (WI.AuditTestCaseResult.prototype.toJSON):
     42        Drive-by: fix the check for optional `data` values to still warn if the value is `null`.
     43
     44        * UserInterface/Controllers/AuditManager.js:
     45        (WI.AuditManager.prototype._addDefaultTests):
     46        Add a default test "Demo Audit > Result Data > data-custom" as an example how to write an
     47        Audit that returns custom data, as well as how that custom data is shown in the Audit tab.
     48
     49        * UserInterface/Views/AuditTestContentView.js:
     50        (WI.AuditTestContentView.prototype.shown):
     51        (WI.AuditTestContentView.prototype.handleResultChanged): Added.
     52        * UserInterface/Views/AuditTestCaseContentView.js:
     53        (WI.AuditTestCaseContentView):
     54        (WI.AuditTestCaseContentView.prototype.layout):
     55        (WI.AuditTestCaseContentView.prototype.handleResultChanged): Added.
     56        Preserve the UI for each section across `layout()`s, so that expand/collapse states aren't
     57        reset each time the user changes the selected Audit.
     58
     59        * UserInterface/Views/AuditTestCaseContentView.css:
     60        (.content-view.audit-test-case > section table > tr > td + td): Added.
     61        Drive-by: ensure that the "index" table column is never larger than it needs to be.
     62
     63        * UserInterface/Views/AuditTreeElement.js:
     64        (WI.AuditTreeElement.prototype.onattach):
     65        (WI.AuditTreeElement.prototype._handleTestResultChanged): Added.
     66        (WI.AuditTreeElement.prototype._handleTestResultCleared): Deleted.
     67
     68        * Localizations/en.lproj/localizedStrings.js:
     69
    1702019-05-28  Devin Rousso  <drousso@apple.com>
    271
  • trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js

    r245833 r245914  
    10821082localizedStrings["This action moves the path outside the visible area"] = "This action moves the path outside the visible area";
    10831083localizedStrings["This audit is not supported"] = "This audit is not supported";
     1084localizedStrings["This is an example of how custom result data is shown."] = "This is an example of how custom result data is shown.";
    10841085localizedStrings["This is an example of how errors are shown. The error was thrown manually, but execution errors will appear in the same way."] = "This is an example of how errors are shown. The error was thrown manually, but execution errors will appear in the same way.";
    1085 localizedStrings["This is an example of how result DOM nodes are shown. It will pass with all elements with an id attribute."] = "This is an example of how result DOM nodes are shown. It will pass with all elements with an id attribute.";
     1086localizedStrings["This is an example of how result DOM attributes are highlighted on any returned DOM nodes. It will pass with all elements with an id attribute."] = "This is an example of how result DOM attributes are highlighted on any returned DOM nodes. It will pass with all elements with an id attribute.";
    10861087localizedStrings["This is an example of how result DOM nodes are shown. It will pass with the <body> element."] = "This is an example of how result DOM nodes are shown. It will pass with the <body> element.";
    10871088localizedStrings["This is what the result of a failing test with no data looks like."] = "This is what the result of a failing test with no data looks like.";
     
    12051206localizedStrings["\u0022%s\u0022 has a non-string \u0022%s\u0022 value"] = "\u0022%s\u0022 has a non-string \u0022%s\u0022 value";
    12061207localizedStrings["\u0022%s\u0022 has an invalid \u0022%s\u0022 value"] = "\u0022%s\u0022 has an invalid \u0022%s\u0022 value";
     1208localizedStrings["\u0022%s\u0022 is not JSON serializable"] = "\u0022%s\u0022 is not JSON serializable";
    12071209localizedStrings["\u0022%s\u0022 is not valid for %s"] = "\u0022%s\u0022 is not valid for %s";
    12081210localizedStrings["\u0022%s\u0022 is too new to run in this Web Inspector"] = "\u0022%s\u0022 is too new to run in this Web Inspector";
  • trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js

    r244154 r245914  
    8181});
    8282
     83Object.defineProperty(Object, "filter",
     84{
     85    value(object, callback)
     86    {
     87        let filtered = {};
     88        for (let key in object) {
     89            if (callback(key, object[key]))
     90                filtered[key] = object[key];
     91        }
     92        return filtered;
     93    }
     94});
     95
    8396Object.defineProperty(Object.prototype, "valueForCaseInsensitiveKey",
    8497{
  • trunk/Source/WebInspectorUI/UserInterface/Controllers/AuditManager.js

    r244039 r245914  
    892892                new WI.AuditTestGroup(WI.UIString("Result Data"), [
    893893                    new WI.AuditTestCase(`data-domNodes`, `function() { return {domNodes: [document.body], level: "pass"}; }`, {description: WI.UIString("This is an example of how result DOM nodes are shown. It will pass with the <body> element.")}),
    894                     new WI.AuditTestCase(`data-domAttributes`, `function() { return {domNodes: Array.from(document.querySelectorAll("[id]")), domAttributes: ["id"], level: "pass"}; }`, {description: WI.UIString("This is an example of how result DOM nodes are shown. It will pass with all elements with an id attribute.")}),
     894                    new WI.AuditTestCase(`data-domAttributes`, `function() { return {domNodes: Array.from(document.querySelectorAll("[id]")), domAttributes: ["id"], level: "pass"}; }`, {description: WI.UIString("This is an example of how result DOM attributes are highlighted on any returned DOM nodes. It will pass with all elements with an id attribute.")}),
    895895                    new WI.AuditTestCase(`data-errors`, `function() { throw Error("this error was thrown from inside the audit test code."); }`, {description: WI.UIString("This is an example of how errors are shown. The error was thrown manually, but execution errors will appear in the same way.")}),
     896                    new WI.AuditTestCase(`data-custom`, `function() { return {level: "pass", a: 1, b: [2], c: {key: 3}}; }`, {description: WI.UIString("This is an example of how custom result data is shown.")}),
    896897                ], {description: WI.UIString("These are all of the different types of data that can be returned with the test result.")}),
    897898            ], {description: WI.UIString("These tests serve as a demonstration of the functionality and structure of audits.")}),
  • trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestBase.js

    r242808 r245914  
    117117            agentCommandFunction = RuntimeAgent.evaluate;
    118118            agentCommandArguments.expression = `(function() { "use strict"; return eval(\`(${this._setup.replace(/`/g, "\\`")})\`)(); })()`;
    119             agentCommandArguments.objectGroup = "audit";
     119            agentCommandArguments.objectGroup = AuditTestBase.ObjectGroup;
    120120            agentCommandArguments.doNotPauseOnExceptionsAndMuteConsole = true;
    121121        }
     
    192192        this._result = null;
    193193
    194         if (!options.suppressResultClearedEvent)
    195             this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultCleared);
     194        if (!options.suppressResultChangedEvent)
     195            this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged);
    196196
    197197        return true;
     
    229229
    230230// Keep this in sync with Inspector::Protocol::Audit::VERSION.
    231 WI.AuditTestBase.Version = 2;
     231WI.AuditTestBase.Version = 3;
     232
     233WI.AuditTestBase.ObjectGroup = "audit";
    232234
    233235WI.AuditTestBase.Event = {
     
    235237    DisabledChanged: "audit-test-base-disabled-changed",
    236238    Progress: "audit-test-base-progress",
    237     ResultCleared: "audit-test-base-result-cleared",
     239    ResultChanged: "audit-test-base-result-changed",
    238240    Scheduled: "audit-test-base-scheduled",
    239241    Stopping: "audit-test-base-stopping",
  • trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js

    r242808 r245914  
    127127        async function parseResponse(response) {
    128128            let remoteObject = WI.RemoteObject.fromPayload(response.result, WI.mainTarget);
    129             if (response.wasThrown || (remoteObject.type === "object" && remoteObject.subtype === "error"))
     129            if (response.wasThrown || (remoteObject.type === "object" && remoteObject.subtype === "error")) {
    130130                addError(remoteObject.description);
    131             else if (remoteObject.type === "boolean")
     131                return;
     132            }
     133
     134            if (remoteObject.type === "boolean") {
    132135                setLevel(remoteObject.value ? WI.AuditTestCaseResult.Level.Pass : WI.AuditTestCaseResult.Level.Fail);
    133             else if (remoteObject.type === "string")
     136                return;
     137            }
     138
     139            if (remoteObject.type === "string") {
    134140                setLevel(remoteObject.value.trim().toLowerCase());
    135             else if (remoteObject.type === "object" && !remoteObject.subtype) {
    136                 const options = {
    137                     ownProperties: true,
    138                 };
    139 
    140                 let properties = await new Promise((resolve, reject) => remoteObject.getPropertyDescriptorsAsObject(resolve, options));
    141 
    142                 function checkResultProperty(key, type, subtype) {
    143                     if (!(key in properties))
    144                         return null;
    145 
    146                     let property = properties[key].value;
    147                     if (!property)
    148                         return null;
    149 
    150                     function addErrorForValueType(valueType) {
    151                         let value = null;
    152                         if (valueType === "object" || valueType === "array")
    153                             value = WI.UIString("\u0022%s\u0022 must be an %s");
    154                         else
    155                             value = WI.UIString("\u0022%s\u0022 must be a %s");
    156                         addError(value.format(key, valueType));
    157                     }
    158 
    159                     if (property.subtype !== subtype) {
    160                         addErrorForValueType(subtype);
    161                         return null;
    162                     }
    163 
    164                     if (property.type !== type) {
    165                         addErrorForValueType(type);
    166                         return null;
    167                     }
    168 
    169                     if (type === "boolean" || type === "string")
    170                         return property.value;
    171 
    172                     return property;
    173                 }
    174 
    175                 async function resultArrayForEach(key, callback) {
    176                     let array = checkResultProperty(key, "object", "array");
    177                     if (!array)
    178                         return;
    179 
    180                     // `getPropertyDescriptorsAsObject` returns an object, meaning that if we
    181                     // want to iterate over `array` by index, we have to count.
    182                     let asObject = await new Promise((resolve, reject) => array.getPropertyDescriptorsAsObject(resolve, options));
    183                     for (let i = 0; i < array.size; ++i) {
    184                         if (i in asObject)
    185                             await callback(asObject[i]);
    186                     }
    187                 }
    188 
    189                 let levelString = checkResultProperty("level", "string");
    190                 if (levelString)
    191                     setLevel(levelString.trim().toLowerCase());
    192 
    193                 if (checkResultProperty("pass", "boolean"))
    194                     setLevel(WI.AuditTestCaseResult.Level.Pass);
    195                 if (checkResultProperty("warn", "boolean"))
    196                     setLevel(WI.AuditTestCaseResult.Level.Warn);
    197                 if (checkResultProperty("fail", "boolean"))
    198                     setLevel(WI.AuditTestCaseResult.Level.Fail);
    199                 if (checkResultProperty("error", "boolean"))
    200                     setLevel(WI.AuditTestCaseResult.Level.Error);
    201                 if (checkResultProperty("unsupported", "boolean"))
    202                     setLevel(WI.AuditTestCaseResult.Level.Unsupported);
    203 
    204                 await resultArrayForEach("domNodes", async (item) => {
    205                     if (!item || !item.value || item.value.type !== "object" || item.value.subtype !== "node") {
    206                         addError(WI.UIString("All items in \u0022%s\u0022 must be valid DOM nodes").format(WI.unlocalizedString("domNodes")));
    207                         return;
    208                     }
    209 
    210                     let domNodeId = await new Promise((resolve, reject) => item.value.pushNodeToFrontend(resolve));
    211                     let domNode = WI.domManager.nodeForId(domNodeId);
    212                     if (!domNode)
    213                         return;
    214 
    215                     if (!data.domNodes)
    216                         data.domNodes = [];
    217                     data.domNodes.push(WI.cssPath(domNode, {full: true}));
    218 
    219                     if (!resolvedDOMNodes)
    220                         resolvedDOMNodes = [];
    221                     resolvedDOMNodes.push(domNode);
    222                 });
    223 
    224                 await resultArrayForEach("domAttributes", (item) => {
    225                     if (!item || !item.value || item.value.type !== "string" || !item.value.value.length) {
    226                         addError(WI.UIString("All items in \u0022%s\u0022 must be non-empty strings").format(WI.unlocalizedString("domAttributes")));
    227                         return;
    228                     }
    229 
    230                     if (!data.domAttributes)
    231                         data.domAttributes = [];
    232                     data.domAttributes.push(item.value.value);
    233                 });
    234 
    235                 await resultArrayForEach("errors", (item) => {
    236                     if (!item || !item.value || item.value.type !== "object" || item.value.subtype !== "error") {
    237                         addError(WI.UIString("All items in \u0022%s\u0022 must be error objects").format(WI.unlocalizedString("errors")));
    238                         return;
    239                     }
    240 
    241                     addError(item.value.description);
    242                 });
    243 
    244                 if (window.InspectorTest && properties.__test)
    245                     data.__test = properties.__test.value;
    246             } else
     141                return;
     142            }
     143
     144            if (remoteObject.type !== "object" || remoteObject.subtype) {
    247145                addError(WI.UIString("Return value is not an object, string, or boolean"));
     146                return;
     147            }
     148
     149            const options = {
     150                ownProperties: true,
     151            };
     152
     153            function checkResultProperty(key, value, type, subtype) {
     154                function addErrorForValueType(valueType) {
     155                    let errorString = null;
     156                    if (valueType === "object" || valueType === "array")
     157                        errorString = WI.UIString("\u0022%s\u0022 must be an %s");
     158                    else
     159                        errorString = WI.UIString("\u0022%s\u0022 must be a %s");
     160                    addError(errorString.format(key, valueType));
     161                }
     162
     163                if (value.subtype !== subtype) {
     164                    addErrorForValueType(subtype);
     165                    return null;
     166                }
     167
     168                if (value.type !== type) {
     169                    addErrorForValueType(type);
     170                    return null;
     171                }
     172
     173                if (type === "boolean" || type === "string")
     174                    return value.value;
     175
     176                return value;
     177            }
     178
     179            async function resultArrayForEach(key, value, callback) {
     180                let array = checkResultProperty(key, value, "object", "array");
     181                if (!array)
     182                    return;
     183
     184                // `getPropertyDescriptorsAsObject` returns an object, meaning that if we
     185                // want to iterate over `array` by index, we have to count.
     186                let asObject = await new Promise((resolve, reject) => array.getPropertyDescriptorsAsObject(resolve, options));
     187                for (let i = 0; i < array.size; ++i) {
     188                    if (i in asObject)
     189                        await callback(asObject[i]);
     190                }
     191            }
     192
     193            let properties = await new Promise((resolve, reject) => remoteObject.getPropertyDescriptors(resolve, options));
     194            for (let property of properties) {
     195                let key = property.name;
     196                if (key === "__proto__")
     197                    continue;
     198
     199                let value = property.value;
     200
     201                switch (key) {
     202                case "level": {
     203                    let levelString = checkResultProperty(key, value, "string");
     204                    if (levelString)
     205                        setLevel(levelString.trim().toLowerCase());
     206                    break;
     207                }
     208
     209                case "pass":
     210                    if (checkResultProperty(key, value, "boolean"))
     211                        setLevel(WI.AuditTestCaseResult.Level.Pass);
     212                    break;
     213
     214                case "warn":
     215                    if (checkResultProperty(key, value, "boolean"))
     216                        setLevel(WI.AuditTestCaseResult.Level.Warn);
     217                    break;
     218
     219                case "fail":
     220                    if (checkResultProperty(key, value, "boolean"))
     221                        setLevel(WI.AuditTestCaseResult.Level.Fail);
     222                    break;
     223
     224                case "error":
     225                    if (checkResultProperty(key, value, "boolean"))
     226                        setLevel(WI.AuditTestCaseResult.Level.Error);
     227                    break;
     228
     229                case "unsupported":
     230                    if (checkResultProperty(key, value, "boolean"))
     231                        setLevel(WI.AuditTestCaseResult.Level.Unsupported);
     232                    break;
     233
     234                case "domNodes":
     235                    await resultArrayForEach(key, value, async (item) => {
     236                        if (!item || !item.value || item.value.type !== "object" || item.value.subtype !== "node") {
     237                            addError(WI.UIString("All items in \u0022%s\u0022 must be valid DOM nodes").format(WI.unlocalizedString("domNodes")));
     238                            return;
     239                        }
     240
     241                        let domNodeId = await new Promise((resolve, reject) => item.value.pushNodeToFrontend(resolve));
     242                        let domNode = WI.domManager.nodeForId(domNodeId);
     243                        if (!domNode)
     244                            return;
     245
     246                        if (!data.domNodes)
     247                            data.domNodes = [];
     248                        data.domNodes.push(WI.cssPath(domNode, {full: true}));
     249
     250                        if (!resolvedDOMNodes)
     251                            resolvedDOMNodes = [];
     252                        resolvedDOMNodes.push(domNode);
     253                    });
     254                    break;
     255
     256                case "domAttributes":
     257                    await resultArrayForEach(key, value, (item) => {
     258                        if (!item || !item.value || item.value.type !== "string" || !item.value.value.length) {
     259                            addError(WI.UIString("All items in \u0022%s\u0022 must be non-empty strings").format(WI.unlocalizedString("domAttributes")));
     260                            return;
     261                        }
     262
     263                        if (!data.domAttributes)
     264                            data.domAttributes = [];
     265                        data.domAttributes.push(item.value.value);
     266                    });
     267                    break;
     268
     269                case "errors":
     270                    await resultArrayForEach(key, value, (item) => {
     271                        if (!item || !item.value || item.value.type !== "object" || item.value.subtype !== "error") {
     272                            addError(WI.UIString("All items in \u0022%s\u0022 must be error objects").format(WI.unlocalizedString("errors")));
     273                            return;
     274                        }
     275
     276                        addError(item.value.description);
     277                    });
     278                    break;
     279
     280                default:
     281                    if (value.objectId) {
     282                        try {
     283                            function inspectedPage_stringify() {
     284                                return JSON.stringify(this);
     285                            }
     286                            let stringifiedValue = await value.callFunction(inspectedPage_stringify);
     287                            data[key] = JSON.parse(stringifiedValue.value);
     288                        } catch {
     289                            addError(WI.UIString("\u0022%s\u0022 is not JSON serializable").format(key));
     290                        }
     291                    } else
     292                        data[key] = value.value;
     293                    break;
     294                }
     295            }
    248296        }
    249297
     
    256304            agentCommandFunction = RuntimeAgent.evaluate;
    257305            agentCommandArguments.expression = `(function() { "use strict"; return eval(\`(${this._test.replace(/`/g, "\\`")})\`)(); })()`;
    258             agentCommandArguments.objectGroup = "audit";
     306            agentCommandArguments.objectGroup = WI.AuditTestCase.ObjectGroup;
    259307            agentCommandArguments.doNotPauseOnExceptionsAndMuteConsole = true;
    260308        }
     
    296344            options.resolvedDOMNodes = resolvedDOMNodes;
    297345        this._result = new WI.AuditTestCaseResult(this.name, level, options);
     346
     347        this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged);
    298348    }
    299349};
  • trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCaseResult.js

    r240471 r245914  
    6868        } else {
    6969            function checkArray(key) {
    70                 if (!payload.data[key])
     70                if (!(key in payload.data))
    7171                    return;
    7272
     
    133133        if (!isEmptyObject(payload.data)) {
    134134            options.data = {};
    135             if (payload.data.domNodes && payload.data.domNodes.length) {
    136                 if (window.DOMAgent && (!payload.metadata.url || payload.metadata.url === WI.networkManager.mainFrame.url)) {
    137                     let documentNode = await new Promise((resolve) => WI.domManager.requestDocument(resolve));
    138                     options.resolvedDOMNodes = await Promise.all(payload.data.domNodes.map(async (domNodeString) => {
    139                         let nodeId = 0;
    140                         try {
    141                             nodeId = await WI.domManager.querySelector(documentNode, domNodeString);
    142                         } catch { }
    143                         return WI.domManager.nodeForId(nodeId) || null;
    144                     }));
     135            for (let key in payload.data) {
     136                if (key === "domNodes" || key === "domAttributes" || key === "errors") {
     137                    if (!payload.data[key].length)
     138                        continue;
    145139                }
    146140
    147                 options.data.domNodes = payload.data.domNodes;
    148             }
    149             if (payload.data.domAttributes && payload.data.domAttributes.length)
    150                 options.data.domAttributes = payload.data.domAttributes;
    151             if (payload.data.errors && payload.data.errors.length)
    152                 options.data.errors = payload.data.errors;
     141                if (key === "domNodes") {
     142                    if (window.DOMAgent && (!payload.metadata.url || payload.metadata.url === WI.networkManager.mainFrame.url)) {
     143                        let documentNode = await new Promise((resolve) => WI.domManager.requestDocument(resolve));
     144                        options.resolvedDOMNodes = await Promise.all(payload.data.domNodes.map(async (domNodeString) => {
     145                            let nodeId = 0;
     146                            try {
     147                                nodeId = await WI.domManager.querySelector(documentNode, domNodeString);
     148                            } catch { }
     149                            return WI.domManager.nodeForId(nodeId) || null;
     150                        }));
     151                    }
     152                }
     153
     154                options.data[key] = payload.data[key];
     155            }
    153156        }
    154157
     
    211214
    212215        let data = {};
    213         if (this._data.domNodes && this._data.domNodes.length) {
    214             data.domNodes = this._data.domNodes;
    215             if (this._data.domAttributes && this._data.domAttributes.length)
    216                 data.domAttributes = this._data.domAttributes;
    217         }
    218         if (this._data.errors && this._data.errors.length)
    219             data.errors = this._data.errors;
     216        for (let key in this._data) {
     217            if (key === "domNodes" || key === "domAttributes" || key === "errors") {
     218                if (!this._data[key].length)
     219                    continue;
     220            }
     221
     222            data[key] = this._data[key];
     223        }
    220224        if (!isEmptyObject(data))
    221225            json.data = data;
  • trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestGroup.js

    r242808 r245914  
    170170        return super.clearResult({
    171171            ...options,
    172             suppressResultClearedEvent: !cleared,
     172            suppressResultChangedEvent: !cleared,
    173173        });
    174174    }
     
    211211            description: this.description,
    212212        });
     213
     214        this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged);
    213215    }
    214216
  • trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.css

    r245497 r245914  
    128128}
    129129
     130.content-view.audit-test-case > section table > tr > td + td {
     131    width: 100%;
     132}
     133
    130134.content-view.audit-test-case > section .CodeMirror {
    131135    width: 100%;
  • trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.js

    r245497 r245914  
    3333
    3434        this.element.classList.add("audit-test-case");
     35
     36        this._resultDataGeneralContainer = null;
     37        this._resultDataDOMNodesContainer = null;
     38        this._resultDataErrorsContainer = null;
    3539    }
    3640
     
    134138        let resultData = result.data;
    135139
    136         if (resultData.domNodes && resultData.domNodes.length) {
    137             let domNodesContainer = this.contentView.element.appendChild(document.createElement("div"));
    138             domNodesContainer.classList.add("dom-nodes");
    139 
    140             let domNodeText = domNodesContainer.appendChild(document.createElement("h1"));
     140        if (!this._resultDataGeneralContainer) {
     141            let nonSpecialData = Object.filter(resultData, (key) => key !== "domNodes" && key !== "errors");
     142            if (!isEmptyObject(nonSpecialData)) {
     143                this._resultDataGeneralContainer = document.createElement("div");
     144
     145                let expression = "(" + JSON.stringify(nonSpecialData) + ")";
     146                const options = {
     147                    objectGroup: WI.AuditTestBase.ObjectGroup,
     148                    doNotPauseOnExceptionsAndMuteConsole: true,
     149                };
     150                WI.runtimeManager.evaluateInInspectedWindow(expression, options, (nonSpecialDataRemoteObject, wasThrown) => {
     151                    console.assert(!wasThrown);
     152                    if (!nonSpecialDataRemoteObject)
     153                        return;
     154
     155                    if (!this.representedObject.result || this.representedObject.result.data !== resultData)
     156                        return;
     157
     158                    const propertyPath = null;
     159                    const forceExpanding = true;
     160                    let element = WI.FormattedValue.createObjectTreeOrFormattedValueForRemoteObject(nonSpecialDataRemoteObject, propertyPath, forceExpanding);
     161
     162                    let objectTree = element.__objectTree;
     163                    if (objectTree) {
     164                        objectTree.showOnlyProperties();
     165                        objectTree.expand();
     166                    }
     167
     168                    this._resultDataGeneralContainer.appendChild(element);
     169
     170                    this.hidePlaceholder();
     171                });
     172            }
     173        }
     174
     175        if (this._resultDataGeneralContainer)
     176            this.contentView.element.appendChild(this._resultDataGeneralContainer);
     177
     178        if (!this._resultDataDOMNodesContainer && resultData.domNodes && resultData.domNodes.length) {
     179            this._resultDataDOMNodesContainer = document.createElement("div");
     180            this._resultDataDOMNodesContainer.classList.add("dom-nodes");
     181
     182            let domNodeText = this._resultDataDOMNodesContainer.appendChild(document.createElement("h1"));
    141183            domNodeText.textContent = WI.UIString("DOM Nodes:");
    142184
    143             let tableContainer = domNodesContainer.appendChild(document.createElement("table"));
     185            let tableContainer = this._resultDataDOMNodesContainer.appendChild(document.createElement("table"));
    144186
    145187            resultData.domNodes.forEach((domNode, index) => {
     
    205247        }
    206248
    207         if (resultData.errors && resultData.errors.length) {
    208             let errorContainer = this.contentView.element.appendChild(document.createElement("div"));
    209             errorContainer.classList.add("errors");
    210 
    211             let errorText = errorContainer.appendChild(document.createElement("h1"));
     249        if (this._resultDataDOMNodesContainer)
     250            this.contentView.element.appendChild(this._resultDataDOMNodesContainer);
     251
     252        if (!this._resultDataErrorsContainer && resultData.errors && resultData.errors.length) {
     253            this._resultDataErrorsContainer = document.createElement("div");
     254            this._resultDataErrorsContainer.classList.add("errors");
     255
     256            let errorText = this._resultDataErrorsContainer.appendChild(document.createElement("h1"));
    212257            errorText.textContent = WI.UIString("Errors:");
    213258
    214             let tableContainer = errorContainer.appendChild(document.createElement("table"));
     259            let tableContainer = this._resultDataErrorsContainer.appendChild(document.createElement("table"));
    215260
    216261            resultData.errors.forEach((error, index) => {
     
    228273        }
    229274
     275        if (this._resultDataErrorsContainer)
     276            this.contentView.element.appendChild(this._resultDataErrorsContainer);
     277
    230278        if (!this.contentView.element.children.length)
    231279            this.showNoResultDataPlaceholder();
     280    }
     281
     282    handleResultChanged(event)
     283    {
     284        super.handleResultChanged(event);
     285
     286        this._resultDataGeneralContainer = null;
     287        this._resultDataDOMNodesContainer = null;
     288        this._resultDataErrorsContainer = null;
    232289    }
    233290
  • trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.js

    r244157 r245914  
    103103            this.representedObject.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestChanged, this);
    104104            this.representedObject.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestChanged, this);
    105             this.representedObject.addEventListener(WI.AuditTestBase.Event.ResultCleared, this._handleTestChanged, this);
     105            this.representedObject.addEventListener(WI.AuditTestBase.Event.ResultChanged, this.handleResultChanged, this);
    106106            this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestChanged, this);
    107107            this.representedObject.addEventListener(WI.AuditTestBase.Event.Stopping, this._handleTestChanged, this);
     
    115115
    116116        super.hidden();
     117    }
     118
     119    handleResultChanged(event)
     120    {
     121        // Overridden by sub-classes.
     122
     123        this.needsLayout();
    117124    }
    118125
  • trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.js

    r240469 r245914  
    6363        if (this.representedObject instanceof WI.AuditTestBase) {
    6464            this.representedObject.addEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
    65             this.representedObject.addEventListener(WI.AuditTestBase.Event.ResultCleared, this._handleTestResultCleared, this);
     65            this.representedObject.addEventListener(WI.AuditTestBase.Event.ResultChanged, this._handleTestResultChanged, this);
    6666
    6767            if (this.representedObject instanceof WI.AuditTestCase)
     
    282282    }
    283283
    284     _handleTestResultCleared(event)
     284    _handleTestResultChanged(event)
    285285    {
    286286        this._updateStatus();
Note: See TracChangeset for help on using the changeset viewer.