wxt-dev / wxt

⚡ Next-gen Web Extension Framework
https://wxt.dev
MIT License
3.64k stars 144 forks source link

Add XPath support for anchor property #809

Closed sir-kokabi closed 1 month ago

sir-kokabi commented 1 month ago

Feature Request

I propose adding support for XPath selectors in the anchor.

Currently, the anchor property supports CSS selector, element, or function returning these. Adding XPath support would provide more flexibility in targeting elements, especially in complex DOM structures where XPath might be more suitable or convenient.

Proposed type changes for the anchor property:

N/A

Is your feature request related to a bug?

N/A

What are the alternatives?

  1. Continue using CSS selectors exclusively, potentially with more complex selectors to achieve similar results.
  2. Implement a custom function that uses document.evaluate() to find elements using XPath, and return the result as the anchor.
function getElementByXPath = (xpath, parent = document) => {
  const result = document.evaluate(xpath, parent, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
  return result.singleNodeValue;
};

However, built-in XPath support would provide a more straightforward and maintainable solution for users who prefer or require XPath selectors. This also helps migrate a project that uses a lot of XPath selectors to WXT more easily.

Additional context

This feature can be a competitive advantage over Plasmo, which selects anchors based on CSS selectors. XPath, especially useful when the DOM structure is dynamically generated or faced with nested and complex structures, provides more powerful selection capabilities compared to CSS selectors in certain cases such as selecting elements based on textual content or relative positions.

Let me add this feature to the codebase.

aklinker1 commented 1 month ago

Could you share an example of using XPath vs CSS so we can see it's benefits? I'm not familiar with XPath tbh.

So you're suggesting to check if the anchor starts with "//" an then use document.evaluate (as shown in your example) instead of document.querySelector?

sir-kokabi commented 1 month ago

For example: //div[@data-testid='sidebarColumn']//div[@aria-label='Timeline: Trending now']/ancestor::*[3] //div[@data-testid='User-Name']//*[starts-with(text(),'@')]/ancestor::*[3]

You can find more examples in my project Twifiner. I developed it using Plasmo. XPath has significantly shortened long code snippets and made it possible to achieve many tasks in just a few lines, tasks that were difficult with CSS selectors and previously required multiple lines of code.

Yes, a relative XPath begins with // and an absolute XPath begins with /.

Here is the suggested implementation:

function getAnchor(options: ContentScriptAnchoredOptions): Element | undefined {
  if (options.anchor == null) return document.body;

  let resolved =
    typeof options.anchor === 'function' ? options.anchor() : options.anchor;

  if (typeof resolved === 'string') {
    if (resolved.startsWith('//') || resolved.startsWith('/')) {
      const result = document.evaluate(resolved,document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
      return (result.singleNodeValue as Element) ?? undefined;
    } else {      
      return document.querySelector<Element>(resolved) ?? undefined;
    }
  }

  return resolved ?? undefined;
}
aklinker1 commented 1 month ago

Alright, sounds good. I'm on board! You said you're willing to open a PR? Thanks!

sir-kokabi commented 1 month ago

Sure. I'll do it. 👍

aklinker1 commented 1 month ago

Released in v0.18.13