MozillaZine

Using jQuery 1.5 in your Firefox extension : A quick guide

Talk about add-ons and extension development.
Ziink
 
Posts: 49
Joined: July 27th, 2009, 8:37 am

Post Posted February 16th, 2011, 10:25 am

This is a follow up on my earlier posting http://forums.mozillazine.org/viewtopic.php?f=19&t=1460255 titled: How To: Using jQuery 1.3.2 in your addon

Although that worked for me then, it wasn't a very good solution. There were certain features of jQuery that didn't work.

Scenario 1: jQuery to work with the ChromeWindow.

Step 1: Add OnLoad event handler.
You probably already do this.
window.addEventListener("load", function(e) { ziink.onLoad(e); }, false);

Here ziink.onLoad is the function where I deal with the event.
Note: If we load jQuery before this event, it might screw up the toolbars and some buttons might not show.

Step 2: Load jQuery in your onLoad handler using loadSubScript

Code: Select all
   loadjQuery: function(wnd){
      var loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
       .getService(Components.interfaces.mozIJSSubScriptLoader);
      loader.loadSubScript("chrome://clhelper/content/jquery/jquery-1.5.js",wnd);
      var jQuery = wnd.jQuery.noConflict(true);
      loader.loadSubScript("chrome://clhelper/content/jquery/jquery.hoverIntent.js", jQuery);
      return jQuery;
   },


Code: Select all
// Within onLoad handler
ziink.jQuery = loadjQuery(window);
// In my case ziink refers to the single variable I declare in the global scope


Note: See how the jQuery plugin (jquery.hoverIntent.js) is loaded by passing jQuery as the scope.

Step 3: Using jQuery
Within any function (or event handler) add
Code: Select all
var $ = ziink.jQuery;       // Note: ziink is what I use, you should have your own (and hopefully single) global variable.


Now use jQuery as you normally would.



Scenario 2: Manipulating web page DOM
Although we can use the jQuery object from scenario 1, it fails under a number of circumstances. This is because jQuery saves the window and document objects from it's global scope when it's created. These are then used by a number of its methods. What we want to do is create the jQuery object within the scope of the web pages 'window'.

Step 1: Add an event listener for when the web page is loaded.
You are probably doing this already. A good place to add it is in the onLoad handler mentioned above.

Code: Select all
      var appcontent = document.getElementById("appcontent");         // browser
      if(appcontent){
         appcontent.addEventListener("DOMContentLoaded", ziink.onPageLoad, true);  // ziink.onPageLoad is what I use, you should use your own.
      }


Step 2: Load jQuery in the onPageLoad handler and save it as a property of the page's window.

Code: Select all
   loadjQuery: function(wnd){
      var loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
       .getService(Components.interfaces.mozIJSSubScriptLoader);
      loader.loadSubScript("chrome://clhelper/content/jquery/jquery-1.5.js",wnd);
      var jQuery = wnd.jQuery.noConflict(true);
      loader.loadSubScript("chrome://clhelper/content/jquery/jquery.hoverIntent.js", jQuery);
      return jQuery;
   },
This is the same function we used in Scenario 1.


Code: Select all
   onPageLoad: function(event) {
      var doc = event.originalTarget;
      var wnd = doc.defaultView;
      // load jQuery and save it as a property of the window
      wnd.ziink_jQuery = ziink.loadjQuery(wnd);   // Be sure to choose a name likely to be unique

      // To use jQuery here the next line helps
      var $ = jQuery = wnd.ziink_jQuery;

      ziink.processPage(doc);
   },


Step 3: Using jQuery within an event handler
Use code similar to
Code: Select all
      var doc = event.originalTarget.ownerDocument;
      var wnd = doc.defaultView;
      var $ = jQuery = wnd.ziink_jQuery;
      // Now you can use jQuery as you normally would


Caveat: Our jQuery object is within the scope of the web page's 'window'. The web pages javascript might be changing the environment that might cause jQuery not to work properly. One situation where this might happen is when the page's javascript messes around with the built-in types prototype.
----------------------
Craigslist Helper: Image, Ad & Forum Previews, Distance Search and All of CL Search, Notes, Alerts etc.
For Firefox, Chrome & Safari : http://ziink.com

rme451
 
Posts: 2
Joined: April 29th, 2011, 8:46 am

Post Posted April 29th, 2011, 10:35 am

Hi

I'm experimenting with adding jquery and jquery-ui to my extension

Is it possible that FF4 has stopped your technique from working?

It's just as likely I messed-up your instructions, but in your Scenario 2, it doesn't seem possible to add jQuery to the window object

The type of the defaultView object is XrayWrapper so perhaps it's protected?

My cobbled-together code can be found here: http://dl.dropbox.com/u/197051/jquery.txt

Any pointers would be really welcome

Cheers

