I have the following code in my content script which is calling a function in the webpage's javascript
var actualCode = '(' + function() {
functionInMyWebPage();
}
+ ')();';
var script = document.createElement('script');
script.textContent = actualCode;
(document.head||document.documentElement).appendChild(script);
script.parentNode.removeChild(script);
This is working and it fires the functionInMyWebPage() function.
Now I need to get the returned value of this functionInMyWebPage() and call another function which defined in the content script itself. If I call it in the above script block it will complain as it has not defined.
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.
More info Content script execution environment
Related
I'm developing an extension for Google Chrome and the problem I'm having is I need to be able to call a JavaScript function that belongs to the webpage that's opened in the tab.
For details, the website is my website, therefore I know that function does exist. That function does a lot of things based on a string value. I want the user to be able to highlight text on any webpage, click a button from the Chrome extension that automatically loads my webpage and calls that function with the highlighted text as it's value.
Here's what I got so far:
chrome.tabs.create({ url: "https://mywebsite.com" }, function (tab) {
var c = "initPlayer('" + request.text + "');"; ////'request.text' is the highlighted text which works
chrome.tabs.executeScript(tab.id, { code: c });
});
But Chrome console says: "Uncaught ReferenceError: initPlayer is not defined."
I know that function does exist as it is in my code on my own website.
Any help is highly appreciated. Thanks!
This happens because pages and content scripts run inside two separate javascript contexts. This means that content scripts cannot acces functions and variables inside a page directly: you'll need to inject a script into the page itself to make it work.
Here is a simple solution:
chrome.tabs.create({url: "https://mywebsite.com"}, function (tab) {
var c = "var s = document.createElement('script');\
s.textContent = \"initPlayer('" + request.text + "');\";\
document.head.appendChild(s);"
chrome.tabs.executeScript(tab.id, {code: c});
});
NOTE: since January 2021, use Manifest V3 with chrome.scripting.executeScript() instead of chrome.tabs.executeScript().
With the above code you will basically:
Create the tab
Inject the code (variable c) into it as a content script that will:
Create a script with the code you want to execute on the page
Inject the script in the page and, therefore, run its code in the page context
I'm trying to load some data from Reddit via a Safari extension. I'm using a JSONP pattern to create the callback function and attach the new src to the script. However, it looks like there are two window namespaces, and the function that I dynamically create is not available to the context of the dynamically added script.
This link seems to detail the problem for chrome, which I'm guessing is similar to mine in Safari.
JSONP request in chrome extension, callback function doesn't exist?
Here's the code (works outside of extension):
function jsonp(url, callback) {
var callbackName = 'jsonp_callback_' + Math.round(100000 * Math.random());
window[callbackName] = function(data) {
delete window[callbackName];
callback(data);
};
console.log('about to create script');
var script = document.createElement('script');
script.src = url + (url.indexOf('?') >= 0 ? '&' : '?') + 'jsonp=' + callbackName;
document.body.appendChild(script);
console.log('script should be appended');
}
function getImages(){
jsonp('http://www.reddit.com/r/cats/.json', function(data) {
data.data.children.forEach(function(el){
console.log(el.data.url);
});
});
}
Any ideas, work arounds?
Thanks!
You're right about there being two namespaces in the same window. Injected scripts cannot access a web page's globals, and vice versa. When you initiate JSONP in an injected script, the script of the inserted <script> tag runs in the web page's namespace.
I know of two ways to work around this limitation in a Safari extension.
The probably better way is to use your extension's global page to communicate with the external API through standard XHR or jQuery Ajax methods (which are supported in global page scripts) and use messages to pass the fetched data to an injected script.
The other way is to go ahead and use JSONP from the injected script, but have the JSONP callback function add the fetched data to the page's DOM, which is accessible by the injected script. For example, you could put the data in a hidden <pre> element and then use the element's innerHTML property to retrieve it. Of course, this technique does make the content visible to the page's own scripts, so exercise caution.
I'm new to javascript. How do I anonymize Google Pagespeed?
Here is the original code:
http://pastebin.com/xRbTekDA. It works when I load the page
Here is the anonymize code: http://pastebin.com/fj9rP7FM. It shows a javascript error every time I load the page. It says "ReferenceError: runPagespeedCallbacks is not defined" because I anonymized it.
How do I anonymize that original code?
The problem you are having is the method the code is expecting to call is not in scope. So if you modify the code slightly this should rid you of the error. This code should fix the issue. http://pastebin.com/RrQ2848j
Notice i'm just returning the callback function and assigning it as a variable. There are other approachs you can take but there needs to be something in the global scope to call.
The reason for this is a script block is being created to get script and data, because an AJAX(XHR) request would violate the same-origin policy trying to reach out to google.com while executing on yourdomain.com . When the script is downloaded, it's going to expect to call a function in the global scope to pass some data into it. That function is named on the query string of the SRC attribute when creating the script block as shown here:
function runPagespeed() {
var s = document.createElement('script');
s.type = 'text/javascript';
s.async = true;
var query = [
'url=' + YN_URL,
'callback=runPagespeedCallbacks',
'key=' + API_KEY
].join('&');
s.src = API_URL + query;
document.head.insertBefore(s, null);
}
The only difference between the two is that the second is wrapped in an immediately invoked function expression (IIFE). The IIFE encapsulates the code so that the free variables are not globally visible. Normally this is a good thing, but if other services rely on that code, it will not be visible.
I'm trying to change the variable in a page using a userscript.
I know that in the source code there is a variable
var smilies = false;
In theory I should be able to change it like that:
unsafeWindow.smilies = true;
But it doesn't work. When I'm trying to alert or log the variable to the console without hijacking I get that it's undefined.
alert(unsafeWindow.smilies); // undefined !!!
EDIT: I'm using Chrome if it changes anything...
http://code.google.com/chrome/extensions/content_scripts.html says:
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.
It's about Chrome Extensions but I guess it's the same story with Userscripts too?
Thank you, Rob W. So the working code for people who need it:
var scriptText = "smilies = true;";
var rwscript = document.createElement("script");
rwscript.type = "text/javascript";
rwscript.textContent = scriptText;
document.documentElement.appendChild(rwscript);
rwscript.parentNode.removeChild(rwscript);
In Content scripts (Chrome extensions), there's a strict separation between the page's global window object, and the content script's global object.
To inject the code, a script tag has to be injected.
Overwriting a variable is straightforward.
Overwriting a variable, with the intention of preventing the variable from being overwritten requires the use of Object.defineProperty Example + notes.
The final Content script's code:
// This function is going to be stringified, and injected in the page
var code = function() {
// window is identical to the page's window, since this script is injected
Object.defineProperty(window, 'smilies', {
value: true
});
// Or simply: window.smilies = true;
};
var script = document.createElement('script');
script.textContent = '(' + code + ')()';
(document.head||document.documentElement).appendChild(script);
script.parentNode.removeChild(script);
I've got some banner zones set up on advertisespace.com - I'm trying to load the script tags using jquery so they load after the page has loaded. However its not working. Here is my code to do this.
Here is the function I use to include the script tag:
function jsinclude(file, dom) {
if (document.createElement && document.getElementsByTagName) {
if(dom=='undefined')
var dom = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.setAttribute('src', file);
script.setAttribute('charset',"utf-8");
dom.appendChild(script);
} else {
alert('Your browser can\'t deal with the DOM standard. That means it\'s old. Go fix it!');
}
}
ANd here is how I am calling the function:
$(function(){
jsinclude('http://ads.advertisespace.com/somethingsomething.js', document.getElementById('location-of-banner-1'));
jsinclude('http://ads.advertisespace.com/somethingsomething.js', document.getElementById('location-of-banner-2'));
})
The result is that the script tag is inserted in the correct place but the banners doesn't show i.e the code in the script file referred to is never executed. How can I fix this.
You tagged your question with jquery so why not make use of its advantages.
The dom is not the head element, and doing dom == 'undefined' checks whether the variable dom is equal to the string 'undefined'. You must have set that explicitly and that's probably not the case. Anyway, there is one head element so it's not necessary to pass it to the function - the function can handle that itself.
Also, checking for DOM functions is not applicable these days. We live in 2011, all browsers have these functions included.
function jsinclude(file) {
var script = $("<script>", { type: 'text/javascript',
src: file,
charset: 'utf-8' });
$('head').append(script);
}