Skip to content

Conversation

@dbjorge
Copy link
Contributor

@dbjorge dbjorge commented Jul 17, 2023

This PR implements hooks for customizing how DqElements are serialized.

Opening this as a draft for @WilcoFiers to pick up since I wasn't able to get it finished before heading off. The implementation is complete and it's passing integration tests (both old and new), but it still needs unit test updates (both to test the changes and also to fix up existing tests that relied on result nodes sometimes being DqElements).

After this PR, the only usage of DqElement within axe-core itself is always of form new DqElement(node, options).toJSON(), ie, serialized immediately after construction. I think this suggests that it'd be a better design to drop the class entirely and just have a serializeVirtualNode function instead that isn't unnecessarily stateful, but that's likely too breaking. We might consider marking DqElement as deprecated, though, to try to mitigate folks getting confused and thinking that they should be using it.

Closes: #3471


var iframe = document.createElement('iframe');
iframe.src = '/test/mock/frames/context.html';
iframe.src = '/test/mock/frames/noHtml-config.html';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original test was configureing in the top frame but not the child frame. This isn't supported and doesn't provide the protection noHtml is intended to (the thing that the test after this one is verifying). However, previously, the original test worked despite testing an unsupported configuration because there was an extra round trip through DqElement in the top frame involved that no longer exists.

I think it's preferable to break the old pattern and demand that configure must be called consistently in each frame, so I changed the test rather than forcing a top-frame-only noHtml configure to suppress html properties from child frame results, but if that feels too concerning for backcompat reasons you could address it by adding an audit.noHtml test into the process-aggregate.js code that's filling in html from node.source.

@hamax
Copy link
Contributor

hamax commented Jul 18, 2023

Should the default serializer take into account options like options.selectors? I wonder if this could have bad performance implications. Otherwise I like that we can add custom element data to results with this. One problem I see is that custom properties wouldn't be copied over in the reporter processAggregate, so if we use this we would need to implement a custom reporter (probably having to re-implement everything in processAggregate).

@WilcoFiers WilcoFiers changed the title feat: add hook for DqElement serializer refactor: make element spec processing more cosistent Jul 25, 2023
@WilcoFiers WilcoFiers marked this pull request as ready for review July 25, 2023 15:04
@WilcoFiers WilcoFiers requested a review from a team as a code owner July 25, 2023 15:04
@WilcoFiers WilcoFiers force-pushed the dbjorge/node-serializer branch from 811d4aa to 9c48f1c Compare July 26, 2023 10:11
Comment on lines +194 to +195
assert.isNull(passes[0].nodes[0].any[0].relatedNodes[0].html);
assert.isNull(violations[0].nodes[0].any[0].relatedNodes[0].html);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prior to this, node.html would be null, but relatedNode.html was "Undefined". That was a bug.

Copy link
Contributor

@straker straker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably need to add the setRunOptions to run virtual rule as well.

serializer = newSerializer;
}

