7

In the background page of my Chrome extension I need to do the following:

  1. create a tab with an html page that resides in my extension folder or that is dynamically generated by the background page;
  2. once the new tab is loaded, inject a content-script in it in order to use message passing;
  3. once the script has been injected, send a message to the new tab with some data.

This always turns into the error:
tabs.executeScript: Cannot access contents of url "". Extension manifest must request permission to access this host.
or, if the page or script are stored into .html and .js dedicated files in my extensions folder:
tabs.executeScript: Cannot access contents of url "chrome-extension://ehdjfh.../page.html". Extension manifest must request permission to access this host.

The error is generated when chrome.tabs.executeScript() is executed and the content-script injection is blocked. (I would need this structure to pass some big data to the tab that will be truncated if passed directly as string into the coded html url)

Here is a code sample that can be used to test the behavior into a working extension, just copy-paste and load the extension in Chrome:

background.js

chrome.browserAction.onClicked.addListener(function() {
    var getSampleHTML = function() {
        return 'javascript:\'<!doctype html><html>' +
            '<head></head>' +
            '<body>' +
            '<p id="myId">page created...</p>' +
            '</body>' +
            '</html>\'';
    };

    // create a new tab with an html page that resides in extension domain:
    chrome.tabs.create({'url': getSampleHTML(), 'active': false}, function(tab){
        var getSampleScript = function() {
            return 'chrome.extension.onRequest.addListener(' +
                'function(request, sender, sendResponse) {' +
                    'if(request.action == "print_data" && sender.tab.id == ' + tab.id + '){' +
                        'var p = document.getElementById("myId");' +
                        'p += "<br>data received: " + request.data;' +
                    '}' +
                '});'
                'document.getElementById("myId").innerHTML += "<br>content-script loaded...";'
        };
        // inject the content-script in the page created:
        chrome.tabs.executeScript(tab.id, {code: getSampleScript()}, function(){
            // send the data to the content-script:
            chrome.tabs.sendRequest(tab.id, {action: "print_data",
                                             data: "some long data"});
        });
    });
});

manifest.json

{
  "name": "ContentScript Injection Sample",
  "description": "",
  "version": "0.1",
  "permissions": ["tabs"],
  "background": {
    "persistent": false,
    "scripts": ["background.js"]
  },
  "browser_action": {
    "default_title": "open a new tab and inject contentscript"
  },
  "manifest_version": 2
}
guari
  • 3,715
  • 3
  • 28
  • 25
  • 1
    The error exactly tells you what's wrong. You need to declare the host permissions, see [match patterns](https://developer.chrome.com/extensions/match_patterns.html). – Rob W Aug 04 '13 at 22:06
  • 1
    If you want to share data between different pages in your own extension, you can simply use `chrome.extension.getViews` (https://developer.chrome.com/extensions/extension.html#method-getViews). Content scripts are used (and can only be used) to inject scripts to web pages. Message passing is usually used to communicate with web pages, content scripts or *other* extensions. – 方 觉 Aug 05 '13 at 02:45
  • @RobW I was wondering how can be set a match pattern in the manifest to allow the execution of a content-script injected at runtime into a local page (by putting _"chrome-extension://*/*"_ in _"permissions"_, like for external http urls, gives error when loading extension manifest, by setting it in _"matches"_ property of _"content_scripts"_ it's nonsense and gives error too since there is no _"js"_ script to inject programmatically) I suspect that my **background.js** code cannot be run with a change in **manifest.json** – guari Aug 05 '13 at 08:34
  • 1
    @guari You can't run a content script on the `chrome-extension:` scheme. See 方觉's comment for the alternative method. – Rob W Aug 05 '13 at 08:44
  • Oh thanks that's true! I was treating a local page in a tab like a 'normal' one without access to chrome.extension API.. my fault.. – guari Aug 05 '13 at 15:49
  • Anyway, in this case, the script injection isn't necessary and the script might be loaded directly with the html page passed to `chrome.tabs.create()`, the problem now is that its callback seems to be called in an arbitrary way (sometimes when the page in the new tab is loading and sometimes when is complete) and it's fired only once so `chrome.tabs.sendRequest()/sendMessage()` fails. Also calling `chrome.extension.getViews({type:'tab'})` into `chrome.tabs.create()` callback returns an empty array.. what I'm doing wrong? – guari Aug 05 '13 at 15:50

1 Answers1

6

ok, this is a possible solution, thanks to @RobW and @方觉 for pointing out my mistake (my error was to consider a local web page opened into a new created tab as normal web page, instead it has direct access to chrome.extension API like eg. a popup):

background.js

chrome.browserAction.onClicked.addListener(function() {
    // create a new tab with an html page that resides in extension domain:
    chrome.tabs.create({'url': chrome.extension.getURL("page.html"), 
                        'active': false}, function(tab){
        var selfTabId = tab.id;
        chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
            if (changeInfo.status == "complete" && tabId == selfTabId){
                // send the data to the page's script:
                var tabs = chrome.extension.getViews({type: "tab"});
                tabs[0].doSomething("some long data");
            }
        });
    });
});

page.html

<!doctype html>
<html>
    <head>
    </head>
    <body>
        <p id="myId">page created...</p>
        <script src="page.js" type="text/javascript"></script>
    </body>
</html>

page.js

var alreadyRun = false;
function doSomething(data){
    if (!alreadyRun) {
        var p = document.getElementById("myId");
        p.innerHTML += "<br>data received: " + data;
        // needed because opening eg. DevTools to inpect the page
        // will trigger both the "complete" state and the tabId conditions
        // in background.js:
        alreadyRun = true; 
    }
}
document.getElementById("myId").innerHTML += "<br>script loaded...";

what I don't like so much of this solution is that use of chrome.tabs.onUpdated.addListener() to check if the created tab is completely loaded and that in this way it's triggered for any change of state of any tabs opened (it's useless... would be great a way to run the check only for the interested tab)

guari
  • 3,715
  • 3
  • 28
  • 25