I've written the following script to change the URL of a tab in Chrome, but can't figure out how to get it to automatically run on every page.
var nytimes = /.*nytimes\.com.*/;
var patt = /(&gwh=).*$/;
function updateUrl(tab){
if(tab.url.match(nytimes))
{
var newUrl = tab.url.replace(patt,"");
chrome.tabs.update(tab.id, {url: newurl});
}
}
chrome.tabs.onUpdated.addListener(function(tab) {updateUrl(tab);});
I put that into my background page, but it isn't working. Do I need to put the code somewhere else to get it to run?
I strongly suggest you read about content scripts. They are exactly what you're looking for but you need to understand that they have limited access to the Chrome.* API, so you'll have to use message passing in order to use your current functionality. However, by using content scripts you can probably make this simpler using one of my proposed solutions.
Solution 1
Assuming you want to send the redirect to the same URL every time, you can easily configure your extension to only run your content script on the NY Times site. For example;
Content Script: content.js
location = 'http://example.com';
Solution 2
However, if the redirect URL can vary you many want to abstract that logic in to your background page. For example;
Content Script: content.js
// Or you can pass a more specific section of the URL (e.g. `location.pathname`)
chrome.extension.sendRequest({href: location.href}, function(data) {
location = data.url;
});
Background Page: background.js
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
sendResponse({
url: getUrl(request.href) // TODO: `getUrl` method containing your logic...
});
});
Important!
Regardless of which approach you go for you will also need to request permission to run the content script on the target site in your manifest file.
Manifest: manifest.json
{
...
"content_scripts": [
{
"js": ["content.js"],
"matches": ["*://*.nytimes.com/*"],
"run_at": "document_start"
}
],
...
}
Related
Problem: I'm trying to have my webapp communicate with the background script of my chrome extension. However, when I invoke chrome.runtime.sendMessage() from my webapp, in order to send a message to the background.js script in my extension, the response callback never gets invoked. It also appears that background.js isn't receiving the message. Is what I'm trying to do possible? If so, given the information below, can anyone help me?
Background
My browser extension has a Newtab page, that I've configured to load my webapp. It does so by loading the webapp in an iframe:
<html>
<body style="margin:0px;padding:0px;overflow:hidden;">
<iframe src="https://www.example.com" frameBorder="0" style={{height:"100vh", width:"100vw", overflow: "hidden"}} height="100%" width="100%"/>
</body>
</html>
I have set the content-security-policy in manifest.json to allow this to happen:
"content_security_policy": {
"extension_pages": "object-src 'none'; child-src https://www.example.com https://example.com; frame-src https://www.example.com https://example.com; script-src 'self'"
}
I have enabled connectivity between my extension and my webapp using the externally_connectable manifest.json key:
"externally_connectable": {
"matches": [
"http://localhost:3000/*",
"https://*.example.com/*",
"https://example.com/*"
]
}
In my webapp (example.com), I have a script that tries to send a message to the background.js service worker. This script executes without any errors reported in the console. Note: this script is running on a webpage that is inside an iFrame in a page bundled with the extension.
function sendMessageToExtension() {
console.log("I got called");
if (chrome && chrome.runtime) {
console.log("Sending message to extension"); // this code is executed
chrome.runtime.sendMessage(
"extension_id",
{ type: "example.message.type"},
{includeTlsChannelId : true},
function(rsp) {
if(arguments.length === 0) {
console.log(chrome.runtime.lastError);
}
console.log(rsp.message)
} // this call back never gets invoked.
);
} else {
// TODO: need to detect this in production.
console.log("no chrome.runtime");
}
}
My background code to handle the message looks like this:
chrome.runtime.onMessageExternal.addListener(
(request, sender, sendResponse) => {
console.log("onMessageExternal invoked") // never gets called
if (request.type === "example.message.type") {
sendResponse({message: "message received by background worker."});
}
}
)
Other Notes: This doesn't appear to work even when I load the webpage by navigating to it in the URL bar, so I'm not entirely sure what's going on.
The answer to my question is yes you can! And as it turns out, the code I posted is exactly how you would do it.
I have a chrome extension, say it injects content script a.js into urls matching 'http://example.com/*'. Then when I click some page elements in example.com page, a.js would ask background to create a new tab with defined url, and programatically inject a content script b.js into the new tab, you can check the code down:
chrome.tabs.create({
url: 'http://example.com'
}, function (tab) {
var taskTab = tab.id;
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
if (tabId === taskTab) {
chrome.tabs.executeScript(taskTab, {
file: 'scripts/b.js'
});
}
});
});
The problem is, the content script b.js would get injected into the new tab for a lot times. I also tried the chrome.webNavigation, the result is the same.
So if I want the b.js got injected into the new tab for only one time? how can i make it?
By the way, the b.js is actually a remote file, I load it into page by jQuery, here to simplify the question I make it a local file.
Thanks very much.
update
I had figure out what's going on with my code.
First, it's wrong for me to wrap the onUpdated event listener inside the create callback, every time I create a new tab, close it, create it again, the event listener would got bind one more time. The more, the more.
Second, #Xan was right, I could do it by checking changeInfo.status. So the code would be:
var taskTab;
chrome.tabs.create({
url: 'http://example.com'
}, function (tab) {
taskTab = tab.id;
});
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
if (changeInfo.status === 'complete'){
if (tabId === taskTab) {
chrome.tabs.executeScript(taskTab, {
file: 'scripts/b.js'
});
}
}
});
And that b.js would only be injected for once, no matter it's a local file or a remote file loaded by jQuery.
onUpdated event can fire for a lot of reasons:
object changeInfo:
Lists the changes to the state of the tab that was updated.
string (optional) status: The status of the tab. Can be either loading or complete.
string (optional) url: The tab's URL if it has changed.
boolean (optional) pinned: The tab's new pinned state.
string (optional) favIconUrl: The tab's new favicon URL.
You might want to filter onUpdated events by those properties of changeInfo
Failsafe method: when you inject a second script, set some guard variable that is checked for undefined before you execute the rest of the script. Any other attempts to inject the script will be in the same context and will see the variable set. Kind of like C's #ifndef includes.
I'm using the example from the Google tutorial and finding it difficult to pass a simple message to the content script from the popup.
Can you provide some suggestions on how to pass a simple message and view it either in the console log or alert?
manifest.json
{
"manifest_version": 2,
"name": "msg-test",
"description": "message test",
"version": "1.0",
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"background": {
"scripts": ["background.js"],
"persistent": true
},
"content_scripts": [{
"matches": ["http://*/*","http://www.site.com/*"],
"js": ["content.js"],
"run_at": "document_end"
}],
"permissions": [
"tabs",
"http://*/*"
]
}
background.js
chrome.runtime.onConnect.addListener(function(port){
port.postMessage({greeting:"hello"});
});
content.js
var port = chrome.runtime.connect({name:"content"});
port.onMessage.addListener(function(message,sender){
if(message.greeting === "hello"){
alert(message.greeting);
}
});
popup.js
window.onload = function() {
document.getElementById('btn2').onclick = function() {
alert("button 2 was clicked");
};
document.getElementById('btn1').onclick = function() {
alert("button 1 was clicked");
};
}
*Note: In this example the content script will fire when the page matches manifest.json and the alert box will show.
First, I wouldn't message pass between your popup and your content script. I would message pass between your Background page and your content scripts. Your popup page should only be used to show some ui to interact with your app.
With that being said, I will show you the way to pass messages between your background and your content script.
In your content script:
//This line opens up a long-lived connection to your background page.
var port = chrome.runtime.connect({name:"mycontentscript"});
port.onMessage.addListener(function(message,sender){
if(message.greeting === "hello"){
alert(message.greeting);
}
});
In your background page(possibly your popup? but I don't recommend it)
chrome.runtime.onConnect.addListener(function(port){
port.postMessage({greeting:"hello"});
});
Here is the sequence of events that will take place:
Your application will inject your content script into the page
Your content script will open up a port to communicate with the background script.
Your background script will be notified that a port was open, allowing it to send a message to it, or attach a message listener to it.
In the background script or the content script, you can listen for messages by using port.onMessage.addListener(). provided that port is in scope. Using ports is much easier to grasp and allows for simple, two way communication!
Edit:
If you would like to pass messages to your background page from your popup script, use the exact same method:
var port = chrome.runtime.connect({name: "popup-port"});
port.postMessage({status:"poppedup"});
Edit 2:
To navigate your user to a new page, do this:
function navigateToPage(url){
chrome.tabs.query({url: url}, function(tabs) {
var tab = tabs[0];
return tab ? chrome.tabs.update(tab.id, {active:true}) : chrome.tabs.create({url: url});
});
}
});
What this function does is, it checks to see if there is a tab with the url you want to go to, if there is, switch to it, else, create a tab with that url and navigate to it.
For my Chrome extension, I am attempting to post selected text to a PHP webpage. A solved question on this website (Chrome Extension: how to capture selected text and send to a web service) has helped me a lot in achieving this, but I want a different way of posting the text.
Instead of XMLHttpRequest as mentioned there, I want to send a hidden JS form from the content script. This method allows me to view or change the text before importing it to the database.
The problem is to get the trigger from the background to the content script. I already have a message the other way, so using the function(response) is desired. However, outside the “sendMessage”, I can’t listen for the response.cmd. And inside the “sendMessage”, I can’t get the response.cmd to trigger a function. Is there a solution for this, other than sending an all new message from the background script?
The code I am referring to:
Background.js
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
if(request.cmd == "createSelectionMenu") {
sendResponse({cmd: "saveText"}); //Do things
}
});
Content_script.js
chrome.extension.sendMessage({ cmd: "createSelectionMenu", data: selectedText },
function(response) {
if(response.cmd == "saveText") {
createForm();
}
}
});
What I do is following:
I keep track of my opened tabs
content script:
// connect to the background script
var port = chrome.extension.connect();
background script
// a tab requests connection to the background script
chrome.extension.onConnect.addListener(function(port) {
var tabId = port.sender.tab.id;
console.log('Received request from content script', port);
// add tab when opened
if (channelTabs.indexOf(tabId) == -1) {
channelTabs.push(tabId);
}
// remove when closed/directed to another url
port.onDisconnect.addListener(function() {
channelTabs.splice(channelTabs.indexOf(tabId), 1);
});
});
Now I can notify all my registered tabs (i.e. content scripts) from my background script when a certain action happened:
var notification = { foo: 'bar' };
for(var i = 0, len = channelTabs.length; i < len; i++) {
chrome.tabs.sendMessage(channelTabs[i], notification, function(responseMessage) {
// message coming back from content script
console.log(responseMessage);
});
}
And again, on the other side in the content script, you can add a listener on these messages:
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
if (request.foo == 'bar') {
executeStuff();
// if a callback is given:
sendResponse && sendResponse('success');
}
});
It's a bit of a brainf*ck, because it's redundant at some places. But I like it best that way, because you can wrap it and make it a bit easier.
If you want to see how I am using this, see my repository on GitHub: chrome-extension-communicator.
I'm writing an extension for Chrome, and I need to upload a file from the page the user is currently on to my server to be processed, I cannot figure out how to upload the file though. I considered just passing the link to the server and having the server download the file, however if the site requires authentication this will not work. Is it possible to upload a file via a Chrome extension to my server?
I've recently developed a Chrome extension which retrieves content from a page, and sends it to the server.
The following approach was used:
File downloads: Get the src property of an <img> element, for example.
Fetch the file from the Cache - use XMLHttpRequest from the background page.
Use a Web Worker in the background page to handle the upload.
Side note, to take the checksum of the image, Crypto-JS: MD5 can be used. Example (where xhr is the XMLHttpRequest object with responseType set to arraybuffer, see Worker demo):
var md5sum = Crypto.MD5( new Uint8Array(xhr.response) );
Full example
Content script
// Example: Grab the first <img> from the document if it exists.
var img = document.images[0];
if (img) {
// Send the target of the image:
chrome.runtime.sendMessage({method: 'postUrl', url: img.src});
}
Background script (with Worker)
chrome.runtime.onMessage.addListener(function(request) {
if (request.method == 'postUrl') {
var worker = new Worker('worker.js');
worker.postMessage(request.url);
}
});
Web Worker
// Define the FormData object for the Web worker:
importScripts('xhr2-FormData.js')
// Note: In a Web worker, the global object is called "self" instead of "window"
self.onmessage = function(event) {
var resourceUrl = event.data; // From the background page
var xhr = new XMLHttpRequest();
xhr.open('GET', resourceUrl, true);
// Response type arraybuffer - XMLHttpRequest 2
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
if (xhr.status == 200) {
nextStep(xhr.response);
}
};
xhr.send();
};
function nextStep(arrayBuffer) {
var xhr = new XMLHttpRequest();
// Using FormData polyfill for Web workers!
var fd = new FormData();
fd.append('server-method', 'upload');
// The native FormData.append method ONLY takes Blobs, Files or strings
// The FormData for Web workers polyfill can also deal with array buffers
fd.append('file', arrayBuffer);
xhr.open('POST', 'http://YOUR.DOMAIN.HERE/posturl.php', true);
// Transmit the form to the server
xhr.send(fd);
};
FormData for Web workers POLYFILL
Web workers do not natively support the FormData object, used to transmit multipart/form-data forms. That's why I've written a polyfill for it. This code has to be included in the Web worker, using importScripts('xhr2-FormData.js').
The source code of the polyfill is available at https://gist.github.com/Rob--W/8b5adedd84c0d36aba64
Manifest file:
{
"name": "Rob W - Demo: Scraping images and posting data",
"version": "1.0",
"manifest_version": 2,
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["contentscript.js"]
}
],
"background": {
"scripts": ["background.js"]
},
"permissions": ["http://*/*", "https://*/*"]
}
Relevant documentation
Message passing Google Chrome Extensions
chrome.runtime.onMessage Google Chrome Extensions
XMLHttpRequest Level 2 W3c specification
FormData (XHR2) MDN
ArrayBuffer responses (XHR2) HTML5 rocks (note: arraybuffer responses are deprecated in favor of typed arrays, the polyfill has been updated to reflect this change)
The simplest solutions seems to be for your extension to send the file's URI to your server, and then your server-side code will download it from the page into the server and process it.
Create a server-side script like http://mysite.com/process.php?uri=[file's URI goes here] that will process the given file. Use AJAX to call this URL (more info at http://code.google.com/chrome/extensions/xhr.html ). The script will return the processed file, which you could then use in your extension.
You should checkout the following:
chrome.extension.sendRequest() and chrome.extension.onRequest()
You can read more about them here: http://code.google.com/chrome/extensions/messaging.html
Basically you will setup the page on the server to watch for the Chrome extension, and once they connect you will need to have a javascript that will do the upload task for you.
I haven't tested this out, but it may get you where you need to be. Also you may want to read the Long-lived connections section.
Goodluck