I've created a chrome extension that consists of manifest.json, content.js and background.js. in content.js, I'm extracting the current tab's URL and in background.js, I'm opening a new tab. what I want to do, which doesn't work is to pass the URL from content and append it to the URL that I'm calling in background.
content.js:
chrome.extension.onMessage.addListener(function(request, sender, sendResponse)
{
if(request.greeting=="gimmieyodatas")
{
var output ="URL=";
//check for the character '?' for any parameters in the URL
var paramIndex = document.URL.indexOf("?");
//if found, eliminate the parameters in the URL
if (paramIndex > -1)
{
output += document.URL.substring(0, paramIndex);
};
sendResponse({data: output});
}
else{
sendResponse({});
}
});
background.js:
var output2;
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendMessage(tab.id, {greeting:"gimmieyodatas"}, function(response) {
output2 = response.data;
});
});
chrome.browserAction.onClicked.addListener(function() {
chrome.tabs.create({url: "http://www.google.com?" + output2}, function(tab) {
chrome.tabs.executeScript(tab.id, {file: "content.js"}, function() {
sendMessage();
});
});
});
When I run the extension from an open tab, it opens google on a new tab, but it doesn't append the current tab's URL in google URL, meaning the 'output' data does not get passed to the background.js. What am I doing wrong?
The problem is that you are not telling the background page to send a message when a new tab is opened. The call to chrome.tabs.getSelected only happens once when the extension is first run -- it does not happen every time a new tab is opened.
You're on the right track by using the background page as an intermediary between the two content pages, but I suggest a different approach:
Load the content script every time a new tab is opened, via the manifest file:
"content_scripts": [
{
"matches" : [
"<all_urls>"
],
"js" : [
"content.js"
]
}
],
Use a much simpler content script that just sends a message to the background with the current URL page as soon as it loads:
(content.js)
var paramIndex = document.URL.indexOf('?');
if (paramIndex > -1) {
chrome.runtime.sendMessage({output2: 'URL=' + document.URL.substring(0, paramIndex)});
}
When the background page receives the message it saves the URL to a global variable:
(background.js)
var output2;
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
output2 = request.output2;
});
You can then load that URL when the action button is clicked:
(background.js)
chrome.browserAction.onClicked.addListener(function() {
chrome.tabs.create({url: "http://www.google.com?" + output2});
});
Related
I have a function in the context.js which loads a panel and sends a message to panel.js at the last. The panel.js function updates the ui on receiving that msg. But it is not working for the first click i.e. it just loads normal ui, not the one that is expected that is updated one after the msg is received. while debugging it works fine.
manifest.json
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_scripts": [{
"all_frames": false,
"matches": ["<all_urls>"],
"js":["context.js"]
}],
"permissions": ["activeTab","<all_urls>", "storage","tabs"],
"web_accessible_resources":
"panel.html",
"panel.js"
]
context.js - code
fillUI (){
var iframeNode = document.createElement('iframe');
iframeNode.id = "panel"
iframeNode.style.height = "100%";
iframeNode.style.width = "400px";
iframeNode.style.position = "fixed";
iframeNode.style.top = "0px";
iframeNode.style.left = "0px";
iframeNode.style.zIndex = "9000000000000000000";
iframeNode.frameBorder = "none";
iframeNode.src = chrome.extension.getURL("panel.html")
document.body.appendChild(iframeNode);
var dataForUI = "some string data"
chrome.runtime.sendMessage({action: "update UI", results: dataForUI},
(response)=> {
console.log(response.message)
})
}
}
panel.js - code
var handleRequest = function(request, sender, cb) {
console.log(request.results)
if (request.action === 'update Not UI') {
//do something
} else if (request.action === 'update UI') {
document.getElementById("displayContent").value = request.results
}
};
chrome.runtime.onMessage.addListener(handleRequest);
background.js
chrome.runtime.onMessage.addListener((request,sender,sendResponse) => {
chrome.tabs.sendMessage(sender.tab.id,request,function(response){
console.log(response)`
});
});
panel.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="panel.css" />
</head>
<body>
<textarea id="displayContent" rows="10" cols="40"></textarea>
</body>
</html>
Any suggestions on what I am doing wrong or what can I do instead?
An iframe with a real URL loads asynchronously so its code runs after the embedding code finishes - hence, your message is sent too early and is lost. The URL in your case points to an extension resource so it's a real URL. For reference, a synchronously loading iframe would have a dummy URL e.g. no src at all (or an empty string) or it would be something like about:blank or javascript:/*some code here*/, possibly srcdoc as well.
Solution 1: send a message in iframe's onload event
Possible disadvantage: all extension frames in all tabs will receive it, including the background script and any other open extension pages such the popup, options, if they also have an onMessage listener.
iframeNode.onload = () => {
chrome.runtime.sendMessage('foo', res => { console.log(res); });
};
document.body.appendChild(iframeNode);
Solution 2: let iframe send a message to its embedder
Possible disadvantage: wrong data may be sent in case you add several such extension frames in one tab and for example the 2nd one loads earlier than the 1st one due to a bug or an optimization in the browser - in this case you may have to use direct DOM messaging (solution 3).
iframe script (panel.js):
chrome.tabs.getCurrent(ownTab => {
chrome.tabs.sendMessage(ownTab.id, 'getData', data => {
console.log('frame got data');
// process data here
});
});
content script (context.js):
document.body.appendChild(iframeNode);
chrome.runtime.onMessage.addListener(
function onMessage(msg, sender, sendResponse) {
if (msg === 'getData') {
chrome.runtime.onMessage.removeListener(onMessage)
sendResponse({ action: 'update UI', results: 'foo' });
}
});
Solution 3: direct messaging via postMessage
Use in case of multiple extension frames in one tab.
Disadvantage: no way to tell if the message was forged by the page or by another extension's content script.
The iframe script declares a one-time listener for message event:
window.addEventListener('message', function onMessage(e) {
if (typeof e.data === 'string' && e.data.startsWith(chrome.runtime.id)) {
window.removeEventListener('message', onMessage);
const data = JSON.parse(e.data.slice(chrome.runtime.id.length));
// process data here
}
});
Then, additionally, use one of the following:
if content script is the initiator
iframeNode.onload = () => {
iframeNode.contentWindow.postMessage(
chrome.runtime.id + JSON.stringify({foo: 'data'}), '*');
};
document.body.appendChild(iframeNode);
if iframe is the initiator
iframe script:
parent.postMessage('getData', '*');
content script:
document.body.appendChild(iframeNode);
window.addEventListener('message', function onMessage(e) {
if (e.source === iframeNode) {
window.removeEventListener('message', onMessage);
e.source.postMessage(chrome.runtime.id + JSON.stringify({foo: 'data'}), '*');
}
});
one possible way that worked for me is by using functionality in setTimeout() method.
in context.js
setTimeout(() => {
chrome.runtime.sendMessage({action: "update UI", results: dataForUI},
(response)=> {
console.log(response.message)
}
)
}, 100);
But I am not sure if this is the best way.
I want to run a callback function from content script after tab loading new page .
Here is my code :
content_script.js
chrome.runtime.onMessage.addListener(function(request, sender, callback) {
if (request.id == "content_script") {
// open google.com
chrome.runtime.sendMessage({
"id" : "openUrl",
"url" : "https://google.com"
}, function(response) {
});
// call background script
// go to the claim code page
chrome.runtime.sendMessage({
"id" : "background"
}, function() {
alert("test");
});
}
});
background.js
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
if (msg.id == "openUrl") {
var tabId = sender.tab.id;
var url = msg.url;
openUrl(tabId, url);
} else if (msg.id == "background") {
setTimeout(function() {
sendResponse();
}, 5000);
}
});
function openUrl(tabId, url) {
chrome.tabs.update(tabId, {
url : url,
active : false
}, function() {
console.log("open tab url callback");
});
};
I also uploaded the source code to google drive, you can download it using the bellow link :
https://drive.google.com/open?id=15zSn40z4zYkvCZ8B-gclzixvy6b0C8Zr
as you can see the alert test don't show !
However if I remove the code which open new url , then alert ("test") appear !
I am not sure why ! but it looks like javascript lost the reference to the call back function when I open new url .
How can I solve the problem ? what's the correct way ?
Thank you
The sendResponse function becomes invalid after the message callback returns, unless you return true from the event listener to indicate you wish to send a response asynchronously. (https://developer.chrome.com/extensions/runtime#event-onMessage)
Add return true; in background.js to make this handler asynchronous.
Now you get an error Attempting to use a disconnected port object in the sendResponse(); call of background.js, and yes, that's a result of the page navigating away and losing the context that the content script was running in.
There's no way to make this work: The context in which you wanted to call alert() simply doesn't exist anymore.
Try using chrome.tabs.sendMessage instead. But this means you have to set up the listener at the top level, and not inside of any callback. Message passing is hard.
Let's say there are two websites: x.com and y.com. Here is what I have done so far:
When a button is clicked on x.com, send a message to a background script.
The background script opens a new tab to y.com.
However, now that the tab to y.com has opened, how can I read from the DOM from y.com and use that info in the content script that is running on x.com?
Here is my code so far:
content.js (Content script that runs for x.com)
var btn = document.querySelector('my-btn')
// Send message when btn is clicked
btn.addEventListener('click', message)
function message() {
chrome.runtime.sendMessage({ message: 'Get Y data' }, null, function() {
// I want to use data from `y.com` here somehow
})
}
background.js
chrome.runtime.onMessage.addListener(function(response, sender, sendResponse) {
if (response.message !== 'Get Y data') {
return
}
var url = '...'
chrome.tabs.create({ active: true, url: url }, function (tab) {
// How do I read DOM from this new `tab` and send it back to content script?
})
})
I'm trying to create a chrome extension that scrapes some content from a particular website and then opens a new tab and does stuff with the scraped data.
Below is a test I made to see how I might do this. Unfortunately I can't seem to execute the newtab-script.js file as I get this error:
Unchecked runtime.lastError while running tabs.executeScript: Cannot
access contents of url
"chrome-extension://FAKEIDgfdsgfdsgfdsgdsgfdsgFAKEID/newpage.html".
Extension manifest must request permission to access this host.
at Object.callback (chrome-extension://FAKEIDgfdsgfdsgfdsgdsgfdsgFAKEID/background.js:43:25)
websitescrape.js
var button = document.createElement("button");
button.classList.add("web-scrape");
button.innerHTML = "scrape web";
document.querySelector('.placeIWantToPutButton').appendChild(button);
button.addEventListener('click', scrapeData);
function scrapeData(){
//do website scraping stuff here...
var fakedata = [{test:"data1"},{test:"data2"}];
//send scraped data to background.js
chrome.runtime.sendMessage({setdata: fakedata}, function(tab){
//callback
});
}
background.js
var dataTempStorage = [];
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
if (request.setdata) {
dataTempStorage = request.setdata;
chrome.tabs.create({
'url': chrome.extension.getURL('newpage.html')
}, function(tab) {
chrome.tabs.executeScript(tab.id, {
file:chrome.extension.getURL("newtab-script.js")});
});
}
if (request == "getdata") {
sendResponse({data: dataTempStorage});
}
});
newtab-script.js
chrome.runtime.sendMessage("getdata", function (response) {
doStuff(response.data);
});
function doStuff(){
//Do staff on newpage.html with data scraped from original page
}
newpage.html
// page ready to be filled with awesome content!
Cause: content scripts can't be injected into extension pages with chrome-extension:// scheme.
Solution: since you have control over that html page just reference the content script file explicitly.
newpage.html:
<script src="newtab-script.js"></script>
</body>
</html>
And don't use executeScript.
I am having issues with getting access to the Chrome's tab ID. I can fetch it, but it remains inside the extension and I cannot use it outside the extension, despite the fact that I was able to record keyboard events outside the extension.
Here's what I'm trying to do:
User navigates to a tab and fetches the tabId with a 'capture' button
The tabId is stored as a global variable
User then can navigate to any other tab inside his browser and from there with a key combination the user can reload the captured tab at any given moment by pressing CTRL + SHIFT simultaneously
extension.html
<!DOCTYPE html>
<html>
<head>
<title>Extension</title>
<style>
body {
min-width: 357px;
overflow-x: hidden;
}
</style>
<p>Step 1. Navigate to tab you want to refresh and click the 'capture' button</p>
<button type="button" id="capture">Capture!</button>
<p id="page"></p>
<p>Step 2. Now you can reload that tab from anywhere by pressing CTRL+SHIFT simultaneously</p>
</div>
<script src="contentscript.js"></script>
</head>
<body>
</body>
</html>
manifest.json
{
"manifest_version": 2,
"name": "Extension",
"description": "This extension allows you to trigger page refresh on key combinations from anywhere",
"version": "1.0",
"content_scripts": [
{
"matches": ["http://*/*","https://*/*"],
"run_at": "document_end",
"js": ["contentscript.js"]
}
],
"browser_action": {
"default_icon": "icon.png",
"default_popup": "extension.html"
},
"web_accessible_resources": ["script.js"],
"permissions": [
"tabs"
],
}
contentscript.js
var s = document.createElement('script');
s.src = chrome.extension.getURL("script.js");
(document.head||document.documentElement).appendChild(s);
s.parentNode.removeChild(s);
script.js
'use strict';
var isCtrl = false;
var tabId = 0;
document.onkeyup=function(e){
if(e.which === 17) {
isCtrl=false;
}
};
document.onkeydown=function(e){
if(e.which === 17) {
isCtrl=true;
}
if(e.which === 16 && isCtrl === true) {
/* the code below will execute when CTRL + SHIFT are pressed */
/* end of code */
return false;
}
};
document.getElementById('capture').onclick = function(){
chrome.tabs.getSelected(null, function(tab) {
tabId = tab.id;
document.getElementById('page').innerText = tab.id;
});
};
I thought this would be the solution, but it didn't work:
/* the code below will execute when CTRL + SHIFT are pressed */
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.reload(tabId);
});
/* end of code */
Having var tabId = 0; as a global variable seems pointless so I thought message passing should be the solution, but the problem with that is that I don't understand how I should implement it.
Any suggestions on how to refresh the tab from anywhere based on its ID?
Your contentscript.js is just a file with programmatic instructions written in JavaScript. Those instructions are interpreted as fresh and new each time they are loaded into a particular execution environment. Your popup and your content scripts are separate execution environments.
The contentscript.js file itself does not store state. When contentscript.js is loaded in a content script environment, the content script execution environment has no idea where else contentscript.js has been included.
The correct pattern to use here would be to have a background page maintain state and remember the tab ID of the last captured tab. The popup would use message passing to send the current tab ID to the background page (using chrome.runtime.sendMessage in the popup and chrome.runtime.onMessage in the background page). Then, later, the content script would send a message to the background page when it saw a Ctrl+Shift press, and the background page would invoke chrome.tabs.reload(tabId).
Inside extension.html, instead of your current <script> tag:
document.getElementById("capture").onclick = function() {
chrome.tabs.getSelected(null, function(tab) {
tabId = tab.id;
// send a request to the background page to store a new tabId
chrome.runtime.sendMessage({type:"new tabid", tabid:tabId});
});
};
Inside contentscript.js:
/* the code below will execute when CTRL + SHIFT are pressed */
// signal to the background page that it's time to refresh
chrome.runtime.sendMessage({type:"refresh"});
/* end of code */
background.js:
// maintaining state in the background
var tabId = null;
// listening for new tabIds and refresh requests
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
// if this is a store request, save the tabid
if(request.type == "new tabid") {
tabId = request.tabid;
}
// if this is a refresh request, refresh the tab if it has been set
else if(request.type == "refresh" && tabId !== null) {
chrome.tabs.reload(tabId);
}
});