Is there a way to uniquely identify an iframe that the content script runs in for my Chrome extension?

In my Chrome extension I am injecting the content script into all IFRAMEs inside a page. Here's a part of the manifest.json file:

"content_scripts": [
    {
        "run_at": "document_end",
        "all_frames" : true,
        "match_about_blank": true,
        "matches": ["http://*/*", "https://*/*"],
        "js": ["content.js"]
    }
],

So a single web page having multiple IFRAMEs will end up running that many copies of my injected content.js.

The logic inside content.js collects data from each IFRAME it's injected into, or from the main/top page, and sends it back to the background script (using chrome.runtime.sendMessage.) The background script in turn needs to store the data in the global variable, that is later used in the extension itself.

The issue I'm facing is that the app needs to distinguish between the "data" received from multiple IFRAMEs, since my data collection method can be called repeatedly upon user's interaction with the page, and thus I cannot simply "dump" the data received by the background script into an array. Instead I need to use a dictionary-type data storage.

I can tell if the data is coming from an IFRAME or from the top page by running the following:

//From the `content.js`
var isIframe = window != window.top;

and my thinking was that if I collect page URLs of each IFRAME then I should be able to use it as a unique key to store the data under in my dictionary-type global variable:

//Again from content.js
var strUniqueIFrameURL = document.URL;

Well, that is not going to work, because two or more IFRAMEs can have the same URLs.

So thus my original question -- how to tell IFRAMEs on the page apart? Is there some unique ID or somethign that Chrome assigns to them?

Answers:

Answer

You can identify the relative place of the document in the hierarchy of iframes. Depending on the structure of the page, this can solve your problem.

Your extension is able to access window.parent and its frames. This should work, or at least works for me in a test case:

// Returns the index of the iframe in the parent document,
//  or -1 if we are the topmost document
function iframeIndex(win) {
  win = win || window; // Assume self by default
  if (win.parent != win) {
    for (var i = 0; i < win.parent.frames.length; i++) {
      if (win.parent.frames[i] == win) { return i; }
    }
    throw Error("In a frame, but could not find myself");
  } else {
    return -1;
  }
}

You can modify this to support nesting iframes, but the principle should work.

I was itching to do it myself, so here you go:

// Returns a unique index in iframe hierarchy, or empty string if topmost
function iframeFullIndex(win) {
   win = win || window; // Assume self by default
   if (iframeIndex(win) < 0) {
     return "";
   } else {
     return iframeFullIndex(win.parent) + "." + iframeIndex(win);
   }
}
Answer

Just to expand on @Xan's answer, here's my method of getting an IFRAME's index considering its possible nesting within other IFRAMEs. I'll use the forward-iframe notation, meaning that the parent IFRAME index will be given first, followed by child indexes, etc. Also to prevent a possible confusion with floating-point numbers I'll use the underscore for a separator instead of the dot.

So to answer my original question, once I have IFRAME index within the page, it will uniquely identify it in that page (coupled with the IFRAME's URL.)

Here's the code to get it:

function iframeIndex(wnd)
{
    //RETURN:
    //      = "" for top window
    //      = IFrame zero-based index with nesting, example: "2", or "0_4"
    //      = "?" if error
    return _iframeIndex(wnd || window);     // Assume self by default
}

function _iframeIndex(wnd)
{
    var resInd = "";

    var wndTop = window.top;

    if(wnd == wndTop)
        return resInd;

    var wndPar = wnd.parent;

    if(wndPar != wndTop)
    {
        resInd = _iframeIndex(wndPar) + "_";
    }

    var frmsPar = wndPar.frames;
    for(var i = 0; i < frmsPar.length; i++)
    {
        if(frmsPar[i] == wnd)
            return resInd + i;
        }

    return resInd + "?";
}
Answer

You can generate a pseudo-unique id using a combination of timestamp and a random number every time a content script loads, like this:

var psUid = (new Date()).getTime() + '_' + Math.random();

And then send all your data-related messages to the background with this ID.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.