Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make INP XPath selector match the one in PHP #1

Open
swissspidy opened this issue Jan 14, 2025 · 8 comments
Open

Make INP XPath selector match the one in PHP #1

swissspidy opened this issue Jan 14, 2025 · 8 comments
Labels
bug Something isn't working

Comments

@swissspidy
Copy link
Owner

Right now the XPath for an image block could look like this in in the tag visitor:

/HTML/BODY/DIV/*[3][self::DIV]/*[1][self::FIGURE]/*[1][self::IMG]

And the one created in the JS looks like this:

/HTML[1]/BODY[1]/DIV[1]/MAIN[1]/DIV[1]/DIV[2]/FIGURE[1]/IMG[1]

So when I try to compare the two in the tag visitor (to only add a dot when the matching element is found), there is no match.

@swissspidy swissspidy added the bug Something isn't working label Jan 14, 2025
@westonruter
Copy link
Collaborator

Note that given this XPath:

/HTML[1]/BODY[1]/DIV[1]/MAIN[1]/DIV[1]/DIV[2]/FIGURE[1]/IMG[1]

The DIV[1] here means "of all the DIV tags here, select the first one". It doesn't mean in CSS div:first-child. So this is why the somewhat-convoluted XPath scheme was devised: *[1][self::DIV] as this means the first child which is also a DIV. This is important because consider these two fragments:

<img src="...">
<p>...</p>
<p>...</p>
<p>...</p>

and

<p>...</p>
<p>...</p>
<p>...</p>
<img src="...">

In both examples, //IMG[1] will select the IMG. However, the structure of the page is different. If this XPath was initially captured and the IMG was LCP, then we wouldn't want to continue to match that IMG when it is moved down below various paragraphs. So this is why the node index is given such priority.

Take note that the XPath format is poised to further evolve with WordPress/performance#1797

@swissspidy
Copy link
Owner Author

So basically I need to make the same PHP changes also in JS here and keep them in sync

// TODO: The resulting selector needs to be compatible with the one in PHP.
function createXPathSelector( element ) {
if ( ! element || element.nodeType !== Node.ELEMENT_NODE ) {
throw new Error( 'Invalid element provided.' );
}
let path = '';
let currentElement = element;
while ( currentElement ) {
// The `document` global doesn't have a tagName.
if ( ! currentElement.tagName ) {
break;
}
let elementName = currentElement.tagName;
let index = 1;
let siblings = currentElement.parentNode
? currentElement.parentNode.childNodes
: [];
for ( let i = 0; i < siblings.length; i++ ) {
const sibling = siblings[ i ];
if ( sibling === currentElement ) {
break;
}
if (
sibling.nodeType === Node.ELEMENT_NODE &&
sibling.tagName === elementName
) {
index++;
}
}
path = `/${ elementName }[${ index }]${ path }`;
currentElement = currentElement.parentNode;
}
return path;
}

@westonruter
Copy link
Collaborator

What if we added a createXPath function in Optimization Detective's detect.js script module and export that to the extensions to reuse?

@swissspidy
Copy link
Owner Author

That could make sense

@westonruter
Copy link
Collaborator

Then you wouldn't have to worry about maintaining this since it would be kept in one place, and conceivably other extensions may want to reuse this as well.

Still, my concern with generating XPaths at runtime remains that JS can make changes to the DOM which immediately cause all XPaths to become invalid. Actually, the XPaths at the very beginning were computed client-side but this was eliminated in favor of server-side generation very early: WordPress/performance#892. The issue there was due to the skip link which was dynamically added to the page, but fortunately it is added as a preceding sibling to .wp-site-blocks, so it won't invalidate any XPaths with the move to remove the node index from children of BODY.

@swissspidy
Copy link
Owner Author

The alternative would be to revert the XPath change again and do everything in JS with selector queries.

@westonruter
Copy link
Collaborator

Except not the selector which was provided by web-vitals attribution build since it included .hide in the selector, right?

@swissspidy
Copy link
Owner Author

Yes. Unless web-vitals somehow provides a way to get the selector from before the interaction / adding the .hide class

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants