Andrei LED
Andrei LED

Reputation: 2699

Mozilla Web Extensions: Insert UI component into page

I am writing a Web Extension for Firefox that needs to insert a substantial amount of additional functionality inside pages retrieved using certain URLs.

I was able to quickly create a content script that is called whenever a certain page is opened thanks to the tutorial at Mozilla's web site, but now I'm stuck on actually inserting html fragment into the page.

I've been at it for hours but to no avail. Here's what I've considered and tried:

  1. iframe didn't work as apparently some security policy doesn't allow using iframes pointing to local resources and the last comment here even tells that I'm supposed to use panel instead of iframe
  2. Using Panel doesn't work for me for two reasons:
    • I couldn't find a way to open a Panel using my own custom code (the sample by the link above fails with ReferenceError: require is not defined)
    • I'm guessing that I can open a panel in a Web Extension only by using a bowserAction but that would put the button on the toolbar while I need it in the page itself
    • According to documentation I can have only one Panel instance open for the whole browser and it would automatically close upon interacting with any other browser element
  3. Lastly I thought about just loading html from a resource file packed into the extension and feeding it into the page using innerHTML but I couldn't find any API to load text from a resource
  4. Just using DOM API doesn't work for me since it would take forever to code creation of all the elements

Upvotes: 1

Views: 778

Answers (1)

Andrei LED
Andrei LED

Reputation: 2699

I can't believe I didn't notice it for so long, but I finally got it all working as I need. While doing that I even came up with an alternative approach, so here goes.

But first here's the main reason why I dismissed all other possible approaches besides using iframe:

  • I needed the UI elements added by extension to use their own UI styles and wanted to take advantage of modern frameworks (e.g. jQuery and Bootstrap) and I didn't want to run into problems of conflicting CSS and JavsScript later. And I actually noticed early on that CSS in the page that I'm embedding into do override Bootstrap styles.
  • Preferably I also didn't want to affect century old markup of the page that I'm embedding into.

Option A - IFRAME with external source file

In the end it turned out that the only thing I was missing is the web_accessible_resources setting in the manifest.json. Once I added the html file used as source for the iframe into that list, it all just started working.

// manifest.json:
{
    "manifest_version": 2,
    ...
    "web_accessible_resources": [
        "data/my.html"
    ],
    "content_scripts": [
        {
            "matches": ["*://*"],
            "js": ["js/my.js"]
        }
    ]
}

// js/my.js
var addonFrame = document.createElement ("IFRAME");
addonFrame.style = "position: absolute; bottom: 0; right: 0; width: 150px; height: 38px;";
addonFrame.frameBorder = "0";
addonFrame.src = chrome.extension.getURL ("data/my.html");
document.body.appendChild (addonFrame);

Option B - IFRAME with inline HTML in JS

Before I finally got the first approach working, my experimentation led me to another working approach - inserting HTML into the iframe directly in the content script.

// js/my.js
var addonFrame = document.createElement ("IFRAME");
addonFrame.style = "position: absolute; bottom: 0; right: 0; width: 150px; height: 38px;";
addonFrame.frameBorder = "0";

var addonHtml = "<!DOCTYPE html>\n\
<html>\n\
    <head>\n\
        <meta charset='UTF-8'>\n\
        <title>Title of the document</title>\n\
    </head>\n\
    <body>\n\
        ...\n\
    </body>\n\
</html>";

addonFrame.src = 'data:text/html;charset=utf-8,' + encodeURI (addonHtml);
document.body.appendChild (addonFrame);

Even though I ended up using option A in the end, let me outline some pros and cons:

  1. Option A is obviously more canon: view (html) is clearly separated from behavior (js) and all files have content appropriate for their type (except for small exception of building iframe element in JS). So it should be easier to support going forward.
  2. Option A doesn't allow to use inline scripts in the frame (https://developer.chrome.com/extensions/contentSecurityPolicy). This makes prototyping harder but ultimately should be a plus.
  3. For some reason that is still unclear to me, I cannot use # in the inserted html in option B.
  4. Option B makes doing ajax calls from the add-on frame to the original server easier since the frame source is considered to be from the same domain as the original web page. In option A, I had to use Window.postMessage in the frame in order to ask my content script inserted into the original page to make an ajax request and give me back the response (the second part was especially hard since there's nothing like jQuery or Prototype available there).

Upvotes: 1

Related Questions