Cross-domain XMLHttpRequest using background pages
Solution 1
You don't have to mess with iframes. It's possible to perform cross-domain XMLHttpRequests, using background pages. Since Chrome 13, cross-site requests can be made from the content script. However, requests can still fail if the page is served with a Content Security Policy header with a restricting connect-src
.
Another reason for choosing the nexy method over content scripts is that requests to http sites will cause a mixed content warning ("The page at https://... displayed insecure content from http://...").
Yet another reason for delegating the request to the background page is when you want to get a resource from the file://
, because a content script cannot read from file:
, unless it is running on a page at the file://
scheme.
Note
To enable cross-origin requests, you have to explicitly grant permissions to your extension using thepermissions
array in your manifest file.
Cross-site request using background script.
The content script would request the functionality from the background via the messaging API. Here is an example of a very simple way of sending and getting the response of a request.
chrome.runtime.sendMessage({
method: 'POST',
action: 'xhttp',
url: 'http://www.stackoverflow.com/search',
data: 'q=something'
}, function(responseText) {
alert(responseText);
/*Callback function to deal with the response*/
});
Background / event page:
/**
* Possible parameters for request:
* action: "xhttp" for a cross-origin HTTP request
* method: Default "GET"
* url : required, but not validated
* data : data to send in a POST request
*
* The callback function is called upon completion of the request */
chrome.runtime.onMessage.addListener(function(request, sender, callback) {
if (request.action == "xhttp") {
var xhttp = new XMLHttpRequest();
var method = request.method ? request.method.toUpperCase() : 'GET';
xhttp.onload = function() {
callback(xhttp.responseText);
};
xhttp.onerror = function() {
// Do whatever you want on error. Don't forget to invoke the
// callback to clean up the communication port.
callback();
};
xhttp.open(method, request.url, true);
if (method == 'POST') {
xhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
}
xhttp.send(request.data);
return true; // prevents the callback from being called too early on return
}
});
Remark: The messaging APIs have been renamed several times. If your target browser is not the latest Chrome version, check out this answer.
For completeness, here's a manifest file to try out my demo:
{
"name": "X-domain test",
"manifest_version": 2,
"permissions": [
"http://www.stackoverflow.com/search*"
],
"content_scripts": {
"js": ["contentscript.js"],
"matches": ["http://www.example.com/*"]
},
"background": {
"scripts": ["background.js"],
"persistent": false
}
}
Solution 2
I implemented the same thing using jquery its much simpler and it worked great too..
background.js
chrome.runtime.onMessage.addListener(function(request, sender, callback) {
if (request.action == "xhttp") {
$.ajax({
type: request.method,
url: request.url,
data: request.data,
success: function(responseText){
callback(responseText);
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
//if required, do some error handling
callback();
}
});
return true; // prevents the callback from being called too early on return
}
});
contentscript.js
chrome.runtime.sendMessage({
method: 'POST',
action: 'xhttp',
url: 'http://example-url.com/page.php',
data: "key=value"
}, function(reponseText) {
alert(responseText);
});
But make sure manifest.json file has required permissions and jquery js file
"permissions": [
"tabs", "activeTab", "http://example-url.com/*"
],
"content_scripts": [ {
"js": [ "jquery-3.1.0.min.js", "contentscript.js" ],
"matches": [ "https://example-ssl-site.com/*" ]
}],
"background": {
"scripts": [ "jquery-3.1.0.min.js", "background.js" ]
}
yuzeh
Updated on September 24, 2020Comments
-
yuzeh over 3 years
In my Chrome extension, I want to have my
options.html
page communicate with something like Google's OpenId API. In order to do this seamlessly, I have a hiddeniframe
on the options page which will pop-up the Google Accounts login page (following the OpenId interaction sequence, etc.).My issue is that I can't communicate from the options page to the
iframe
(the origin of theiframe
is something I control, but not the same as my chrome extension) viawindow.postMessage
. I was wondering if there is a quick workaround to this issue.If there isn't, I'm going to make
options.html
contain aniframe
that houses the layout and logic of the page. -
ElJeffe over 9 yearsThanks for this - it was helpful, but you may want to update it as your background event page has a couple problems. xhttp.open() must be place before the POST setRequestHeader or it will error. Also, there is no reason to set Content-length as it will just be refused.
-
ElJeffe over 9 yearsAny suggestions on adding a timeout / retry to this that would work with the async nature, which might have multiple queries running at once?
-
Rob W over 9 years@JeffG Could you be a bit more specific?
-
ElJeffe over 9 yearsSometimes the XMLHttpRequest can hang as there is no timeout set. So I was looking for a way to set a timeout, then retry up to 3 times. I just found this: stackoverflow.com/questions/1523686/timeout-xmlhttprequest which might help, but not sure about determining the number of tries within each request as it can't be an external variable due to the async nature.
-
Rob W over 9 years@JeffG If you want to have some back-and-forths between the content script and background page, use
chrome.runtime.connect
andchrome.runtime.onConnect
instead of sendMessage/onMessage. -
Roman Gaufman over 7 yearsWhen I try the first method, it shows this: extensions::runtime:143 Uncaught Error: chrome.runtime.connect() called from a webpage must specify an Extension ID (string) for its first argument -- any ideas?
-
Rob W over 7 years@RomanGaufman The code must be called from a content script, not from a web page.
-
ElJeffe about 7 years@RobW Sometimes I get this error in the background console: Uncaught Error: Attempting to use a disconnected port object at PortImpl.postMessage (extensions::messaging:60:13) at Port.publicClassPrototype.(anonymous function) [as postMessage] (extensions::utils:149:26) at responseCallback (extensions::messaging:166:16) at XMLHttpRequest.xhttp.onload (chrome-extension://.../background.js:20:13)
-
Rob W about 7 years@JeffG That may happen if you unload the page/frame before the HTTP response was fully received.
-
ElJeffe about 7 years@RobW If I understand, the act of watching the background console creates the error as I've changed focus from the extension popup for which the message is sent. Is that correct?
-
Rob W about 7 years@JeffG If you call
chrome.runtime.sendMessage
from the popup and then inspect the background page (causing the popup to close) before the HTTP response was received, thensendResponse
will indeed fail as the popup context has closed. -
user1254723 about 5 yearsThis example would not work for me when matches was set to only execute on example.com. It pops up with NULL instead of the expected search results html. I changed matches in my manifest to all web pages as such and it works perfectly !! BUT I HAVE NO IDEA WHY. IT SHOULD NOT BE THIS WAY. Here is my mod for anyone trying to get this to actually work: "matches": [ "http://*/*", "https://*/*" ]