Executing injected Javascript code in a page when developing a Firefox extension - javascript

I'm developing a Firefox extension which places a button in the status bar. When the button is clicked, the extension injects some Javascript into the current page. This Javascript has a function that I would like to invoke with some parameters. I've managed injecting the code, I've inspected the page through Firebug and verified that the JS has been injected. How can I call a Javascript function in the page from my extension?
--More information
Here's the code that I'm using to inject my Javascript:
var doc = window.content.document;
//Add the script
var visibilityJS = doc.createElement("script");
visibilityJS.setAttribute("type", "text/javascript");
visibilityJS.setAttribute("charset", "UTF-8");
visibilityJS.setAttribute("src", "chrome://visibility/content/scripts/visibility.js");
head.appendChild(visibilityJS);
//Call the function
alert("Executing testfunction");
window.content.document.defaultView.testFunction();
..and the code inside my JS file that i'm going to inject. i.e. visibility.js
window.testFunction = function() {
alert("Message");
}
Thanks.

This worked. I don't know the technicalities. I got part of the solution from Felix and part of it from here.
window.content.document.defaultView.wrappedJSObject.testFunction();

If you declare a global variable in your injected code (or explicitly set a property of the window object), then one way do get a reference to this element from your extension, is via the gBrowser object:
gBrowser.contentDocument.defaultView.yourObject
^-- HTML document ^
object |-- window object
Be careful though, when you use window and document inside your code. Depending on the context it might refer to the Firefox window or the website window object.

Related

Polyfill window.showModalDialog in webview

So I am creating a Chrome app with an embedded <webview> element. The embedded webapp contains a lot of legacy code such as window.showModalDialog calls (which Chrome no longer supports).
The thing I'm trying to do now is to polyfill those calls. I've created a simple test example:
<webview src="http://legacyCodeIsAwesome.com/" style="width:100%; height:100%"></webview>
Code operating on the webview element:
webview.addEventListener('contentload', function() {
webview.executeScript(
{
code: 'window.showModalDialog = function() { console.log ("Hello, world");}',
runAt: 'document_start'
}
);
});
The above code runs (adding a debug console.log works), only it doesn't do what it's supposed to do, which is overwrite the showModalDialog function.
Is there some sort of restriction on overwriting functions on the window object in webviews? Is there a way around it?
When you call webview.executeScript, you essentially create a Content Script.
One of the core principles of content scripts is isolated world: a content script sees a separate copy of window object.
Content scripts execute in a special environment called an isolated world. They have access to the DOM of the page they are injected into, but not to any JavaScript variables or functions created by the page. It looks to each content script as if there is no other JavaScript executing on the page it is running on. The same is true in reverse: JavaScript running on the page cannot call any functions or access any variables defined by content scripts.
So, your override works, but only in the content script context.
To perform the override in the page context itself, we need to go deeper.
A content script can create a <script> element on a page; code in said element will execute in page's context. You can read about this technique, called page-level scripting or injected scripts, in this canonical question.
// Content script
var script = document.createElement('script');
script.textContent = `
window.showModalDialog = function() {
console.log("Hello, world");
}
`;
(document.head||document.documentElement).appendChild(script);
script.remove();
That said, you're injecting your code far too late. On contentload the offending JS likely executed already, and adding "document_start" isn't going to rewind time.
Fortunately, you can declare in advance the content scripts you want to have:
webview.addContentScripts([
{
name: "showModalDialogPolyfill",
matches: ["https://webapp.example.com/*"],
js: { files: ['content.js'] },
run_at: 'document_start'
}
]);
webview.src = "https://webapp.example.com/";

How can I import an external .js to my Java test with Selenium in Eclipse?

I want to import my function of JavaScript to my Java project in Eclipse and using it with Selenium, but I can't find the form to do this.
I try making .js file like this to Selenium could recognise this code:
Selenium.prototype.doProve = function() {
$("#proveDiv > div > div").each(function(i, obj)
{
$(i).click(function(){});
});
};
Well, as you can see I have 3 divs and what I want to do it's to access to the third div in which I have 2 divs more (this is the clue of the loop). In each div of the loop I want to make a click.
I tried to use this function in my Java project but I can't get any result so I tried to execute this function as a String and then executing the script like this:
String script = "$(\"#proveDiv > div > div" +
"\").each(function(i, obj){ " +
"$(i).click(function(){});})";
//Executing script
if (driver instanceof JavascriptExecutor) {
((JavascriptExecutor) driver).executeScript(script);
}
It works, but it's not very useful, because I want to make an external .js which contains all the JavaScript functions and call them from there, not in a String.
Any help would be appreciated. I saw some questions here, but any of them worked for me.
Thank you very much!
It works, but it's not very useful, because I want to make an external
.js which contains all the JavaScript functions and call them from
there, not in a String.
You can achieve this only by loading your external js file into the DOM
var addscript=window.document.createElement('script');addscript.type='text/javascript';addscript.src='http://localhost/somescript.js';document.getElementsByTagName('body')[0].appendChild(addscript);
Note : Most browsers do not allow you to load local resources so put your external js file in local webserver and then access it like http://localhost/somescript.js
After loading the js file into DOM now you can call the javascript functions in the external js file
Example
Lets say we have a external js file named somescript.js which contains the below function
//simple function which sets the value "test" to the search box
window.somefunc = function () {document.getElementsByName("s")[0].value='test';}
Webdriver code :
driver.get("http://www.jquery.com");
//Load the External js file into DOM
((JavascriptExecutor) driver)
.executeScript("var addscript=window.document.createElement('script');addscript.type='text/javascript';addscript.src='http://localhost/somescript.js';document.getElementsByTagName('body')[0].appendChild(addscript);");
//wait for the js to be loaded to the DOM
((JavascriptExecutor) driver)
.executeScript("return typeof(somefunc)").toString().equals("function");
//Now you call the JavaScript functions in the JS file
((JavascriptExecutor) driver)
.executeScript("somefunc();");
Note : Behind the scenes Selenium wraps your JavaScript code in an anonymous function. So your somefunc function is local to this anonymous function.And due to JavaScript's scoping rules, somefunc doesn't exist outside of that anonymous function. so we have made it a global function by assigning it to window.
EDIT :
And I don't really understand why you use the window statement. And I
was searching something like ((JavascriptExecutor)
driver).executeScript("here the .js"); But I don't know if it is
possible
This is how executeScript method executes the provided javascript
The script fragment provided will be executed as the body of an
anonymous function.
Example if we used the below code
((JavascriptExecutor) driver)
.executeScript("somefunc = function () {document.getElementsByName("s")[0].value='test';}");
((JavascriptExecutor) driver)
.executeScript("somefunc();");
(function() {
somefunc = function () {document.getElementsByName("s")[0].value='test';}
})();
(function() {
somefunc();
});
What do you mean where you say that you want to put the external .js
into the DOM?
By DOM i mean Document object model of the page constructed as a tree of Objects (in short your webpage).We use javascript to load the external js to the webpage and then call the functions in the js file and execute them(like in the above example).
In the code that you put in your edit. Both functions are the same?
I just gave an example by that what i meant was each script provided in execute script will be executed in the body of an anonymous function.In our case we haven't used executescript to create the somefunc function rather used it from the external js file in dom we only called it using the executescript method so you can do it with or without the window object
//simple function which sets the value "test" to the search box
somefunc = function () {document.getElementsByName("s")[0].value='test';}//this will also work
Hope this helps you.Kindly get back if you have any queries.
You could store the javascript in a file like a properties or xml file.
Sample File:
clickOnLoginButton=function bclick(){....};bclick();
Sample Code:
FileInputStream file;
Properties properties = new Properties();
// load the file handle for properties file
file = new FileInputStream(filename);
// load all the properties from this file
properties.load(file);
// we have loaded the properties, so close the file handle
file.close();
String mainExecutor = properties.getProperty(parameter);
WebDriver dr = initalizeWebDriver();
JavascriptExecutor js = (JavascriptExecutor) dr;
js.executeScript(mainExecutor);

In a Firefox Add-on how to resolve the error "window is not defined" when adding an eventlistener to the overall browser window?

I try to add an eventListener to my extension. I want to execute a function everytime a tab is active (got clicked by the user) or is new loaded.
I tried this:
window.addEventListener("DOMContentLoaded", checkHost(), false);
It gives me the error
Uncaught Reference Error: window is not defined
It drives me cracy, i can't find examples on the web. Please help me.
For those wanting to use the window object, you can create it using this code:
var { viewFor } = require("sdk/view/core");
var window = viewFor(require("sdk/windows").browserWindows[0]);
This code can be found on MDN at: https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/windows
Via your current status: http://builder.addons.mozilla.org/package/206579/latest
The online builder is an online IDE for developing with the Addon-SDK, where window isn't in global scope -- it's not any specific window.
You can include the tabs module and listen for ready events or activate (a tab is now focused) events, which may be what you want.
let tabs = require('sdk/tabs');
tabs.on('ready', function (tab) {
console.log(tab.url + ' is ready!');
});
The issue is that Firefox extensions do not run in the context of any particular window. As such, they often do not have the window object defined, or it is defined as something which you are not expecting if you are not familiar with writing extension code. This is particularly true if you are approaching this from the point of view of writing JavaScript for use within an HTML page. Extensions operate in a significantly larger context which includes the entire browser and all windows and tabs. Thus, there is no automatically appropriate window to use as the window object. In the context of an extension, each HTML page is just a part of the whole.
You can obtain each primary browser window through the use of nsIWindowMediator. The following function, from MDN, will run the function you pass to it once for each open window:
Components.utils.import("resource://gre/modules/Services.jsm");
function forEachOpenWindow(todo) // Apply a function to all open browser windows
{
var windows = Services.wm.getEnumerator("navigator:browser");
while (windows.hasMoreElements())
todo(windows.getNext().QueryInterface(Components.interfaces.nsIDOMWindow));
}
You will often want to find the window for the most recent browser/tab which was accessed by the user. The following code will define and set the window variable to the most recently used browser/tab. It will work either in the Add-on SDK, or in overlay/bootstrap extensions depending on which portion you un-comment.
For more information about using windows in a Firefox extension, you should see Working with windows in chrome code.
if (window === null || typeof window !== "object") {
//If you do not already have a window reference, you need to obtain one:
// Add a "/" to un-comment the code appropriate for your add-on type.
/* Add-on SDK:
var window = require('sdk/window/utils').getMostRecentBrowserWindow();
//*/
/* Overlay and bootstrap (from almost any context/scope):
var window=Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
//*/
}
Alternately using Services.jsm to access nsIWindowMediator:
/* Overlay and bootstrap:
Components.utils.import("resource://gre/modules/Services.jsm");
//*/
if (window === null || typeof window !== "object") {
//If you do not already have a window reference, you need to obtain one:
// Add a "/" to un-comment the code appropriate for your add-on type.
/* Add-on SDK:
var window = require('sdk/window/utils').getMostRecentBrowserWindow();
//*/
/* Overlay and bootstrap (from almost any context/scope):
var window = Services.wm.getMostRecentWindow("navigator:browser");
//*/
}
You have to put the eventlistener inside a script referenced in the browser.xul overlay:
Try it like this:
window.addEventListener("DOMContentLoaded", function() {checkHost();}, false);

Can the window object be modified from a Chrome extension? [duplicate]

This question already has answers here:
Access variables and functions defined in page context using a content script
(6 answers)
Closed 9 months ago.
I would like to make a Chrome extension that provides a new object inside window. When a web page is viewed in a browser with the extension loaded, I would like window.mything to be available via Javascript. The window.mything object will have some functions that I will define in the extension, and these functions should be callable from console.log or any Javascript file when the page is viewed in a browser with the extension enabled.
I was able to successfully inject a Javascript file into the page by using a Content Script:
var s = document.createElement("script");
s.src = chrome.extension.getURL("mything.js");
document.getElementsByTagName("head")[0].appendChild(s);
mything.js looks like this:
window.mything = {thing: true};
console.log(window);
Whenever a page loads, I see the entire window object as I expect it to be in the console. However, I can't interact with the window.mything object from the console. It seems at if the injected script hasn't really modified the global window object.
How can I modify the global window object from a Chrome extension?
You can't, not directly. From the content scripts documentation:
However, content scripts have some limitations. They cannot:
Use chrome.* APIs (except for parts of chrome.extension)
Use variables or functions defined by their extension's pages
Use variables or functions defined by web pages or by other content scripts
(emphasis added)
The window object the content script sees is not the same window object that the page sees.
You can pass messages via the DOM, however, by using the window.postMessage method. Both your page and content script listen to the message event, and whenever you call window.postMessage from one of those places, the other will receive it. There's an example of this on the "Content Scripts" documentation page.
edit:
You could potentially add some methods to the page by injecting a script from the content script. It still wouldn't be able to communicate back with the rest of the extension though, without using something like postMessage, but you could at least add some things to the page's window
var elt = document.createElement("script");
elt.innerHTML = "window.foo = {bar:function(){/*whatever*/}};"
document.head.appendChild(elt);
After hours trying different attempts and facing security issues like CORS, I found ways to edit the window object on Chrome, Firefox and Safari. You need to use different strategies for each one:
Chrome
Add your script to content_scripts.
Inside your script file, append a script to the page and make it run your custom code inline. Like this:
;(function() {
function script() {
// your main code here
window.foo = 'bar'
}
function inject(fn) {
const script = document.createElement('script')
script.text = `(${fn.toString()})();`
document.documentElement.appendChild(script)
}
inject(script)
})()
Firefox
On Firefox, the solution above doesn't work due to a Content-Security-Policy error. But the following workaround is currently working, at least for now:
Add 2 scripts to content_scripts, e.g. inject.js and script.js
The inject script will get the full absolute url of the script.js file and load it:
;(function() {
const b = typeof browser !== 'undefined' ? browser : chrome
const script = document.createElement('script')
script.src = b.runtime.getURL('script.js')
document.documentElement.appendChild(script)
})()
Your script.js will contain your main code:
;(function() {
// your main code here
window.foo = 'bar'
})()
Safari
It's very similar to Firefox.
Create 2 javascript files, e.g. inject.js and script.js
The inject script will get the full absolute url of the script.js file and load it:
;(function() {
const script = document.createElement('script')
script.src = safari.extension.baseURI + 'script.js'
document.documentElement.appendChild(script)
})()
Your script.js will contain your main code:
;(function() {
// your main code here
window.foo = 'bar'
})()
Source code
See full code here: https://github.com/brunolemos/simplified-twitter
As others have pointed out, context scripts do not run in the same context as the page's, so, to access the correct window, you need to inject code into the page.
Here's my take at it:
function codeToInject() {
// Do here whatever your script requires. For example:
window.foo = "bar";
}
function embed(fn) {
const script = document.createElement("script");
script.text = `(${fn.toString()})();`;
document.documentElement.appendChild(script);
}
embed(codeToInject);
Clean and easy to use. Whatever you need to run in the page's context, put it in codeToInject() (you may call it whatever you prefer). The embed() function takes care of packaging your function and sending it to run in the page.
What the embed() function does is to create a script tag in the page and embed the function codeToInject() into it as an IIFE. The browser will immediately execute the new script tag as soon as it's appended to the document and your injected code will run in the context of the page, as intended.
A chrome extension's content_script runs within its own context which is separate from the window. You can inject a script into the page though so it runs in the same context as the page's window, like this: Chrome extension - retrieving global variable from webpage
I was able to call methods on the window object and modify window properties by essentially adding a script.js to the page's DOM:
var s = document.createElement('script');
s.src = chrome.extension.getURL('script.js');
(document.head||document.documentElement).appendChild(s);
s.onload = function() {
s.remove();
};
and creating custom event listeners in that injected script file:
document.addEventListener('_my_custom_event', function(e) {
// do whatever you'd like! Like access the window obj
window.myData = e.detail.my_event_data;
})
and dispatching that event in the content_script:
var foo = 'bar'
document.dispatchEvent(new CustomEvent('_save_OG_Editor', {
'detail': {
'my_event_data': foo
}
}))
or vice versa; dispatch events in script.js and listen for them in your extension's content_script (like the above link illustrates well).
Just be sure to add your injected script within your extension's files, and add the script file's path to your manifest within "web_accessible_resources" or you'll get an error.
Hope that helps someone \ (•◡•) /
I've been playing around with this. I found that I can interact with the window object of the browser by wrapping my javascript into a window.location= call.
var myInjectedJs = "window.foo='This exists in the \'real\' window object';"
window.location = "javascript:" + myInjectedJs;
var myInjectedJs2 = "window.bar='So does this.';"
window.location = "javascript:" + myInjectedJs2;
It works, but only for the last instance of window.location being set. If you access the document's window object, it will have a variable "bar" but not "foo"
Thanks to the other answers here, this is what I'm using:
((source)=>{
const script = document.createElement("script");
script.text = `(${source.toString()})();`;
document.documentElement.appendChild(script);
})(function (){
// Your code here
// ...
})
Works great, no issues.
Content Scripts can call window methods which can then be used to mutate the window object. This is easier than <script> tag injection and works even when the <head> and <body> haven't yet been parsed (e.g. when using run_at: document_start).
// In Content Script
window.addEventListener('load', loadEvent => {
let window = loadEvent.currentTarget;
window.document.title='You changed me!';
});

