Inject functions/variables to page from a chrome extension - javascript

I'm writing a Chrome Extension that adds functionality to certain pages a user visits.
To do that, I'll need to inject a few variables and functions that the page needs to be able to call.
These variables/functions are generated in a content script.
However, since content scripts run in a secluded environment, the host page can not access it.
According to this article:
http://code.google.com/chrome/extensions/content_scripts.html#host-page-communication
it is possible for content script and host page to communicate through the DOM by adding events.
But that's a horrible way to do things, and I'd really like to see some way to inject methods/variables easily.
Is there such a possibility?
Thanks!

If it still interests anybody, I've found a solution communicating between content script and page itself through messages.
Something like this on the sending script:
window.postMessage({ type: "messageType", params: { param: "value", anotherParam: "value" } }, "*"/*required!*/);
And then on the receiving script do something like this:
window.addEventListener("message", function(event) {
// We only accept messages from ourselves
if (event.source != window)
return;
switch (event.data.type) {
case "blabla":
// do blabla
// you can use event.data.params to access the parameters sent from page.
break;
case "another blabla":
// do another blabla
break;
}
});

Here's how I coded it on my extension, http://pagexray.com/extension/
on your manifest.json
"content_scripts": [
{
"matches": ["http://*/*"],
"js": ["script.js"]
}
],
on your script.js
(function(){
var script = document.createElement('script');
script.src = "http://example.com/external.js";
script.addEventListener('load', function() { });
document.head.appendChild(script);
})();

Related

Chrome Extentions: How can I get errors from console in active tab