Object.assign(nodeSerializer, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding functions onto another function hides those functions from the user. They won't get code completion nor typescript support for this. Is there a reason you didn't want to have nodeSerializer be an object with a setSerializer function?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why wouldn't you be able to type / get intellisense on this? I think this would do it:

  type NodeToSpec = <T extends SerialDqElement = SerialDqElement>
    (node: VirtualNode | Element) => T;
  type MergeSpecs = <T extends SerialDqElement = SerialDqElement>
    (parentSpec: SerialDqElement, childSpec: SerialDqElement) => T;

  interface Utils {
    nodeSerializer: {
      ({ isSpec, mergeSpecs }: { isSpec?: NodeToSpec, mergeSpecs?: MergeSpecs }): void,
      isSpec: NodeToSpec,
      mergeSpecs: NodeToSpec
    }

But sure, this could also just be an object with a "set" function if you want. I like this approach better personally, but it's not a big deal.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, I'm replacing this with a .update() method.

@WilcoFiers
Copy link
Contributor

WilcoFiers commented Aug 8, 2023

We probably need to add the setRunOptions to run virtual rule as well.

This isn't useful, as the only time DqElement will be part of runVirtualRule is if that vNode includes an actualNode. But then if you do that, this throws becauseaxe._selectorData isn't set up correctly. Adding this won't do anything, and there's no way to test it either because it throws before these options are applied.

const fixture = document.querySelector('#fixture')
fixture.innerHTML = '<i role="invalid-role"></i>'
const vNode = axe.setup(fixture.firstElementChild);
const result = axe.runVirtualRule('aria-roles', vNode);
// TypeError: Cannot read properties of undefined (reading 'role="invalid-role"')

If there's ever a use case for it, solving for it belongs in a different PR.

@WilcoFiers WilcoFiers force-pushed the dbjorge/node-serializer branch from cebf827 to d1b07e1 Compare August 11, 2023 10:11
@dbjorge
Copy link
Contributor Author

dbjorge commented Aug 11, 2023

@WilcoFiers I just have the two most recent unresolved comments remaining (it's technically my PR so I can't "request changes")

@WilcoFiers WilcoFiers force-pushed the dbjorge/node-serializer branch from e546e14 to c59ad63 Compare August 15, 2023 16:05
@dbjorge
Copy link
Contributor Author

dbjorge commented Aug 18, 2023

LGTM :shipit: (still technically my PR, so @straker will need to approve)

* suitable for JSON.stringify to consume. Output must include all properties
* that DqElement.toJSON() would have. Will always be invoked from the
* input element's original page context.
* @property {Function} mergeSpecs (Optional) Merges two specs (produced by toSpec) which
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also include a note about needing to pass through all properties that DqElement.mergeSpec would pass through (ancestry, selector, etc.) just like the note for toSpec?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's crucial, but if you have a suggestion I'll put it in 😁

@WilcoFiers WilcoFiers merged commit 381b2c3 into develop Aug 21, 2023
@WilcoFiers WilcoFiers deleted the dbjorge/node-serializer branch August 21, 2023 16:18
hamax added a commit to hamax/axe-core that referenced this pull request Aug 1, 2024
This API was added in pr dequelabs#4093, but TS definitions were never added.
hamax added a commit to hamax/axe-core that referenced this pull request Aug 14, 2024
This API was added in pr dequelabs#4093, but TS definitions were never added.
dbjorge pushed a commit that referenced this pull request Sep 24, 2024
This API was added in pr #4093, but TS definitions were never added.

For simplicity I'm using SerialDqElement in the API. We could introduce
a generic for the custom serialized type (T extends SerialDqElement),
but it's hard to consistently use it everywhere (AxeReporter,
NodeSerializer.dqElmToSpec).

I also fixed DqElement.mergeSpecs which is needed to implement a custom
node serializer: it exists on the constructor and not on the individual
instances
straker added a commit that referenced this pull request Sep 24, 2025
The function is no longer used in the code base and was replaced in
#4093

Closes: #4129
straker added a commit that referenced this pull request Oct 9, 2025
##
[4.11.0](v4.10.3...v4.11.0)
(2025-10-07)

### Features

- add RGAA tags to rules
([#4862](#4862))
([53a925a](53a925a))
- **aria-prohibited-attr:** add support for fallback roles
([#4325](#4325))
([62a19a9](62a19a9))
- **axe.d.ts:** add nodeSerializer typings
([#4551](#4551))
([a2f3a48](a2f3a48)),
closes [#4093](#4093)
- **DqElement:** deprecate fromFrame function
([#4881](#4881))
([374c376](374c376)),
closes [#4093](#4093)
- **DqElement:** Truncate large `html` strings when the element has a
large outerHTML string
([#4796](#4796))
([404a4fb](404a4fb)),
closes [#4544](#4544)
- **get-xpath:** return proper relative selector for id
([#4846](#4846))
([1035f9e](1035f9e)),
closes [#4845](#4845)
- **i18n:** Add Portugal Portuguese translation
([#4725](#4725))
([5b6a65a](5b6a65a))
- incomplete with node on which an error occurred
([#4863](#4863))
([32ed8da](32ed8da))
- **locale:** Added ru locale
([#4565](#4565))
([067b01d](067b01d))
- **tap:** some best practice rules map to RGAA
([#4895](#4895))
([bc33f4c](bc33f4c))
- **td-headers-attr:** report headers attribute referencing other <td>
elements as unsupported
([#4589](#4589))
([ec7c6c8](ec7c6c8)),
closes [#3987](#3987)

### Bug Fixes

- **aria-allowed-role:** add form to allowed roles of form element
([#4588](#4588))
([8aa47ac](8aa47ac)),
closes
[/github.com/dequelabs/axe-core/blob/develop/lib/standards/html-elms.js#L264](https://github.com/dequelabs//github.com/dequelabs/axe-core/blob/develop/lib/standards/html-elms.js/issues/L264)
- **aria-allowed-role:** Add math to allowed roles for img element
([#4658](#4658))
([95b6c18](95b6c18)),
closes [#4657](#4657)
- **autocomplete-valid :** Ignore readonly autocomplete field
([#4721](#4721))
([491f4ec](491f4ec)),
closes [#4708](#4708)
- **autocomplete-valid:** treat values "xon" and "xoff" as
non-WCAG-violations
([#4878](#4878))
([52bc611](52bc611)),
closes [#4877](#4877)
- **axe.d.ts:** add typings for preload options object
([#4543](#4543))
([cfd2974](cfd2974))
- **button-name,input-button-name,input-img-alt:** allow label to give
accessible name
([#4607](#4607))
([a9710d7](a9710d7)),
closes [#4472](#4472)
[#3696](#3696)
[#3696](#3696)
- **captions:** fix grammar in captions check incomplete message
([#4661](#4661))
([11de515](11de515))
- **color-contrast:** do not run on elements with font-size: 0
([#4822](#4822))
([d77c885](d77c885)),
closes [#4820](#4820)
- consistently parse tabindex, following HTML 5 spec
([#4637](#4637))
([645a850](645a850)),
closes [#4632](#4632)
- **core:** measure perf for async checks
([#4609](#4609))
([7e9bacf](7e9bacf))
- fix grammar when using "alternative text" in a sentence
([#4811](#4811))
([237a586](237a586)),
closes [#4394](#4394)
- **get-ancestry:** add nth-child selector for multiple siblings of
shadow root ([#4606](#4606))
([1cdd6c3](1cdd6c3)),
closes [#4563](#4563)
- **get-ancestry:** don't error when there is no parent
([#4617](#4617))
([a005703](a005703))
- **locale:** fix typos in japanese (ja) locale
([#4856](#4856))
([3462cc5](3462cc5))
- **locale:** fixed typos in german (DE) locale
([#4631](#4631))
([b7736de](b7736de))
- **locale:** proofread and updated de.json
([#4643](#4643))
([8060ada](8060ada))
- **meta-viewport:** lower impact to moderate
([#4887](#4887))
([2f32aa5](2f32aa5)),
closes [#4714](#4714)
- **no-autoplay-audio:** don't timeout for preload=none media elements
([#4684](#4684))
([cdc871e](cdc871e))
- **performanceTimer:** throwing in axe catch clause
([#4852](#4852))
([a4ade04](a4ade04)),
closes
[/github.com/dequelabs/axe-core/blob/e7dae4ec48cbfef74de9f833fdcfb178c1002985/lib/core/base/rule.js#L297-L300](https://github.com/dequelabs//github.com/dequelabs/axe-core/blob/e7dae4ec48cbfef74de9f833fdcfb178c1002985/lib/core/base/rule.js/issues/L297-L300)
- **performanceTimer:** work in frames
([#4834](#4834))
([d7dfebc](d7dfebc))
- **rules:** Change "alternate text" to "alternative text"
([#4582](#4582))
([b03ada3](b03ada3))
- **target-size:** do not treat focusable tabpanels as targets
([#4702](#4702))
([60d11f2](60d11f2)),
closes [#4421](#4421)
[#4701](#4701)
- **type:** correct RuleError type
([#4893](#4893))
([d1aa8e2](d1aa8e2))
- **types:** correct raw types
([#4903](#4903))
([3eade11](3eade11))

This PR was opened by a robot 🤖 🎉
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create utils.nodeSerializer method

5 participants