Ziink
 
Posts: 49
Joined: July 27th, 2009, 8:37 am

Post Posted April 29th, 2011, 4:36 pm

You are right, it doesn't work in FF4 but good news is that it can be done. The problem with FF4 if I remember right is that if you change an existing property of 'window' in FF4, there's some kind of access problem and has to do with the context.

Try commenting out
var jQuery = wnd.jQuery.noConflict(true);
and instead use
wnd.makethisuniquename = wnd.jQuery

wnd.makethisuniquename will then be usable by your extension code but not visible to the JS loaded by the page.
----------------------
Craigslist Helper: Image, Ad & Forum Previews, Distance Search and All of CL Search, Notes, Alerts etc.
For Firefox, Chrome & Safari : http://ziink.com

studgeek

User avatar
 
Posts: 23
Joined: April 9th, 2006, 12:08 pm

Post Posted April 30th, 2011, 9:22 am

FF 4 has broken/changed how it handles wrapper objects passed to loadSubScript. I reported it in https://bugzilla.mozilla.org/show_bug.cgi?id=653926 which has now been marked as "Security-Sensitive" so I have deleted the details from here until it is fixed or unmarked.

The key thing is that it breaks the original scenario 2 suggestion. Instead I have switched to using a scoped eval which works on FF 3.6, FF 4 and really should work in any browser.

In the scope I set window and document so that jQuery and its plugins will use that window and document (which are both wrappers). I also set jQuery so the plugins have access to that as well.

Code: Select all
         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;
         },
Last edited by studgeek on June 7th, 2011, 1:23 pm, edited 1 time in total.

rme451
 
Posts: 2
Joined: April 29th, 2011, 8:46 am

Post Posted May 5th, 2011, 5:00 pm

Just a quick note to say thanks guys!

I got it going with your help

I was at the end of my tether with this

Simple in GM & chrome, but apparently not in FF

Would have liked to use the new addons-sdk, but no help from that direction
http://groups.google.com/group/mozilla-labs-jetpack/browse_thread/thread/c5fd40bae1aea97f

I leave a link for you to offer your comments/corrections/mockery here
http://dl.dropbox.com/u/197051/jquery1.js

I can't believe there isn't a crowd of folks who want to do just this - so please feel free to fix my feeble efforts & post improvements

Thank you again!

studgeek

User avatar
 
Posts: 23
Joined: April 9th, 2006, 12:08 pm

Post Posted May 10th, 2011, 6:36 am

Cool, I'm glad it was helpful. I also spent a lot of time to get this figured out, then FF4 made me do it again :/.

For CSS, instead of injecting into the target page you can also load it as a user sheet, that will make it available in all pages. If you want to override styles in the target page you can use a "!" in your style. Here is the code I have:
Code: Select all
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);

xtomiii
 
Posts: 1
Joined: May 26th, 2011, 12:33 am

Post Posted May 26th, 2011, 12:38 am

Guys, your solution does not work int Firefox 3.6
So there is not general solution for this and I have to create 2 separate plugins?

Thanks

studgeek

User avatar
 
Posts: 23
Joined: April 9th, 2006, 12:08 pm

Post Posted June 1st, 2011, 12:11 pm

The code I posted in my last two posts runs fine in Firefox 3.6.17. That is actually still my main development platform due to some unrelated Firefox 4.0 issues.

If you can be specific how it doesn't work maybe I can help track down what is wrong.

d

MichaelAtUVa
 
Posts: 3
Joined: June 10th, 2011, 7:56 am

Post Posted June 10th, 2011, 8:07 am

Thanks for the great post I had been having a few issues with this very problem.

First, a note to anyone trying to do this so they don't make the same mistake.
Code: Select all
//DO
window = aEvent.originalTarget.defaultView;

//DON'T
window = aEvent.originalTarget.defaultView.wrappedJSObject;

The latter causes all kinds of scoping issues in jQuery.

Second, I have a question / issue remaining with this method. Since I dropped the wrappedJSObject, I find that I have had to do things like
Code: Select all
window.console = window.wrappedJSObject.console; //exposes firebug console
window.unescape = window.wrappedJSObject.unescape; //exposes unescape

otherwise they aren't available. In particular, $(elem).focus() throws and exception and prevents jQuery UI from working.

Any idea what might be the cause?

Thanks for your help!

Michael

MichaelAtUVa
 
Posts: 3
Joined: June 10th, 2011, 7:56 am

Post Posted June 11th, 2011, 11:22 am

Quick update --- under further investigation, it appears that in the jQuery trigger function this is problematic code:

Code: Select all
            //Trigger an inline bound script
            if ( ontype && jQuery.acceptData( cur ) && cur[ ontype ] && cur[ ontype ].apply( cur, data ) === false ) {
               event.result = false;
               event.preventDefault();
            }


