Monthly Archives: April 2011

Inserting JavaScript/CSS in a target page from a Firefox Extension

When you’re working with Firefox extensions you often want to act on the target page, accessing elements or even changing HTML, but JavaScript libraries and jQuery plugins assume that they are always working on the current scope’s global objects.

So every time you find yourself wanting to act on variables like window, document and jQuery you run into a problem.  You could insert the module into the target window using a script tag, but that may conflict with the target window’s JS.

A better solution is to load another instance of these libraries in your own scope tied to the target window’s global variables. One approach is to eval the window inside a context that has remapped the window and document element.

The following code demonstrates this given a passed target window.


createTargetScripts: function(targetWindow) {
   // Create the scripts we will use on the target window, we create them in a closure so they will
   // have the target window and document (but our privilege, and the target objects they use will actually be wrappers).

   (function() {
      var window = targetWindow;
      var document = targetWindow.document;
      eval(evalStringForURL('libs/jquery.js'));
      var jQuery = targetWindow.jQuery;          // Add jQuery to local scope for jQuery plugins
      eval(evalStringForURL('libs/jquery.tmpl-master0405.js'));
      eval(evalStringForURL('libs/jquery.qtip.js'));

   })();

   // Even though the jQuery is on the wrapper, that wrapper may be used by other extensions
   targetWindow.targetJQ = targetWindow.jQuery.noConflict(true);

   } ,
   readURLSync: function(url) {
      var data = myExtensionJQ.ajax({
         url: url,
         async: false,
         dataType: "text",
         mimeType: 'text/plain; charset=x-user-defined'
         }).responseText;
      return data;
   },

   // Add sourceURL so debuggers will know what source file to use
   evalStringForURL: function(url) {
      var evalStr = this.readURLSync(url);
      var sourceURLStr = "//@ sourceURL=" + url;
      return evalStr + sourceURLStr;
   },

For CSS you can do even better. Instead of inserting the CSS into the target page you can load it as a “user sheet,” making it available in all pages. If you want to override existing styles in the target page you just use a “!” in your style. Here is the code:


var sss = Components.classes["@mozilla.org/content/style-sheet-service;1"]
   .getService(Components.interfaces.nsIStyleSheetService);
var ios = Components.classes["@mozilla.org/network/io-service;1"]
   .getService(Components.interfaces.nsIIOService);
var uri = ios.newURI(url, null, null);
   sss.loadAndRegisterSheet(uri, sss.USER_SHEET);

FYI, Another Firefox-specific approach for loading the JS libraries with a different context is to use loadSubScript, but a bug in Firefox 4 broke this. I’ve documented the bug at https://bugzilla.mozilla.org/show_bug.cgi?id=653926. Unfortunately, you can’t actually view the bug report – it was restricted because it exposes a potential Firefox security vulnerability.

Update 6/15/11

Apparently the bug I mentioned above exposes a severe security vulnerability since I was awarded a Firefox Security Bug Bounty Award. See [post: Firefox Security Bug Bounty…].

Advertisement
%d bloggers like this: