Use content script to define global variables - javascript

I am creating a Firefox extension, and one feature of it that I would like is the ability for the user to inject a script or stylesheet into a specific website, rather like Greasemonkey (except that this will only be for one site). I am adding some functions for the scripts to make use of, which I intended to add from the Content Script into the main (unsafe) window. On the MDN blog, it says that they have made changes to how it should be implemented, so I have based my code on the new implementation as advised in the post, so this is what I have:
var $jq = jQuery.noConflict();//Yes, I am also injecting jQuery at the same time
console.log("created jquery object"); //This works
exportFunction($jq, unsafeWindow, {defineAs: "$jq"});
console.log("This will never be called");
But execution of the script just stops, and in the console it prints Message: TypeError: window is null.
I am testing in Firefox 28 predominantly (I can't seem to get Firefox for Ubuntu to update beyond that right now, and a whole load of issues are forcing me to use Ubuntu in a VM for this), but in Nightly 31a1 (Win7) nothing is ever injected, including a hardcoded style (that works on FF28) so I will have to figure that out at some point. (The PageMod code is here:
var lttWorker = sdk.pageMod.PageMod({
include:["*"],
/*contentScriptFile: [sdk.data.url("jquery.large.js"), sdk.data.url("scripts/bootstrapper.js")],
contentScriptWhen: "ready",*/ //This is commented to test whether it was an issue with the script. It's not.
contentStyle: "#header_bar{background-color:green;}", //This is injected in FF28 but not 31
attachTo: ["existing", "top"],
onAttach: function(){desktopNotifications({title:"attached content worker", text:"The content worker has been successfully attached"})} //This is called in FF28 but not 31
});
lttWorker.on("error", function(){callError("pageWorker failed");}); //This never gets called. Ever.
if anybody is interested)
EDIT: I have now tried it on Firefox 30b and there are still a load of issues, although they seem to be slightly different to both FF28 and 31...

First of all: These new functions are supported in Firefox 30 and later. See #canuckistani answer.
The exportFunction API is way too limited to actually inject something like jQuery with all the complex objects being or containing DOM nodes. That simply won't fly with the structured-clone algorithm that is applied to arguments.
The API is meant as a way for add-ons to communicate with pages bi-directionally, and not to inject complex libraries.
Your best bet is actually creating a script tag using the DOM APIs and putting jQuery there.

Related

How can I define a property on the tab's main window object?

I'm trying to define the property jQuery on the window object from a content script with code in the setter. That way, when the actual jQuery object is defined, I can use it right away. I seem to be unable to get it right, though.
The target website is Outlook.com. That's the webmail version of Outlook.
I tried to put the code in the content script directly, but even if I put "all_frames": true in the content_scripts section of the manifest (so the code gets injected into every frame), it isn't working.
function afterInit(){
var _jQuery;
console.log('This gets logged');
Object.defineProperty(window, 'jQuery', {
get: function() { return _jQuery; },
set: function(newValue) {
_jQuery = $ = newValue;
console.log('This is never logged!');
$(document.body).append($('<span>I want to use jQuery here</span>'));
}
});
}
afterInit();
I verified that window.jQuery is properly defined afterwards by the actual jQuery function/object, but my setter code is never executed.
I also tried it with message passing: I send a message with the code as a string to a background script, and use executeScript to execute it on the correct tab, but this also doesn't work.
chrome.runtime.sendMessage(
{action:'jQueryPreInit', value: '('+afterInit.toString()+')();'});
And in my background script:
chrome.runtime.onMessage.addListener(function(message, sender, callback) {
switch (message.action){
case "jQueryPreInit": chrome.tabs.executeScript(sender.tab.id, {code: message.value});
}
});
If I put something else than the Object.defineProperty code in the executeScript code, that works fine. I only have problems defining the property.
(quotes are from a comment)
I want to use the jQuery provided by the page itself. I could try inserting the same jQuery file as Outlook does and hope it gets loaded from cache, but I'd rather just keep my extension as clean as possible, and use what is already available.
Your attempt at optimizing the extension is not workable / not recommended.
First off, you will not be able to use the page's code anyway because of the isolation between content script and webpage code. You cannot obtain a reference to page's own jQuery/$.
But let's for a moment suppose that you could. And then the site updates jQuery to another version, renames the jQuery object or stops using it entirely, which is outside your control. Result: your extension is broken. This is, partially, the rationale behind the isolation in the first place.
As a result of the context isolation, you are guaranteed there are no conflicts between your copy of jQuery and whatever runs on the site. So you don't need to worry about that: use your copy, and use the standard $ to access it.
Bundling a <100 KB file with your extension as a one-time download that makes sure code is available 100% of the time and with at worst disk-access latency is not making it less "clean", quite the opposite. It's a common practice and is enshrined in the docs.
Looking at your actual code, it executes in the content script context (regardless whether it's through manifest or executeScript), not in the page context. As such, no matter what the page does, $ will not be defined there.
I verified that window.jQuery is properly defined afterwards by the actual jQuery function/object [...]
I assume that you tried to execute window.jQuery in the console; by default, that executes it in the page context, not in your content script context (therefore, not reflecting the state of the content script context and not invoking your getter/setter). If you want to test your content script, you need to change top in the context drop-down above the console to your extension's context.
All that said, however,
When all is said and done, I want to use jQuery's ajaxSuccess function to execute code every time an e-mail is opened in the read pane.
Here we've got a problem. Since the content script code and webpage code are isolated, your code will never know about AJAX executing in the page's copy (not through ajaxSuccess, anyway).
Possible courses of action:
Rely on other methods to detect the event you want. Perhaps monitoring the DOM.
Inject some code into the page itself; the only way to do so is by injecting a <script> tag into the page from the content script. There, you can access the page's copy of jQuery, attach your listener and message your content script when something happens.
Rely on the background page to detect activity you need with webRequest API. This will likely intercept the AJAX calls, but will not give you the reply contents.
Final note: this may not be as simple as AJAX calls; perhaps the page maintains a WebSocket connection to get realtime push updates. Tapping into this is trickier.
Thanks to Xan, I found there are only two ways to do this.
The first is by adding a <script> element to the DOM containing the appropriate code. This is a pretty extensive StackOverflow answer on how to do that: https://stackoverflow.com/a/9517879/125938.
The second is using Javascript pseudo-URLs and the window.location object. By assigning window.location a bookmarklet-style URL containing Javascript, you also bypass certain security measures. In the content script, put:
location = 'javascript:(' + (function(){
var _jQuery;
Object.defineProperty(window, 'jQuery', {
get: function() { return _jQuery; },
set: function(newValue) {
_jQuery = $ = newValue;
console.log('totally logged!');
$('<span>jQuery stuff here</span>');
}
});
}).toString().replace(/\n/g, ' ')+')();';
The reason I/you were originally failing to define it, was because both methods of code injection we were using, caused our code to be sandboxed into isolated worlds: https://developer.chrome.com/extensions/content_scripts#execution-environment. Meaning, they share the page's DOM and could communicate through it, but they can't access each other's window object directly.

Firefox Extension: Access the DOM Before It's Loaded

I'm trying to create a Firefox extension that fires my Javascript code before any of the current page's Javascript is fired. My Javascript code will basically control whether or not the page's Javascript code can be executed or denied.
I first started out by trying to follow this answer, but I couldn't really figure out how to get it to work and realized I was relying on onDOMContentLoaded, which loads after the Javascript has already executed.
I then turned my attention toward XPCOM, but once again didn't really understand what the Firefox tutorials were telling me.
I've recently been trying to make an extension through Firebug, but I seem to hit the same problem... only having access to the Javascript after it's been parsed/executed. Here's the resulting code that I wrote. I think if I could access the file's objects in the onExamineResponse event, my problem could be solved, but I don't know how to do that... I'm talking about this code:
BeepbopListener.prototype = {
onRequest: function(context, file) {
...
},
onExamineResponse: function(context, file) {
FBTrace.sysout("onexamineresponse " + file); // this returns something like
// '[xpconnect wrapped (nsISupports, nsIHttpChannel, nsIRequest, nsIUploadChannel, nsITraceableChannel, nsIHttpChannelInternal)]'
// but I don't know how to access those elements...
var pattern = /\.js$/;
if (pattern.test(file.href) && FBTrace.DBG_BEEPBOP) {
FBTrace.sysout("ONEXAMINE DOESN'T EVEN GET IN THIS IF SO YOU WON'T SEE THIS");
}
},
...
};
So my question is... is there a tutorial out there that shows me how I can get access to all Javascript code on a page before it's executed? Also, if anyone has any helpful insight, I'd love to hear it. Oh, and if y'all need more code from me, just let me know, and I'll post it.
You can access a new document before any JavaScript code runs by listening to the content-document-global-created observer notification. However, the document will be empty at this point and JavaScript code will run as soon as the parser adds a <script> tag - you cannot really prevent it. Here are the options to control script execution that I am aware of.
1) Disable all JavaScript for a window using nsIDocShell.allowJavascript:
wnd.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIWebNavigation)
.QueryInterface(Components.interfaces.nsIDocShell)
.allowJavascript = false;
This is an all or nothing approach. Note that JavaScript stays disabled even when a new document loads into the same frame.
2) Implement the nsIContentPolicy interface in an XPCOM component and register it in the content-policy category (via nsICategoryManager). Your shouldLoad() function will be able to block scripts selectively - but it will only called for external scripts (meaning <script src="...">), not for inline scripts on the page.
3) Use JavaScript debugger service to intercept script execution. You could use jsdIDebuggerService.interruptHook to step through JavaScript execution and abort the script whenever you like. But that would slow down JavaScript execution very significantly of course. At the very least you should use jsdIDebuggerService.addFilter() to restrict it to a particular document, otherwise you will slow down the entire browser (including browser UI).
I'm trying to create a Firefox extension that fires my Javascript code before any of the current page's Javascript is fired. My Javascript code will basically control whether or not the page's Javascript code can be executed or denied.
Start by completely preventing the document from getting parsed altogether then on the side, fetch the same document, do any processing on this document and then inject the resulting document in the page. Here is how I currently do just that https://stackoverflow.com/a/36097573/6085033

JavaScript objects visible in FireBug, inaccessible in code

In my code I have a line that dumps the current window (which happens to be a youtube video page):
Firebug.Console.log(myWindow);
It can be seen that window object contains "yt" property, which is another object that can be easily inspected in debugger:
http://i.imgur.com/lHHns.png
Unfortunately, calling
Firebug.Console.log(myWindow.yt);
logs "undefined" - why is that, and how can I access this "yt" property?
Edit: one addidtion that might be important: the code I'm writing is part of a firefox extension, so it's not really running inside a pgae, but in chrome - I'm starting to think that it may be the cause. Can chrome scripts be somehow limited in what they can see/acces as opposed to code in script tags?
For security reasons, Firefox extensions don't access web page objects directly but via a wrapper. This wrapper allows you to use all properties defined by the DOM objects but anything added by page JavaScript will be invisible. You can access the original object:
Firebug.Console.log(XPCNativeWrapper.wrappedJSObject.yt);
However, if you want to interact with the web page from an extension you should consider alternatives where the web page cannot play tricks on you (e.g. running unprivileged code in the content window: myWindow.location.href = "javascript:...").
Firefox and Chrome extensions can't access JavaScript within the page for security reasons.
I have seen confusion like this using asynchronous APIs.
console.log(obj); shows the contents of an object all filled in, but when accessing the object properties in code, they aren't really populated yet due to the call being asynchronous.
Why Chrome and Firefox shows them all filled in is probably just a timing issue as they probably process the console.log() asynchronously as well.

How do you find out when new data is loaded via ajax in a firefox addon?

I am creating an addon for firefox with the new addon builder. I would like to have an event fired when data is loaded with an XMLHttp request so I can run a script to add in some extra features. Similar to a page load except the site uses ajax. This is my first addon and I'm not sure how to go about doing this. I looked around and there was this method
XMLHttpRequest.prototype.getResponseHeader=function(){
runCheck();
};
But when I added it in the console tells me that there is no XMLHttpRequest.prototype. I had tested it by loading it in firebug and it worked, but when I try to use in the content script it doesn't?
There's a little known event called DOMSubtreeModified, which fires when the DOM Subtree is Modified (big surprise there). I read some place that it is supposed to be deprecated, but I can't remember where. Check the DOM event reference.

How to help the debugger see my javascript, or how to best refactor my script to help make it debugger-friendly?

I have an ASP.NET MVC project that uses some simple AJAX functionality through jQuery's $.get method like so:
$.get(myUrl, null, function(result) {
$('#myselector').html(result);
});
The amount of content is relatively low here -- usually a single div with a short blurb of text. Sometimes, however, I am also injecting some javascript into the page. At some point when I dynamically include script into content that was itself dynamically added to the page, the script still runs, but it ceases to be available to the debugger. In VS2008, any breakpoints are ignored, and when I use the "debugger" statement, I get a messagebox saying that "no source code is available at this location." This fails both for the VS2008 debugger and the Firebug debugger in Firefox. I have tried both including the script inline in my dynamic content and also referencing a separate js file from this dynamic content -- both ways seemed to result in script that's unavailable to the debugger.
So, my question is twofold:
Is there any way to help the debugger recognize the existence of this script?
If not, what's the best way to include scripts that are used infrequently and in dynamically generated content in a way that is accessible to the debuggers?
I can not comment yet, but I can maybe help answer. As qwerty said, firefox console can be the way to go. I'd recommend going full bar and getting firebug. It hasn't ever missed code in my 3 years using it.
You could also change the way the injected javascript is added and see if that effects the debugger you're using. (I take it you're using Microsoft's IDE?).
In any case, I find the best way to inject javascript for IE is to put it as an appendChild in the head. In the case that isn't viable, the eval function (I hate using it as much as you do) can be used. Here is my AJAX IE fixer code I use. I use it for safari too since it has similar behavior. If you need that too just change the browser condition check (document.all for IE, Safari is navigator.userAgent.toLowerCase() == 'safari';).
function execajaxscripts(obj){
if(document.all){
var scripts = obj.getElementsByTagName('script');
for(var i=0; i<scripts.length; i++){
eval(scripts[i].innerHTML);
}
}
}
I've never used jquery, I preferred prototype then dojo but... I take it that it would look something like this:
$.get(myUrl, null, function(result) {
$('#myselector').html(result);
execajaxscripts(result);
});
The one problem is, eval debug errors may not be caught since it creates another instance of the interpreter. But it is worth trying.. and otherwise. Use a different debugger :D
This might be a long shot, but I don't have access to IE right now to test.
Try naming the anonymous function, e.g.:
$.get(myUrl, null, function anon_temp1(result) {
$('#myselector').html(result);
});
I'm surprised firebug is not catching the 'debugger' statement. I've never had any problems no matter how complicated the JS including method was
If this is javascript embedded within dynmically generated HTML, I can see where that might be a problem since the debugger would not see it in the initial load. I am surprised that you could put it into a seperate .js file and the debugger still failed to see the function.
It seems you could define a function in a seperate static file, nominally "get_and_show" (or whatever, possibly nested in a namespace of sorts) with a parameter of myUrl, and then call the function from the HTML. Why won't that trip the breakpoint (did you try something like this -- the question is unclear as to whether the reference to the .js in the dynamic HTML was just a func call, or the actual script/load reference as well)? Be sure to first load the external script file from a "hard coded" reference in the HTML file? (view source on roboprogs.com/index.html -- loads .js files, then runs a text insertion func)
We use firebug for debug javascript, profile requests, throw logs, etc.
You can download from http://getfirebug.com/
If firebug don't show your javascript source, post some url to test your example case.
I hope I've been of any help!
If you add // # sourceURL=foo.js to the end of the script that you're injecting then it should show up in the list of scripts in firebug and webkit inspector.
jQuery could be patched to do this automatically, but the ticket was rejected.
Here's a related question: Is possible to debug dynamic loading JavaScript by some debugger like WebKit, FireBug or IE8 Developer Tool?

Categories