I want create support extention (only for my site) which will take errors from the console. So, how can I do this?
chrome.debugger gives access to some target, but how can I get all console on click event (or when error appears in the console)
try window.ErrorEvent().it should work
I found a solution, maybe it will be useful to someone:
manifest.json:
"content_scripts": [
{
"matches": ["your_site"],
"run_at": "document_end",
"js": ["content_script.js"]
}
in content_script.js inject another one script (fore example: page_script.js):
const s = document.createElement('script');
s.src = chrome.extension.getURL('page_script.js');
(document.head || document.documentElement).appendChild(s);
s.onload = function() {
s.remove();
};
we can comunicate with main js file of extension by sendMessage() method. here is documentasion
in page_scripte.js we handle errors:
let pageErr = [];
window.console.error = function () {
pageErr.push((arguments.length === 1)?arguments[0]:Array.prototype.slice.call(arguments, 0));
};
dispatch custom event in content_script.js when message from main js is sended, catch it in page_script.js, send another custom event from page_script.js to content_script.js and, at last, send data to main js
I think it's not best solution, but if anyone has any ideas, you are welcome=)
P.S.: I also tried to catch errors in content_script.js, but it doesn't work

Can I use links in chrome extensions? [duplicate]

I am writing a chrome extension which will enable transliteration for specific textboxes in facebook.
I have used the script tab to load https://www.google.com/jsapi in background.html
here is the code i have used in a content script
i tried to load using ajax and the generic way.
when i checked it said google undefined.
/*
$.ajax({
url: "https://www.google.com/jsapi",
dataType: "script",
});
*/
var script = document.createElement("script");
script.setAttribute('type','text/javascript');
script.setAttribute('src','https://www.google.com/jsapi?'+(new Date()).getTime());
document.body.appendChild(script);
$(document).ready(function()
{
alert(google)
if(window.location.href.indexOf('facebook.com'))
yes_it_is_facebook();
})
function yes_it_is_facebook()
{
// document.getElementsByName('xhpc_message_text')[0].id = 'facebook_tamil_writer_textarea';
// alert(document.getElementsByName('xhpc_message').length)
google.load("elements", "1", { packages: "transliteration" });
google.setOnLoadCallback(onLoad);
}
function onLoad()
{
var options = {
sourceLanguage:
google.elements.transliteration.LanguageCode.ENGLISH,
destinationLanguage:
[google.elements.transliteration.LanguageCode.HINDI],
shortcutKey: 'ctrl+g',
transliterationEnabled: true
};
var control = new google.elements.transliteration.TransliterationControl(options);
control.makeTransliteratable(['facebook_tamil_writer_textarea']);
}
and i have https://www.google.com/jsapi in manifest.json content script array.
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["js/jquery-1.7.2.min.js", "js/myscript.js", "https://www.google.com/jsapi"]
}
],
it showed an error
Could not load javascript https://www.google.com/jsapi for content
script
here is my manifest.json
{
"name": "Facebook Tamil Writer",
"version": "1.0",
"description": "Facebook Tamil Writer",
"browser_action": {
"default_icon": "images/stick-man1.gif",
"popup":"popup.html"
},
"background_page": "background.html",
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["js/jquery-1.7.2.min.js", "js/myscript.js", "https://www.google.com/jsapi"]
}
],
"permissions": [
"http://*/*",
"https://*/*",
"contextMenus",
"tabs"
]
}
in that i have added https://www.google.com/jsapi for your understanding and i have tested removing that also.
so how do i load that javascript into a document context . that is when ever a web page is loaded... here i specifically loading for facebook. still i have to correct the indexof condition because it is not giving the proper result but that is not the problem to this context of my question.
so please suggest me.
I don't seem to find any documentation regarding this but I think you cannot mention an http:// path in content_scripts option. A possible work around could be this:
$('head').append("<script type='text/javascript' src='http://google.com/jsapi'>");
Or loading it via ajax request as you have commented out in your code.
Secondly google.com/jsapi will have to be loaded before you can use it in your script. In your manifest you are loading your script first and then google.com/jsapi.
A friendly advice:
jQuery by default disallows caching by appending timestamp at the end of url. Since the script you are trying to load is not likely to change in short durations you can pass cache: false as an option for saving load time. Check out this page for more info.
Better yet you can bundle the script with your package so that there is no ajax request associated with your extension, that will add to the speed of your extension.
One of the biggest issues with google.load is that it cannot properly load resources after the page has fully loaded, because the API uses document.write to inject scripts/styles. To fix the issue, two methods have to be patched:
(function(g) {
var loader_d = g.loader.d,
setOnLoadCallback = g.setOnLoadCallback;
// Force not to use document.write when the document is loaded
g.loader.d = g.loader.writeLoadTag = function(a, b) {
loader_d(a, b, document.readyState === 'complete');
};
// Executes functions directly when page has loaded
g.setOnLoadCallback = function(a_listener, b) {
if (b || document.readyState !== 'complete') {
setOnLoadCallback(a_listener, b);
} else {
// When the API is not loaded yet, a TypeError with google.
// will be thrown. Not a ReferenceError, because google.* is defined
// Retry max *c* times.
var c = 5;
b = function() {
try {
a_listener();
} catch (e) {
if (e instanceof TypeError && (''+e).indexOf('google.')!=-1) {
if (c--) setTimeout(b, 2000);
}
}
};
b();
}
};
})(google);
Now, problem 2: Content Scripts run in an isolated environment: any properties of the global namespace, window, are not accessible. So, any injected APIs are not visible to your Content Script.
To fix this, see the following Stack Overflow answer: Building a Chrome Extension.
This might help with understanding Chrome's security policies
CSP
In there is says that if you attach a script tag to the page (not the popup or content script) it loads in the context of the page not your extension. And script from the extension can not talk to scripts of the page. If you look at the page script's you'll see it there but not under your extension scripts.
I discovered this while trying to inject the Google API script.
script = document.createElement('script');
script.src = "https://apis.google.com/js/client.js?onload=init";
(document.head||document.documentElement).appendChild(script);
The init function is defined in my content script. But the Goolge API script is loaded as a page script. So if I do this
var script = document.createElement('script');
script.innerText = "function init(){ alert('hi'); }";
(document.head||document.documentElement).appendChild(script);
script = document.createElement('script');
script.src = "https://apis.google.com/js/client.js?onload=init";
(document.head||document.documentElement).appendChild(script);
The injected init function is called and I see the alert 'hi'. Not sure if this helps but I figured I'd make a note of it for anyone else struggling with loading the Google apis. I'll update this answer if I figure out a way to actually get it loaded.

API WebExtensions, communicate between browser and content script