How would I pass data to an external script loaded with $.getScript()?

So I'm trying to load a javascript remotely using jquery's $.getScript, but I'm puzzled on how I can pass data to the external script.
I've tried setting variables before the call but they aren't available in the script that gets loaded, and when I try to send/retrieve them using the query string, the remote script tries to read the querystring of the base file that it gets called from, not itself. Is there any other way to do this? Or is it possible to have a javascript file read its own querystring rather than the file it's called from (that's loaded in the browser)?
// editor ini
var editor_ini = { page: current_page, action: 'edit' };
var foo = 'bar';
// load the editor
$.getScript('assets/desktop/desklets/'+launcher.config.editor+'/execute.js', function(){});
In the execute.js file, the editor_ini and foo are both unavailable, I get the same result with:
// load the editor
$.getScript('assets/desktop/desklets/'+launcher.config.editor+'/execute.js', { page: current_page, action: 'edit', foo: 'bar' }, function(){});
because the remote script seems to be getting the query string from the original document rather than the one used when calling the file.
If it matters, I was trying to use the query object plugin for jquery for reading the query string.
global variable declared in inline javascript is accessible in external javascript page loaded using $.getScript().
I bet that your var foo='bar' is inside a function, so not visible in global scope. Try:
window.foo = 'bar'
Truly global variables will be accessible to your script. So, if they aren't, then it's probably because your variables that you think are global actually aren't. You can either move them to the top level scope or set them on the window object like Alexei suggested.
There are other ways to share data.
1) You can put an id on the <script> tag that loads the code and then have the code get the .src value from that tag and get the query string off the script's actual URL. I like this option, but I don't know if you can do it using jQuery.getScript() since I don't think it exposes that as an option.
2) You can have the loading script call a function that you provide and return an object with the desired data from that function.
3) Once the new script is loaded, you can call a setXXX() function in that script to set the state that it needs.
4) You can set information into a cookie that the other script can read.
5) You can encode data into a URL hash value that the other script can read.

Categories

Resources