0

I'm building a Chrome extension that needs to see the results of a Google Groups topic search (the page URL is https://groups.google.com/forum/?fromgroups=#!searchin/opencomments-site-discussions/subject:{some topic} and I can tell if there is or is not a match by looking for a certain classname). Unfortunately, the field it's looking for doesn't get created until after the page's Javascript has run, so I can't simply look at the raw HTML from the page via

try {
  var request = new XMLHttpRequest();
  request.open("GET", url, false);
  request.send(null);
} catch (e) {
  ....
}
if (request.status == 200) {
  var rawText = request.responseText;
  ...
}

Does anyone know if it's possible to load the entire page, including executing the Javascript, without displaying the page? Been pulling my hair out on this problem.

Rob W
  • 341,306
  • 83
  • 791
  • 678
FractalBob
  • 3,225
  • 4
  • 29
  • 40

1 Answers1

1

Have a look at the offscreenTabs API (currently experimental).
After creating a tab using chrome.experimental.offscreenTabs.create, you can use chrome.tabs.sendMessage or chrome.tabs.executeScript to do whatever you want.

If you need inspiration, check this full demo.

Per specification, an Offscreen tab can only be created within a real tab (otherwise, you'll get "Error during experimental.offscreenTabs.create: No current tab found"). In my other demo, I used an options page for this purpose. If you don't want to use such a page, create a new tab using chrome.tabs.create, do whatever you want, then use window.close() to close the tab.

So, you're going to open a (hidden) tab after all (though the page itself is not visible). With the current APIs, there is no other way to achieve rendering a hidden tab without opening a tab (unless you want to render the page in an iframe at background page...). If you don't mind the actual tab from showing up in the tabs bar, use chrome.tabs.create({active:false, url:'...'}). If it's important to keep the tab truly invisible to the user, proceed with the following demo:

Demo usng the offscreenTabs API

In this example, I'm going to show an alert which contains the invisible tab's title. I can show this alert from the content script right away, but instead, I'm going to pass the data to the background page, to demonstrate the feature:

Background script (background.js):

 chrome.extension.onMessage.addListener(function(message, sender, sendResponse) {
     var tab = sender.tab;
     if (tab && tab.index === -1) { // <-- Detect offscreen tab
         if (message.method == 'title') {
             // Close helper tab:
             chrome.extension.sendMessage({method: 'finishedTab', id: tab.id});
             alert('Got title from ' + tab.url + ' :\n' + message.result);
         }
     }
 });

 chrome.browserAction.onClicked.addListener(function() {
     chrome.tabs.create({
        active: false,
        url: chrome.extension.getURL('launcher.html')
     });
 });

Content script (contentscript.js):

chrome.extension.sendMessage({
    method: 'title',
    result: document.title
});

launcher.html contains <script src="launcher.js"></script>. launcher.js:

var offscreenTabId;
// Close the tab when the stuff is done
chrome.extension.onMessage.addListener(function(message) {
    if (message.method == 'finishedTab' && message.id === offscreenTabId) {
        window.close();
    }
});
// Create invisible tab
var createProperties = {url: "https://stackoverflow.com/"};
chrome.experimental.offscreenTabs.create(createProperties, function(offscreenTab) {
    offscreenTabId = offscreenTab.id;
});

Manifest file:

{
    "name": "Get information from DOM in an invisible tab",
    "description": "See https://stackoverflow.com/q/13714002",
    "version": "1",
    "manifest_version": 2,
    "permissions": ["experimental", "<all_urls>"],
    "background": {"scripts": ["background.js"] },
    "content_scripts": [{
        "js": ["contentscript.js"],
        "matches": ["<all_urls>"]
    }],
    "browser_action": {}
}
Community
  • 1
  • 1
Rob W
  • 341,306
  • 83
  • 791
  • 678
  • Thanks for your suggestion, Rob. Unfortunately, I wasn't able to load the extension, even after enabling experimental APIs (got the warning that I needed to enable the Experimental Extensions API, which I had already done). BTW, I'm on Chrome 23.0.1271.95 m. – FractalBob Dec 05 '12 at 18:03
  • I wonder if I need to run the dev or trunk version of Chrome. I'm on stable. – FractalBob Dec 05 '12 at 18:15
  • Never mind: I just needed to install Canary. It loads fine now, but even if this solves my problem, if I can't upload it to the Chrome Web store, I won't be able to use it. Any idea when Google will legimize OffscreenTabs? – FractalBob Dec 05 '12 at 18:24
  • Seems like I spoke too soon. Although I didn't get an error with Canary when I loaded the extension, I don't see the it in the list, so apparently it didn't load. – FractalBob Dec 05 '12 at 18:28
  • @FractalBob Wait till it becomes stable. You cannot upload extensions using experimental APIs to the CWS. The extension works fine: I copied all files from the answer, and see a working extension: http://i.stack.imgur.com/6zOXH.png. Here's a zip file containing the source: http://rob.lekensteyn.nl/offscreentabs.zip – Rob W Dec 05 '12 at 18:34
  • My mistake. I neglected to relaunch the browser with the new flags. Now your extension appears. – FractalBob Dec 05 '12 at 18:35
  • I tried your idea and it didn't seem to work in my case: function commentsExist(url){ chrome.experimental.offscreenTabs.create({url: "https://groups.google.com/forum/?fromgroups=#!searchin/opencomments-site-discussions/subject:foobar.com:"}, function(tab) { var elts = document.getElementsByClassName("gwt-Label"); if (elts.length == 0) return true; return false; }); elts is in all cases 0 bytes in length, yet gwt-Label is a class in the page https://groups.google.com/forum/?fromgroups=#!searchin/opencomments-site-discussions/subject:foobar.com: – FractalBob Dec 05 '12 at 20:35
  • Sorry for the poor formatting, but it doesn't seem possible to format code properly in a comment. – FractalBob Dec 05 '12 at 20:36
  • @FractalBob `document` does **not** refer to the invisible tab in your case. It points to the document of the current execution context (probably a content script). I have added a new example to my answer which grabs the title from a http://stackoverflow.com/ through DOM. – Rob W Dec 05 '12 at 21:55
  • But what if the invisible tab is created in background.js? How do you reference the underlying DOM? – FractalBob Dec 05 '12 at 22:21
  • @FractalBob Not. If you read the fourth paragraph of my answer, you'd see that an offscreen tab cannot be created in non-tab contexts, such as the background page or popup page ("Error during experimental.offscreenTabs.create: No current tab found"). – Rob W Dec 05 '12 at 22:22
  • I missed that. So it looks like I'll need to create the invisible tab in a content script, which is fine. However, when I ran your extension, why did it display? I thought it would be truly invisible. – FractalBob Dec 05 '12 at 22:33
  • I did, but what really got my attentions was the sentence, "If it's important to keep the tab truly invisible to the user, proceed with the following demo:". It IS important that the tab remain invisible, but in the second demo, it didn't. – FractalBob Dec 06 '12 at 02:35
  • @FractalBob The original tab (http://stackoverflow.com/) **is not visible**. If you do not want any tabs at all, another option is to load the extension's page in an (invisible) iframe in the current page instead of opening a new tab. Make sure that `launcher.html` is added to `web_accessible_resources` in the manifest file. – Rob W Dec 06 '12 at 08:26
  • I saw a tab appear briefly and then disappear, but you're right; it wasn't the stackoverflow.com page. All this seems pretty complicated, considering all I want to do is determine whether or not a topic search comes up with results. – FractalBob Dec 06 '12 at 17:50
  • @FractalBob It's indeed complicated, but that's what you've asked. I've also suggested a simpler method when you don't mind that the real tab is visible for a split second. Loading and rendering is relatively expensive, so you would probably gain more by finding a specific solution to your problem (e.g. an unique characteristic in the source code). After all, the script which inserts that class name must also get the information from the page itself. – Rob W Dec 06 '12 at 17:56
  • Unfortunately, since the results come from some Javascript executing on the page, without executing it, the search pages returned are identical, regardless of whether there is a match or not. I am posting your answer as a solution, even though it's not ideal for me. I may end up implementing it if I can't find a better alternative. Thanks for all your help. – FractalBob Dec 06 '12 at 20:48