I'm trying to communicate from a web page to an extension and vice versa.
To do so, I looked at the Mozilla documentation here : https://developer.mozilla.org/fr/Add-ons/WebExtensions/Content_scripts#Communicating_with_the_web_page
And it has a simple example, but I can't make it work. On the web page script, I have this :
// page-script.js
var messenger = document.getElementById("from-page-script");
messenger.addEventListener("click", messageContentScript);
function messageContentScript() {
window.postMessage({
direction: "from-page-script",
message: "Message from the page"
}, "*");
On the content scripts page in the extension :
// content-script.js
window.addEventListener("message", function(event) {
if (event.source == window &&
event.data.direction &&
event.data.direction == "from-page-script") {
alert("Content script received message: \"" + event.data.message + "\"");
}
});
I installed the extension (as a temporary one, I uploaded my xpi file), then I used the "Debugging" method of API WebExtensions, and put a breakpoint into the listener, but whenever I call the PostMessage, the extension never seems to receive the event, the breakpoint is never triggered.
Is it possible to communicate this way between a web page and an extension ? Or is there another one ?
The problem was in the manifest of my extension. I declared my content script as a background script.
So, instead of writing this :
"background": {
"scripts": ["myscript.js"],
"persistent": true
},
You have to declare the script like this :
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["myscript.js"]
}
]
I was experiencing similar issues and the problem for me is I was calling the
window.postMessage
function from within an iframe. After I changed this to
top.window.postMessage
it started working.

chrome extension unable to load external javascript from google using content scripts and other ways

I am writing a chrome extension which will enable transliteration for specific textboxes in facebook.
I have used the script tab to load https://www.google.com/jsapi in background.html
here is the code i have used in a content script
i tried to load using ajax and the generic way.
when i checked it said google undefined.
/*
$.ajax({
url: "https://www.google.com/jsapi",
dataType: "script",
});
*/
var script = document.createElement("script");
script.setAttribute('type','text/javascript');
script.setAttribute('src','https://www.google.com/jsapi?'+(new Date()).getTime());
document.body.appendChild(script);
$(document).ready(function()
{
alert(google)
if(window.location.href.indexOf('facebook.com'))
yes_it_is_facebook();
})
function yes_it_is_facebook()
{
// document.getElementsByName('xhpc_message_text')[0].id = 'facebook_tamil_writer_textarea';
// alert(document.getElementsByName('xhpc_message').length)
google.load("elements", "1", { packages: "transliteration" });
google.setOnLoadCallback(onLoad);
}
function onLoad()
{
var options = {
sourceLanguage:
google.elements.transliteration.LanguageCode.ENGLISH,
destinationLanguage:
[google.elements.transliteration.LanguageCode.HINDI],
shortcutKey: 'ctrl+g',
transliterationEnabled: true
};
var control = new google.elements.transliteration.TransliterationControl(options);
control.makeTransliteratable(['facebook_tamil_writer_textarea']);
}
and i have https://www.google.com/jsapi in manifest.json content script array.
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["js/jquery-1.7.2.min.js", "js/myscript.js", "https://www.google.com/jsapi"]
}
],
it showed an error
Could not load javascript https://www.google.com/jsapi for content
script
here is my manifest.json
{
"name": "Facebook Tamil Writer",
"version": "1.0",
"description": "Facebook Tamil Writer",
"browser_action": {
"default_icon": "images/stick-man1.gif",
"popup":"popup.html"
},
"background_page": "background.html",
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["js/jquery-1.7.2.min.js", "js/myscript.js", "https://www.google.com/jsapi"]
}
],
"permissions": [
"http://*/*",
"https://*/*",
"contextMenus",
"tabs"
]
}
in that i have added https://www.google.com/jsapi for your understanding and i have tested removing that also.
so how do i load that javascript into a document context . that is when ever a web page is loaded... here i specifically loading for facebook. still i have to correct the indexof condition because it is not giving the proper result but that is not the problem to this context of my question.
so please suggest me.
I don't seem to find any documentation regarding this but I think you cannot mention an http:// path in content_scripts option. A possible work around could be this:
$('head').append("<script type='text/javascript' src='http://google.com/jsapi'>");
Or loading it via ajax request as you have commented out in your code.
Secondly google.com/jsapi will have to be loaded before you can use it in your script. In your manifest you are loading your script first and then google.com/jsapi.
A friendly advice:
jQuery by default disallows caching by appending timestamp at the end of url. Since the script you are trying to load is not likely to change in short durations you can pass cache: false as an option for saving load time. Check out this page for more info.
Better yet you can bundle the script with your package so that there is no ajax request associated with your extension, that will add to the speed of your extension.
One of the biggest issues with google.load is that it cannot properly load resources after the page has fully loaded, because the API uses document.write to inject scripts/styles. To fix the issue, two methods have to be patched:
(function(g) {
var loader_d = g.loader.d,
setOnLoadCallback = g.setOnLoadCallback;
// Force not to use document.write when the document is loaded
g.loader.d = g.loader.writeLoadTag = function(a, b) {
loader_d(a, b, document.readyState === 'complete');
};
// Executes functions directly when page has loaded
g.setOnLoadCallback = function(a_listener, b) {
if (b || document.readyState !== 'complete') {
setOnLoadCallback(a_listener, b);
} else {
// When the API is not loaded yet, a TypeError with google.
// will be thrown. Not a ReferenceError, because google.* is defined
// Retry max *c* times.
var c = 5;
b = function() {
try {
a_listener();
} catch (e) {
if (e instanceof TypeError && (''+e).indexOf('google.')!=-1) {
if (c--) setTimeout(b, 2000);
}
}
};
b();
}
};
})(google);
Now, problem 2: Content Scripts run in an isolated environment: any properties of the global namespace, window, are not accessible. So, any injected APIs are not visible to your Content Script.
To fix this, see the following Stack Overflow answer: Building a Chrome Extension.
This might help with understanding Chrome's security policies
CSP
In there is says that if you attach a script tag to the page (not the popup or content script) it loads in the context of the page not your extension. And script from the extension can not talk to scripts of the page. If you look at the page script's you'll see it there but not under your extension scripts.
I discovered this while trying to inject the Google API script.
script = document.createElement('script');
script.src = "https://apis.google.com/js/client.js?onload=init";
(document.head||document.documentElement).appendChild(script);
The init function is defined in my content script. But the Goolge API script is loaded as a page script. So if I do this
var script = document.createElement('script');
script.innerText = "function init(){ alert('hi'); }";
(document.head||document.documentElement).appendChild(script);
script = document.createElement('script');
script.src = "https://apis.google.com/js/client.js?onload=init";
(document.head||document.documentElement).appendChild(script);
The injected init function is called and I see the alert 'hi'. Not sure if this helps but I figured I'd make a note of it for anyone else struggling with loading the Google apis. I'll update this answer if I figure out a way to actually get it loaded.

