[csswg-drafts] [css-highlight-api] Feedback: the global registry string-keyed makes usage very hard (#11095)

nicolo-ribaudo has just created a new issue for https://github.com/w3c/csswg-drafts:

== [css-highlight-api] Feedback: the global registry string-keyed makes usage very hard ==
Example use case: I have a contenteditable `div` that marks as `::highlight(has-x)` all the words that contain `x`.

The simplest way of doing it is:
```js
function highlightWords(div) {
  const highlight = new Highlight();

  for (const child of div.childNodes) {
    if (child.nodeType !== Node.TEXT_NODE) continue;

    const regexp = /\w*x\w*/g;
    let match;
    while (match = regexp.exec(child.textContent)) {
      const range = document.createRange();
      range.setStart(node, match.index);
      range.setEnd(node, match[0].length + match.index);
      highlight.add(range);
    }
  }

  CSS.highlights.set("has-x", highlight);
}

highlightWords(myElement);
myElement.addEventListener("input", () => highlightWords(myElement));
```

This works perfectly as long as I only call this function once on a single element.

One day I decide that actually, I need this behavior on two different elements on my page, so I try doing this:
```js
highlightWords(myElement1);
myElement1.addEventListener("input", () => highlightWords(myElement1));

highlightWords(myElement2);
myElement1.addEventListener("input", () => highlightWords(myElement2));
```

Except that this doesn't work at all: the highlighting logic over `myElement1` and `myElement2` fight over control of the `has-x` entry in the global `CSS.highlights` registry, and delete each other's highlights as they get updated.

To make it work, I need to instead update the logic in `highlightWords` to _never_ overwrite an existing entry in the `CSS.highlights` map. Instead, it must check through all the ranges in the entry and only remove/add its own, and then _if the map ends up being empty_ it can remove it.

The global registry introduces a synchronization point between different parts of the page that is easier to get wrong than it's to get right, and if you are a library author (for example, publishing the logic above in a custom element) you won't notice unless you explicitly test multiple instances on the same page.

---

There are multiple ways that the API can be changed to make it difficult to do the wrong thing.

**Do not allow reading `Highlight` objects from the global registry, and do not allow removing `Highlight` objects that you do not control**:
- `.get()` is removed
- to check whether an highlight has been registered, you have to do `CSS.highlights.has(name, highlight)` instead of `CSS.highlights.has(name)`
- `.set(name, highlight)` throws if there is already an entry called `name`
- to delete an highlight, you must use `.delete(name, highlight)` instead of `.delete(name)`

This keeps the synchronization complexity, but forces developers to think about it.

**Allow registering multiple `Highlights` with the same name**:

Similar to above, except that instead of it makes conflicts work well together instead of overwriting each other

- `.get()` is removed, or it returns an array of `Highlight` objecs
- to check whether an highlight has been registered, you have to do `CSS.highlights.has(name, highlight)` instead of `CSS.highlights.has(name)`
- `.set(name, highlight)` adds a new highlight named `name`, without removing any existing highlight with the same name.
- to delete an highlight, you must use `.delete(name, highlight)` instead of `.delete(name)`

**Allow using local registries**

Allow the function above to create its own `highlights` registry by doing something like `const highlights = new HighlightsRegistry(document)`. Different registries can declare the same name without conflicting with each other. The example above would become
```js
const registry1 = new HighlightsRegistry(document);
highlightWords(registry1, myElement1);
myElement1.addEventListener("input", () => highlightWords(registry1, myElement1));

const registry2 = new HighlightsRegistry(document);
highlightWords(registry2, myElement2);
myElement1.addEventListener("input", () => highlightWords(registry2, myElement2));
```

Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/11095 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Saturday, 26 October 2024 16:38:27 UTC