In particular it is cur\[ ontype \] that throws the exception: component cannot be found.

Thanks,
Michael

studgeek

User avatar
 
Posts: 23
Joined: April 9th, 2006, 12:08 pm

Post Posted June 11th, 2011, 11:36 am

Hi Michael, I think I need to see more of your code to understand what you are exactly doing.

Are you are using my code for scenario 2 exactly, if not please post.

And what are your lines for getting the window, selecting the objects, and then calling jQuery?

d

MichaelAtUVa
 
Posts: 3
Joined: June 10th, 2011, 7:56 am

Post Posted July 9th, 2011, 11:29 am

Hello Again,

I had gotten your method to work fine in FF4 but now I am having issue in FF5. I posted here https://forums.mozilla.org/addons/viewt ... 88&p=10656 but haven't had much luck. I thought I would ask you.

Here is the post I had made for quick reference:
I have this snippet of code running in a JSM:

Code:
readURLSync = function (url) {
var xmlhttp = new targetWindow.XMLHttpRequest();
xmlhttp.open("GET", url, false);
xmlhttp.send(null);
return xmlhttp.responseText;
};


That works fine to load a chrome URI (chrome://myext/content/lib/jquery-1.6.1.js) in FF4 but in FF5 I get "Access to restricted URI denied"/"NS_ERROR_DOM_BAD_URI" what do I need to change to get this to work?

I scanned https://developer.mozilla.org/en/firefo ... developers but can't see the change.

Thanks!
Michael

studgeek

User avatar
 
Posts: 23
Joined: April 9th, 2006, 12:08 pm

Post Posted July 11th, 2011, 6:33 pm

Hi Michael, Not sure off the top of my head, and it doesn't really seem related to this thread. Perhaps you should start a new thread? Or try stackoverflow.com (it's a great resource for Firefox questions).

M_Adham
 
Posts: 1
Joined: July 17th, 2011, 1:59 pm

Post Posted July 17th, 2011, 2:08 pm

studgeek wrote:Hi Michael, Not sure off the top of my head, and it doesn't really seem related to this thread. Perhaps you should start a new thread? Or try stackoverflow.com (it's a great resource for Firefox questions).
Hello,

I'm new to this extension development world and this is my first post.. I've searched for the answer in many places but couldnt find anything that worked.. I'm trying to build an extension with JQuery. I tried the approach above by putting all the code in .js class however alert does not work. :-(.. please help

I have copies the code here:
window.addEventListener("load", function() { myExtension.init(); }, false);


window.addEventListener("load", function() { myExtension.init(); }, false);

var myExtension = {
init: function() {
var appcontent = document.getElementById("appcontent"); // browser
if(appcontent){
appcontent.addEventListener("DOMContentLoaded", myExtension.onPageLoad, true);
}
},

onPageLoad: function(e) {
var doc = e.originalTarget; // doc is document that triggered "onload" event
var wnd = doc.defaultView;
// load jQuery and save it as a property of the window
wnd.myExtension_jQuery = myExtension.loadjQuery(wnd); // Be sure to choose a name likely to be unique

// To use jQuery here the next line helps
var $ = jQuery = wnd.myExtension_jQuery;

myExtension.processPage(doc);

if(doc.location.hostname=="hotmail.com") {
window.alert("hotmail");
}
if(doc.location.hostname=="google.com") {
window.alert("google");
getItems();
// doc = document.defaultView;
//jQuery("a[rel='nofollow']", doc).css({border: 'dotted 1px red', backgroundColor: 'pink'});
}
e.originalTarget.defaultView.addEventListener("unload", function(){ myExtension.onPageUnload(); }, true);
},

loadjQuery: function(wnd){
var loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Components.interfaces.mozIJSSubScriptLoader);
loader.loadSubScript("chrome://clhelper/content/jquery/jquery-1.5.js",wnd);
var jQuery = wnd.jQuery.noConflict(true);
//wnd.makethisuniquename = wnd.jQuery
loader.loadSubScript("chrome://clhelper/content/jquery/jquery.hoverIntent.js", jQuery);
return jQuery;
},

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 = myExtension.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;
},
onPageUnload: function(e) {}
};


function getItems() {
window.alert("1");
var doc = event.originalTarget.ownerDocument;
var wnd = doc.defaultView;
var $ = jQuery = wnd.myExtension_jQuery;
jQuery(doc).ready(function () {
alert("hello");
});
};

Thanks in advance.

studgeek

User avatar
 
Posts: 23
Joined: April 9th, 2006, 12:08 pm

Post Posted July 18th, 2011, 7:42 am

Please start a new thread for this or ask on stackoverflow. I don't want this thread to get sidetracked, I know several people following it for its original purpose.

Return to Extension Development


Who is online

Users browsing this forum: No registered users and 3 guests