tabs.executeScript - passing parameters and using libraries?

I am writing a Chrome extension that needs to modify pages in a specific domain according to some given parameter, which needs XSS in order to be obtained, so simply using a content script seems impossible. So, I've decided to inject the script using tabs.executeScript.
Now I need to know two things: First, how can I pass parameters to the script when using executeScript? I guess I can use messages, but isn't there a more direct way to pass the parameter while injecting the script?
Second, my script uses jQuery, so I need to include jQuery somehow. It's silly, but I'm not sure how to do it. So far, I embedded jQuery in the HTML page I was writing (for example background.html).
If you don't want to use messaging then:
chrome.tabs.executeScript(tabId, {file: "jquery.js"}, function(){
chrome.tabs.executeScript(tabId, {code: "var scriptOptions = {param1:'value1',param2:'value2'};"}, function(){
chrome.tabs.executeScript(tabId, {file: "script.js"}, function(){
//all injected
});
});
});
(jquery.js should be placed into extension folder). Script options will be available inside scriptOptions variable in the script.js.
With messaging it is just as easy:
chrome.tabs.executeScript(tabId, {file: "jquery.js"}, function(){
chrome.tabs.executeScript(tabId, {file: "script.js"}, function(){
chrome.tabs.sendMessage(tabId, {scriptOptions: {param1:'value1',param2:'value2'}}, function(){
//all injected
});
});
});
You would need to add a request listener to script.js:
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
var scriptOptions = message.scriptOptions;
console.log('param1', scriptOptions.param1);
console.log('param2', scriptOptions.param2);
doSomething(scriptOptions.param1, scriptOptions.param2);
});
Building off the direct method above, I was able to inject code into a new tab directly from the background script on my Chrome Extension. However, be advised that the code section of the executeScript command will not simply take variables, but only a string. Therefore, after experimenting, I found we need to setup the string of commands beforehand and include the variables we want. Like this:
var sendCode = 'document.getElementsByClassName("form-control n-gram")[0].value = "' + TMObj.brand + '";';
var TMUrl = "http://website.com";
chrome.tabs.create({ url: TMUrl }, function(tab){
chrome.tabs.executeScript(null, {code: sendCode});
});
});
This worked well!
Better way to include dependencies
Add the dependant libraries (and other .js files) to the background scripts in your manifest.json by:
"background": {
"scripts": [
"jquery.js",
"main.js"
]
List the dependencies before app code so that they are loaded before.
Reference: Register Background Scripts
Note: this would perform eager-loading of the scripts, instead of lazy-loading as with executeScript.

Categories

Resources