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

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!';
});

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/";

Using loadFrameScript, how can inject file before the page's script execution?

I'm currently using this in the bootstrap.js file. Is used to edit the client's JavaScript code in my case I'm using recentBrowserWindow.messageManager.loadFrameScript(file, true):
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import('resource://gre/modules/Services.jsm');
var file = 'chrome://testAddon/content/inject.js';
function startup(aData, aReason) {
var recentBrowserWindow = Services.wm.getMostRecentWindow('navigator:browser');
if(recentBrowserWindow.document.readyState == 'complete') {
recentBrowserWindow.messageManager.loadFrameScript(file, true);
}
}
inject.js:
var window = content;
var pageJS = window.wrappedJSObject;
console.log("jQuery is defined:", "jQuery" in window);
//true, jQuery is already assigned on client page.
But here the script inject.js is running after of the client page. The question is:
How can inject inject.js just before the page's script execution?. What method should I use?
From: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIFrameScriptLoader
It says to use document-element-inserted, this will ensure your script runs before the page scripts run.
I don't have much experience with frame scripts, others will have to verify me on this.
This is a really nice example of simple loadFrameScript: https://github.com/mdn/e10s-example-addons/tree/master/run-script-in-all-pages/ported-message-manager/src/chrome/content
See here how he does DOMContentLoaded: https://github.com/mdn/e10s-example-addons/blob/master/run-script-in-all-pages/ported-message-manager/src/chrome/content/frame-script.js
Change that to document-element-inserted but i think this is not an event listener but an observer service.

Make IE7 load javascript first then call function

IE7 gives the following error: 'myapp' is undefined
//home.html
<script type="application/x-javascript" src="//www.mysite.com/myjs.js"></script>
<script type="text/javascript" charset="utf-8">
new myapp.myfunc();
</script>
javascript file:
//myjs.js
myapp = {
myfunc : function(){
alert('hello world');
}
};
*I understand there are many ways to rewrite the code that is used on home.html, but I want to make this work without changing that. I found a working example with similar structure and no JS errors (IE7, IE6). https://google-developers.appspot.com/custom-search-ads/docs/sample
EDIT:
The < script > code will be given to external clients, so I want to keep it as simple as possible. See example link.
Occam's razor suggests that either IE/MSHTML does not support script elements with type="application/x-javascript". That might have to do with the fact that application/x-javascript is not a registered MIME media type, nor was it ever necessary.
Or it has to do with the fact that //www.mysite.example.com/myjs.js is not a supported URI-reference in that environment. Use fully-qualified URIs like http://www.mysite.example.com/myjs.js instead. (And please use the registered example domains for examples.)
You should also declare identifiers that you intend to use as variables:
var myapp = {
…
};
If you do not do this, problems can occur if there is an element named myapp in the document. In JScript/MSHTML, identifier resolution will find a host object in the scope chain that has myapp as its property. The value of that property will be a reference to the corresponding element object, and attempting to overwrite that property value will cause a runtime error.
Add an event handler to the body's Load event. In that event handler, make your myapp.myfunc() call.
Whenever you're making your code available for consumption, you always want to make sure you're being a good citizen on the page. That means you don't want to create unnecessary global variables, and make sure ones you do create are unique enough. Thats why it's a good idea to wrap your code in an immediately-invoked function expression.
Also, it's generally just easier to do the whole thing with javascript. This is how Google analytics and Facebook plugins load their code.
<script type="text/javascript">
(function() {
var loadScript = function(src, callback) {
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = src;
// modern browsers
script.onload = callback;
// IE 6 & 7
script.onreadystatechange = function() {
if (this.readyState == 'complete') {
callback();
}
}
head.appendChild(script);
};
loadScript('path/to/myscript.js', function() {
//script loaded
});
})();
</script>

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

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.

Injecting JS functions into the page from a Greasemonkey script on Chrome

