9

I know that waiting for an asynchronous method is stupid, one should use callbacks instead. But what if an third party API forces you to be synchronous?

I'm developing a Chrome Extension that will prevent the user from visiting websites that's already open in another tab. I basically need to cancel requests based on the urls in open tabs. I want to use chrome.webRequest.onBeforeRequest like this:

function onBeforeRequest(details) {
  var websiteAlreadyOpenInOtherTab;

  // Here i want to set `websiteAlreadyOpenInOtherTab` by using the `chrome.tabs`
  // API. It's asynchronous though and that's my problem. I cant return a value
  // from an asynchronous method call.

  if (websiteAlreadyOpenInOtherTab) {
    return { cancel: true };
  }
}

chrome.webRequest.onBeforeRequest.addListener(
  onBeforeRequest,
  { urls: ['<all_urls>'], types: ['main_frame'] },
  ['blocking']);

Hopefully you see my dilemma in the code above. I need to return an object based on the result of asynchronous method calls. Is it possible to achieve this?

Community
  • 1
  • 1
Sven
  • 913
  • 8
  • 16
  • Can't you listen to all `chrome.tabs` changes and have `websiteAlreadyOpenInOtherTab` already containing the right value when `onBeforeRequest` is called? – Bergi Feb 28 '13 at 00:12
  • I think you're approaching the problem incorrectly. Firstly, if things need to be asynchronous, then instead of requiring a synchronous response, work within the model and use an asynchronous callback in which if you decide the tab shouldn't be open, you close it. There is a separate API function to close tabs, you don't need to return false just from the onbeforerequest... – davin Feb 28 '13 at 00:23
  • @davin: But that'll lead to "tab flickering" or something, a completely different behaviour (and a request would be sent) – Bergi Feb 28 '13 at 00:46
  • @Bergi Yeah, almost. `onBeforeRequest` can't contain the right value (because I won't know it before `onBeforeRequest` is called. But I could maintain some kind of structure that knows about open tabs. It's a walk-around alright. @davin +1 on @Bergi's arguments. It's unacceptable to send a request and close the tab in my use case. The request should be canceled. But thanks for your suggestion anyway. :) – Sven Feb 28 '13 at 05:54

1 Answers1

6

Maybe you can solve the problem by keeping track of the tab URLs?:

  1. When the app starts, get all open tab URLs by using chrome.tabs.query
  2. Subscribe to chrome.tabs.onUpdated and chrome.tabs.onRemoved and add/remove/update the URLs when they change.

Some kind of code example based on the documentation:

var tabUrlHandler = (function() {
   // All opened urls
   var urls = {},

   queryTabsCallback = function(allTabs) {
      allTabs && allTabs.forEach(function(tab) {
          urls[tab.id] = tab.url;
      });
   },

   updateTabCallback = function(tabId, changeinfo, tab) {
       urls[tabId] = tab.url;
   },

   removeTabCallback = function(tabId, removeinfo) {
       delete urls[tabId]; 
   };

   // init
   chrome.tabs.query({ active: true }, queryTabsCallback);
   chrome.tabs.onUpdated.addListener(updateTabCallback);
   chrome.tabs.onRemoved.addListener(removeTabCallback);

   return {
     contains: function(url) {
        for (var urlId in urls) {
           if (urls[urlId] == url) {
              return true;
           }
        }

        return false;
     }
   };

}());

Now you should be able to ask the tabUrlHandler directly in your onBeforeRequestMethod:

function onBeforeRequest(details) {
  var websiteAlreadyOpenInOtherTab = tabUrlHandler.contains(details.url);

  if (websiteAlreadyOpenInOtherTab) {
    return { cancel: true };
  }
}
nekman
  • 1,919
  • 2
  • 15
  • 26