Detect when a Ctrl+F result is inside an iframe

Hello (again),

I tried to detect when a Ctrl+F result is inside an iframe. Currently, Firefox’s selection state is the same whether the result is inside an iframe or if the input field has been cleared! I need this for my extension, FineFind.

Possible solutions I’ve tried:

Reading the state of document.activeElement
No matter if the result is inside an iframe or not, activeElement is always the ‘body’ of the main page.

Adding focus and blur event listeners for every iframe and toggling the state of a boolean variable isInsideFrame
When the user clicks inside an iframe, the event triggers, but not when Ctrl+F selects text inside the iframe.

Check the Selection.type property between a result inside an iframe or a cleared search field
In both cases, the type is Caret.

Check the position of the result between a result inside an iframe or a cleared search field
In both cases, the position corresponds to the last element from Selection.type of Range (or at least it seems so).

:upside_down_face:

The iframe is a separate website, so whatever event handlers you have in the parent page, needs to be registered also in the iframe (and maybe forwarded to parent for further processing).

For example, if I register this code in parent AND iframe:

document.addEventListener("selectionchange", console.log);

It will log something when I press F3 key and the result is in the “iframe” (but you need to be watching the “iframe” console:
image

Hmm, but I can’t execute code inside iframes from different websites, right? Cross-site scripting issues, I guess.

You can if you have all_urls permission, and looking at your code, it looks like you have, since your content script has:

      "matches": ["*://*/*"],

So all you need to do is add all_frames option: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/content_scripts#all_frames
(and then detect in content script whether you are in the parent or in iframe)

Or register new content script with all_frames enabled that will register and forward event to the parent (I think you’ll have to send them first to background script which then needs to send them back to the sender.tab.id).

Its not working :frowning: I’ve updated my manifest like so:

“content_scripts”: [
{
“matches”: [":///*"],
“all_frames”: true,
“js”: [“js/utils.js”, “js/highlighter.js”, “js/notifier.js”, “js/content-script.js”],
“css”: [“css/highlighter.css”, “css/notifier.css”]
}
],

Well, it’s not gonna be that simple, iframes are tricky. But I think it should be possible.
Try something simple first, some proof of concept.

For example, now that you have content script running in all frames (including iframes), use this code to detect when the code runs in iframe:

function isInIframe() {
  try {
    return window.self !== window.top
  }
  catch (e) {
    return true
  }
}

If it is in inframe, use console log or send message to your content script when selectionchange changes. Does this work?

1 Like

This looks so cursed:

I solved it… and now I have new questions. The problem was that, for some reason, the all_frames: true option ignores iframes with the srcdoc attribute. Normal iframes with the src attribute seem to work.

So, how can I run my code inside an srcdoc iframe as well?

Ahh, the joys of iframes and script injection. The reason that srcdoc iframes don’t match is because they run on a special origin. You can see this in action if you visit example.com and run the following code in DevTools

iframe = document.createElement('iframe');
iframe.srcdoc = "Hello, world!"
document.body.append(iframe);
console.log(iframe.contentDocument.location.toString()); // "about:srcdoc"
console.log(iframe.contentDocument.location.origin);     // "null"

There are a few different special cases on the web that have similar behavior. In order to target them, you’ll need to add match_origin_as_fallback to your content script declaration. It basically does what it says: when the frame doesn’t have an origin that the content script’s matches patterns can match against, the browser will fall back to the parent document’s origin for matching purposes.

2 Likes

Ahh! Thanks :smile:

Is there any reason, why match_origin_as_fallback is disabled by default @dotproto ? Performance? Security?

Mmmm, I’m not sure. I would guess that subframe injection isn’t something all extensions need, so the default implementation favors limiting access for performance reasons. Same goes for subframes with easily target-able origins vs. null origins. That said, that’s supposition on my part.