I have a Greasemonkey script that works just fine in Firefox and Opera. I struggle with getting it to work in Chrome, however. The problem is injecting a function into the page that can be invoked by code from the page. Here's what I'm doing so far:
First, I get a helper reference to the unsafeWindow for Firefox. This allows me to have the same code for FF and Opera (and Chrome, I thought).
var uw = (this.unsafeWindow) ? this.unsafeWindow : window;
Next, I inject a function into the page. It's really just a very thin wrapper that does nothing but invoking the corresponding function in the context of my GM script:
uw.setConfigOption = function(newValue) {
setTimeout(setConfigOption, 0, newValue);
}
Then, there's the corresponding function right in my script:
setConfigOption = function(newValue) {
// do something with it, e.g. store in localStorage
}
Last, I inject some HTML into the page with a link to invoke the function.
var p = document.createElement('p');
p.innerHTML = 'set config option to 1';
document.getElementById('injection-point').appendChild(p);
To summarize:
In Firefox, when the user clicks that injected link, it will execute the function call on the unsafeWindow, which then triggers a timeout that invokes the corresponding function in the context of my GM script, which then does the actual processing. (Correct me if I'm wrong here.)
In Chrome, I just get a "Uncaught ReferenceError: setConfigOption is not defined" error. And indeed, entering "window.setConfigOption" into the console yields an "undefined". In Firebug and the Opera developer console, the function is there.
Maybe there's another way to do this, but a few of my functions are invoked by a Flash object on the page, which I believe makes it necessary that I have functions in the page context.
I took a quick look at the alternatives to unsafeWindow on the Greasemonkey wiki, but they all look pretty ugly. Am I completely on the wrong track here or should I look more closely into these?
RESOLUTION: I followed Max S.' advice and it works in both Firefox and Chrome now. Because the functions I needed to be available to the page had to call back into the regular ones, I moved my whole script to the page, i.e. it is completely wrapped into the function he called 'main()'.
To make the extra uglyness of that hack a little bit more bearable, I could at least drop the usage of unsafeWindow and wrappedJSObject now.
I still haven't managed to get the content scope runner from the Greasemonkey wiki working. It should do the same and it seems to execute just fine, but my functions are never accessible to <a> elements from the page, for example. I haven't yet figured out why that is.
The only way to communicate with the code running on the page in Chrome is through the DOM, so you'll have to use a hack like inserting a <script> tag with your code. Note that this may prove buggy if your script needs to run before everything else on the page.
EDIT: Here's how the Nice Alert extension does this:
function main () {
// ...
window.alert = function() {/* ... */};
// ...
}
var script = document.createElement('script');
script.appendChild(document.createTextNode('('+ main +')();'));
(document.body || document.head || document.documentElement).appendChild(script);
I have this :
contentscript.js :
function injectJs(link) {
var scr = document.createElement('script');
scr.type="text/javascript";
scr.src=link;
document.getElementsByTagName('head')[0].appendChild(scr)
//document.body.appendChild(scr);
}
injectJs(chrome.extension.getURL('injected.js'));
injected.js :
function main() {
alert('Hello World!');
}
main();
The other answers either force you to use function expressions, import an external additional file or use a long patched hack.
This answer will add the javascript into the page directly from your source code. It will use ECMAScript 6 (ES6) template literals to get the multi-line javascript string effortlessly onto the page.
var script = document.createElement('script');
script.type = "text/javascript";
script.innerHTML = `
function test() {
alert(1);
}
`;
document.getElementsByTagName('head')[0].appendChild(script);
Please note the backticks `` that define the beginning and the end of a multi-line string.
I took a quick look at the alternatives to unsafeWindow on the Greasemonkey wiki, but they all look pretty ugly. Am I completely on the wrong track here or should I look more closely into these?
You should look, because it's only available option. I'd prefer to use location hack.
myscript.user.js:
function myFunc(){
alert('Hello World!');
}
location.href="javascript:(function(){" + myFunc + "})()"
example.com/mypage.html
<script>
myFunc() // Hello World!
</script>
Sure, it's ugly. But it's working well.
Content Scope Runner method, mentioned by Max S. is better than location hack, because its easier to debug.

Categories

Resources