Skip to content

Fill fields inside shadow roots#397

Open
raxod502 wants to merge 2 commits intobrowserpass:masterfrom
radian-software:rr-shadowroot-support
Open

Fill fields inside shadow roots#397
raxod502 wants to merge 2 commits intobrowserpass:masterfrom
radian-software:rr-shadowroot-support

Conversation

@raxod502
Copy link
Copy Markdown
Contributor

Fairly straightforward. I just introduce a function queryShadowRoots that converts a single parent element into a list that contains the parent plus all shadow roots amongst its descendants. Then queryAllVisible invokes it and iterates through the results, applying querySelectorAll to each individual shadow root (and the original parent).

I tested this and it seemed to work right away, allowing autofill to succeed on one of my sites where the login form is inside a shadow root.

I recommend viewing the diff with whitespace changes disabled.

Closes #73

*/
function queryShadowRoots(parent) {
return [parent, ...parent.querySelectorAll("*")]
.filter((e) => e.shadowRoot)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When parent is document (in the very first call), won't this .filter() line remove it from the result array, resulting in a change of behavior?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Oops, sorry. Revised, and now tested with both a shadow-root and non-shadow-root login page.

@raxod502 raxod502 requested a review from max-baz February 9, 2026 23:58
@erayd
Copy link
Copy Markdown
Collaborator

erayd commented Mar 25, 2026

Interrogating every single element on the page just to find the roots can be quite slow, and is a notable performance issue on pages with a large number of elements. I would suggest instead collating a list of shadow roots as they are created, or marking them in some way that will allow querySelect() to locate them later. This is vastly quicker.

This can be done by intercepting the attachShadow function via a content script in the page context at document_start.

For example:

var _attachShadow;
if (!_attachShadow) {
    _attachShadow = Element.prototype.attachShadow;
    Element.prototype.attachShadow = function (options) {
        this.setAttribute("is-shadow", "");
        return _attachShadow.call(this, options);
    };
}

Which allows quickly getting the roots later using document.querySelectorAll("[is-shadow]"). This approach also works well with recursion, if you want to support nested shadow roots.

@raxod502
Copy link
Copy Markdown
Contributor Author

Hmm, not a bad idea, the thing about that though is we have to start injecting code into every webpage at startup, whereas the current architecture only has us injecting any logic once the extension menu icon is interacted with and a password entry is selected.

I would be somewhat leery of going to a model where the extension has to have access to all website data even when not actively being used, maybe it could be opt in though?

@erayd
Copy link
Copy Markdown
Collaborator

erayd commented Mar 25, 2026

...the thing about that though is we have to start injecting code into every webpage at startup, whereas the current architecture only has us injecting any logic once the extension menu icon is interacted with and a password entry is selected.

This is true. However, the injected code would be extremely lightweight, and is essentially a noop where shadow roots aren't used.

I would be somewhat leery of going to a model where the extension has to have access to all website data even when not actively being used, maybe it could be opt in though?

It's not a move to that model; Browserpass already holds (and uses) the all-sites permission, and therefore already has access to all site data:

"host_permissions": ["http://*/*", "https://*/*"],

@raxod502
Copy link
Copy Markdown
Contributor Author

Browserpass already holds (and uses) the all-sites permission, and therefore already has access to all site data

Mmm, I think the way the host permission is handled is a bit different than the other ones, the manifest doesn't say anything about it being optional, but check out how it renders in the Firefox preferences:

image

So at the minimum we'd have to implement in such a way that we only use the new approach when the permission has been granted.

@erayd
Copy link
Copy Markdown
Collaborator

erayd commented Mar 25, 2026

Mmm, I think the way the host permission is handled is a bit different than the other ones, the manifest doesn't say anything about it being optional, but check out how it renders in the Firefox preferences:

I think this is just Firefox's way of making it revocable by the user. Chrome has a similar thing on the context menu for it. However, we hold the permission by default - it's not something we need to ask for, and unless the user has manually removed it later, we should already hold it on all existing installs.

So at the minimum we'd have to implement in such a way that we only use the new approach when the permission has been granted.

Easy enough. Just have the injected script drop an identifier in the DOM when it runs (e.g. an attribute on the document element), and if that identifier is missing when we go to fill credentials, then gracefully fall back to the slow / expensive check-all-the-elements approach.

@raxod502
Copy link
Copy Markdown
Contributor Author

Fair enough. Will wait to hear back from @max-baz before doing more work though, as I'd like an indication that this will be merged at some point.

@max-baz
Copy link
Copy Markdown
Member

max-baz commented Mar 27, 2026

@erayd designed and developed a big portion of this extension, I trust his judgment and happy to get this merged as soon as you two reach the stage where you both are happy with the implementation 👍

@raxod502
Copy link
Copy Markdown
Contributor Author

Oh, okay! I'll fix this up as soon as I have a chance, then.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Need support for shadowDOM

3 participants