Can I use links in chrome extensions? [duplicate] - javascript

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.

Related

my first chrome extension: Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist [duplicate]

After the Chrome extension I'm working on is installed, or upgraded, the content scripts (specified in the manifest) are not re-injected so a page refresh is required to make the extension work. Is there a way to force the scripts to be injected again?
I believe I could inject them again programmatically by removing them from the manifest and then handling which pages to inject in the background page, but this is not a good solution.
I don't want to automatically refresh the user's tabs because that could lose some of their data. Safari automatically refreshes all pages when you install or upgrade an extension.
There's a way to allow a content script heavy extension to continue functioning after an upgrade, and to make it work immediately upon installation.
Install/upgrade
The install method is to simply iterate through all tabs in all windows, and inject some scripts programmatically into tabs with matching URLs.
ManifestV3
manifest.json:
"background": {"service_worker": "background.js"},
"permissions": ["scripting"],
"host_permissions": ["<all_urls>"],
These host_permissions should be the same as the content script's matches.
background.js:
chrome.runtime.onInstalled.addListener(async () => {
for (const cs of chrome.runtime.getManifest().content_scripts) {
for (const tab of await chrome.tabs.query({url: cs.matches})) {
chrome.scripting.executeScript({
target: {tabId: tab.id},
files: cs.js,
});
}
}
});
This is a simplified example that doesn't handle frames. You can use getAllFrames API and match the URLs yourself, see the documentation for matching patterns.
ManifestV2
Obviously, you have to do it in a background page or event page script declared in manifest.json:
"background": {
"scripts": ["background.js"]
},
background.js:
// Add a `manifest` property to the `chrome` object.
chrome.manifest = chrome.runtime.getManifest();
var injectIntoTab = function (tab) {
// You could iterate through the content scripts here
var scripts = chrome.manifest.content_scripts[0].js;
var i = 0, s = scripts.length;
for( ; i < s; i++ ) {
chrome.tabs.executeScript(tab.id, {
file: scripts[i]
});
}
}
// Get all windows
chrome.windows.getAll({
populate: true
}, function (windows) {
var i = 0, w = windows.length, currentWindow;
for( ; i < w; i++ ) {
currentWindow = windows[i];
var j = 0, t = currentWindow.tabs.length, currentTab;
for( ; j < t; j++ ) {
currentTab = currentWindow.tabs[j];
// Skip chrome:// and https:// pages
if( ! currentTab.url.match(/(chrome|https):\/\//gi) ) {
injectIntoTab(currentTab);
}
}
}
});
Historical trivia
In ancient Chrome 26 and earlier content scripts could restore connection to the background script. It was fixed http://crbug.com/168263 in 2013. You can see an example of this trick in the earlier revisions of this answer.
The only way to force a content script to be injected without refreshing the page is via programatic injection.
You can get all tabs and inject code into them using the chrome tabs API.
For example you can store a manifest version in local storage and every time check if the manifest version is old one (in background page), if so you can get all active tabs and inject your code programmatically, or any other solution that will make you sure that the extension is updated.
Get all tabs using:
chrome.tabs.query
and inject your code into all pages
chrome.tabs.executeScript(tabId, {file: "content_script.js"});
Try this in your background script. Many of the old methods have been deprecated now, so I have refactored the code. For my use I'm only installing single content_script file. If need you can iterate over
chrome.runtime.getManifest().content_scripts array to get all .js files.
chrome.runtime.onInstalled.addListener(installScript);
function installScript(details){
// console.log('Installing content script in all tabs.');
let params = {
currentWindow: true
};
chrome.tabs.query(params, function gotTabs(tabs){
let contentjsFile = chrome.runtime.getManifest().content_scripts[0].js[0];
for (let index = 0; index < tabs.length; index++) {
chrome.tabs.executeScript(tabs[index].id, {
file: contentjsFile
},
result => {
const lastErr = chrome.runtime.lastError;
if (lastErr) {
console.error('tab: ' + tabs[index].id + ' lastError: ' + JSON.stringify(lastErr));
}
})
}
});
}
Chrome has added a method to listen for the install or upgrade event of the extension. One can re-inject the content script when such an event occur.
https://developers.chrome.com/extensions/runtime#event-onInstalled
Due to https://bugs.chromium.org/p/chromium/issues/detail?id=168263, the connection between your content script and background script is severed. As others have mentioned, one way to get around this issue is by reinjecting a content script. A rough overview is detailed in this StackOverflow answer.
The main tricky part is that it's necessary to "destruct" your current content script before injecting a new content script. Destructing can be really tricky, so one way to reduce the amount of state you must destruct is by making a small reinjectable script, that talks to your main content script over the DOM.
can't you add ?ver=2.10 at the end of css or js you upgraded?
"content_scripts": [ {
"css": [ "css/cs.css?ver=2.10" ],
"js": [ "js/contentScript.js?ver=2.10" ],
"matches": [ "http://*/*", "https://*/*" ],
"run_at": "document_end"
} ],

Chrome Extension: Run Content Script before any Embedded Scripts run on Page

I'm trying to change some behavior of the YouTube player, by changing some variables inside of the player_api script that is embedded into the html watch page of videos.
Problem is, whatever i try, the embedded script of the player always runs before my extension adds modifications to it. Thus keeping the behavior of the player the same.
I tried setting the run_at property in my manifest to document-start, but then the script didn't run at all.
What can i do to halt the execution of that script until i make changes to it?
PS: I tried changing the script by intercepting the html call and editing the body with Charles Proxy and the behavior of the player changed as i wanted. So it know it should work, if done at the right time.
.
manifest.json
{
"manifest_version": 2,
"name": "YouFit For YouTube",
"version": "1",
"content_scripts": [{
"js": ["content.js"],
"matches": ["https://*.youtube.com/watch?*",
"https://*.youtube.com/watch?*"],
}],
"browser_action": {
"default_icon": "icon.png"
}
}
content.js
function changeBehavior() {
var scriptElements = document.getElementsByTagName('script');
for (var i = 14; i < scriptElements.length; i++) {
var curScriptBody = scriptElements[i].outerHTML;
// Find the script i'm interested in
if (curScriptBody.indexOf("var ytplayer") != -1) {
scriptElements[i].outerHTML = scriptElements[i].outerHTML.replace("<text>", "<replacement text>");
alert("Replaced");
break;
}
}
}
changeBehavior();
Did you try something like this?
content.js
var script = document.createElement('script');
script.textContent = "/* What you have in content.js right now */";
(document.head||document.documentElement).prepend(script);
Add "run_at": "document_start" to the manifest file for the content script then modify your content script such that changeBehavior is called after the current call stack is exhausted using setTimeout(fn, 0). It will run just after the HTML document is rendered but before any embedded scripts.
This solution also avoids potential issues with running unsafe inline scripts when the content security policy is set.
Content.js
function changeBehavior() {
...
}
setTimeout(() => {
changeBehavior();
}, 0);

overriding javascript alerts in chrome extension

I am trying to build a chrome extension; minimal experience in this area. I have followed Insert code into the page context using a content script to inject js code into pages using the first method.
I'm trying to build a js framework over someone else's code which relies heavily on alerts which are breaking the functionality of my overlay, so I just want to silence them for a bit--actually, I'd rather pass the messages into console.log but I'll take what I can get at this stage. So I've tried to follow JavaScript: Overriding alert() in setting up my eventual js file (nogo.js) to be injected.
nogo.js is injected but it doesn't seem to have the effect of suppressing the alerts. could it be that because the other html file is itself being initiated by a different js file that the injection is happening too slowly or out of order?
Manifest.json
"content_scripts": [
{
"matches": ["*://URL/*"],
"js": ["myscript.js"],
"run_at": "document_end",
"all_frames": true
},
{
"matches": ["*://URL/*"],
"js": ["noalerts.js"],
"run_at": "document_start",
"all_frames": true
}
],
"web_accessible_resources": ["script.js","nogo.js"]
}
myscript.js
var s = document.createElement('script');
// TODO: add "script.js" to web_accessible_resources in manifest.json
s.src = chrome.extension.getURL('script.js');
s.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
noalerts.js
var n = document.createElement('script');
// TODO: add "script.js" to web_accessible_resources in manifest.json
n.src = chrome.extension.getURL('nogo.js');
n.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(n);
nogo.js
window.alert = null;
To make alert do nothing, just paste the lines to override it:
var s = document.createElement('script');
s.innerHTML = "alert = function(){}"
document.body.appendChild(s);
The function will be redeclared with you function body. I've had to do similar things in my extensions.
By analogy you can make confirm function say "yes" everytime:
var s = document.createElement('script');
s.innerHTML = "confirm= function(){return true;}"
document.body.appendChild(s);
That can be used for the simplest cases.. For example, noone else does anything else on the page, etc.
Notice: you can try this approach by pasting the code in the console and trying to invoke alert.
Notice 2: the code can be executed in your content-script because the document it shared between its scripts and your content scripts.

Inject functions/variables to page from a chrome extension

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);
})();

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.

Categories

Resources