I have a Chrome extension that loads/injects a contentscript.js This script appends html and css to the webpage.
Currently I have the html and css written into my contentscript. What I would like is for the css itself, as well as the body of my new elements to be in separate document, mostly so it looks better than having html and css as text in a .js document.
Then in the contentscript I would do something like
node = the_html_doc.html
document.getElementsByTagName("body")[0].appendChild(node);
But how do I access such a separate document from my contentscript? It needs to be available in all tabs (I use the activeTab permission), not just the url in the manifest "matches".
A better solution would be to use a background script. What I did was to make a file called background.html that would store nothing but templates. I then had my background script (background.js) setup to communicate with my content script (content.js). The content script would send a message to the background script with a command indicating it wants a template. Leveraging jQuery, i can easily select and return a template to my content script which can then be injected into the page.
Here is the code (bits an pieces):
background.html
<div id="template-1"></div>
<div id="template-2"></div>
...
background.js
chrome.runtime.onMessage.addListener(function(cmd, sender, sendResponse){
c = JSON.parse(cmd);
if(c.cmd == "GET_TEMPLATE"){
//respond with the template referenced by c.selector
sendResponse($(c.selector).outerHTML);
}
});
content.js
var command = {cmd:"GET_TEMPLATE", selector:"#template-1"};
chrome.runtime.sendMessage(JSON.stringify(command), function(response) {
//and here you should get your template
console.log(response);
//you can start using jQuery like $(response) to alter it
});
This method has worked flawlessly for me. I not only use commands here but I use them everywhere now, it works well with message passing.
You might be able to use the web_accessible_resources manifest setting, then in your content script you can just inject a link element that points to the chrome.extension.getURL(<filename>) value for the CSS, and inject a script element of type text/html with an id, and then fetch the contents of that node and use those for your appendChild call.
Related
I've seen this answer:
Chrome extension: Insert fixed div as UI
which inserts a div into the current web page.
However, it gets pretty tiresome to be doing this all the time:
"div.id = 'myDivId';" +
"div.style.position = 'fixed';" +
"div.style.top = '50%';" +
"div.style.left = '50%';" +
Is it possible to insert a fully-formed HTML template instead (i.e. that you include as template.html in the extension)?
There are several methods.
Expose resources to content script via web_accessible_resources
Then the content script can use them almost directly (by using chrome.runtime.getURL) in images, videos, iframes, or read the contents of html files using fetch() and XMLHttpRequest.
If your UI is complex then go with the iframe approach - its contents will be a full-fledged extension environment like a browser_action popup with its own chrome://extension URL.
The downside is that your resources may be read by any site directly and the sites may easily detect the presence of your extension.
Use messaging and read the resources via the background script
content.js:
chrome.runtime.sendMessage({cmd: 'getTemplate'}, html => {
document.body.insertAdjacentHTML('beforeend', html);
});
background.js:
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.cmd === 'getTemplate') {
fetch('/templates/foo.html').then(r => r.text()).then(sendResponse);
return true;
}
});
Use you compiler/bundler
There are plugins for major bundlers/compilers that allow to inline the file contents in another one so you can include a HTML template in your compiled content script as a literal string.
In the background.html:
chrome.tabs.query({active:true, currentWindow:true},function(tabs){
chrome.tabs.sendMessage(tabs[0].id,"target.js");
});
In the content.js:
chrome.extension.onMessage.addListener(function(msg,sender,sendResponse){
if (msg == "target.js"){
extensionID = sender.id;
}
});
However, it doesn't work;
Uncaught TypeError: Cannot read property 'onMessage' of undefined
How to make it right?
You said "content.js is a content script and it is injected to the current tab by another content script.".
Herein lies your problem. Injected scripts are executed in the context of a page (as if they were created by the page itself), and therefore they have no access to any of the Chrome extension APIs.
You could use a custom event or postMessage to communicate between the page and your content script, which in turn communicates with the background page.
For instance, see this basic example:
// Injected script script
addEventListener('message', function(event) {
if (event.data && event.data.extensionMessage) {
alert(event.data.extensionMessage);
}
});
// Content script which injects the script:
chrome.extension.onMessage.addListener(function(message) {
postMessage({extensionMessage: message}, '*');
});
I think that you want to use content.js as a real content script though, rather than an injected script. If you want to know the differences between injected scripts and content scripts, see Chrome extension code vs Content scripts vs Injected scripts.
It looks like you're trying to do some action to the current tab. Are you sure you need a message to do that? I'm assuming that background.html is your extension's background page and content.js is your content script. Now I skip the background.html page and simply run a javascript file for my background page. My manifest.json page would look something like this.
"background": {
"scripts": [ "js/background.js"]
},
In that page, I add message listeners to my background page since that's the only way I know to interact with the background.js file. In your case however, it's the background page that doing the action therefore you could use the chrome.tab method to modify the current tab directly. Instead of onMessage, you may try something executeScript where the InjectDetails is the javascript or whatever you want to execute.
chrome.tabs.query({active:true, currentWindow:true},function(tabs){
chrome.tabs.executeScript(tabs[0].id, InjectDetails);
});
See more on that HERE.
I am writing a chrome extension that is a 'content script'
I want to inject a google map on to a webpage.
Problem:
It appears that i have no way to add functions on to the window object, thus i cannot define a callback function for googlemaps to call when it loads.
How do people usually go about mucking with the window?
--
someone on the interwebs suggested i do this:
You can do this easily with a JavaScript URL: window.location =
"javascript:obj.funcvar = function() {}; void(0);"
but when i did this i got an access denied error. it seems like a lot of search results about this problem are outdated.
Content scripts have a separate JavaScript execution ennvironment from the page they run on, so they cannot alter JS variables in the page itself. However, the content script shares the DOM with the page, so you can inject a <script> tag into the DOM which will be loaded and run in the actual page's execution environment.
I'd like to have my background page watch the URL and call chrome.tabs.executeScript on certain URLs. What API should I call to watch the URL in such a manner?
chrome.tabs.onUpdated.addListener can be used to detect tab loads. This will not detect navigation within frames though:
chrome.tabs.onUpdated.addListener(function(tabId, info, tab) {
if (info.status === 'complete' && /some_reg_ex_pattern/.test(tab.url)) {
// ...
}
});
For this purpose, you'd better use a content script, with all_frames set to true. Within the content script, you can inject code using the methods as described in this answer. Then use the page's location object to filter URLs.
The problem now is that if I add some DOM elements I must put all JS inside tags creating huge mess in code for example:
<div onclick='//js monstrous oneliner with function declarations and so on..
//that must be repeated many times multipcating the whole mess..
'>
Here some pure html thing.
</div>
So can I add my custom JS file into rendered document in the same way as I'm able to add DOM elements ?
You can inject a JavaScript file from your extension into the page. Then, the onclick handler can refer to that directly.
Assume you have a script in your extension:
// myfile.js
function hello() {
alert('hello world');
}
In your content script, make the script tag:
var script = document.createElement('script');
script.src = chrome.extension.getURL('/myfile.js');
Now, you can write your HTML like this:
<div onclick="hello">…</div>
Note that because the content script doesn't run in the same JavaScript context as the page, doing this won't work from your content extension.
var div = document.createElement('div');
div.onclick = someFuncInMyExtension;
Yes, it's quite simple: see this document just to have an idea http://blog.jeffhaynie.us/cross-browser-way-to-dynamically-load-javascript.html