yuzeh yuzeh - 2 months ago 12
Javascript Question

Cross-domain XMLHttpRequest using background pages

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 hidden
iframe
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 the
iframe
is something I control, but not the same as my chrome extension) via
window.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 an
iframe
that houses the layout and logic of the page.

Answer

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 the permissions 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
    }
}
Comments