Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Block the navigation indicated by an onBeforeNavigate event in a Chrome extension, but not non-navigation requests

I want to restrict the browser to within a set of URLs. I'm using:

chrome.webNavigation.onBeforeNavigate.addListener(functon(details){
    if (notAllowed(details.url)) {
         // Do something to stop navigation
    }
});

I know that I can cancel chrome.webRequest.onBeforeRequest. But, I don't want to block requests, like XHR or any other. I want this filter to be applied only for navigation.

For the user, it should looks like, the link (e.g. <a href="http://...">foo</a>) click event was stopped.

like image 989
dkiselev Avatar asked Sep 05 '16 15:09

dkiselev


2 Answers

The following extension adds a listener to webNavigation.onCompleted which is used to remember, indexed by tabId, both the most recent URL in frameId==0 for which the event is fired, and the prior URL.

A listener is added to webNavigation.onBeforeNavigate which watches for matching URLs, in this case, stackexchange.com. If the URL matches, the tab URL is updated, via tabs.update, to navigate back to the last URL for which a webNavigation.onCompleted event was fired.

If the onBeforeNavigate event is for a frameId other than 0, then the tab is navigated to the previous URL for which a onCompleted event was fired for frameId==0. If the prior URL was not used, then we could get into a loop where the current URL is repeatedly re-loaded due to the URL in one of its frames matching the URL we are blocking. A better way to handle this would be to inject a content script to change the src attribute for the frame. We would then need to handle frames within frames.

blockNavigation.js:

//Remember tab URLs
var tabsInfo = {};
function completedLoadingUrlInTab(details) {
    //console.log('details:',details);
    //We have completed loading a URL.
    createTabRecordIfNeeded(details.tabId);
    if(details.frameId !== 0){
        //Only record inforamtion for the main frame
        return;
    }
    //Remember the newUrl so we can check against it the next time
    //  an event is fired.
    tabsInfo[details.tabId].priorCompleteUrl = tabsInfo[details.tabId].completeUrl;
    tabsInfo[details.tabId].completeUrl = details.url;
}

function InfoForTab(_url,_priorUrl) {
    this.completeUrl = (typeof _url !== 'string') ? "" : _url;
    this.priorCompleteUrl = (typeof _priorUrl !== 'string') ? "" : _priorUrl;
}

function createTabRecordIfNeeded(tabId) {
    if(!tabsInfo.hasOwnProperty(tabId) || typeof tabsInfo[tabId] !== 'object') {
        //This is the first time we have encountered this tab.
        //Create an object to hold the collected info for the tab.
        tabsInfo[tabId] = new InfoForTab();
    }
}


//Block URLs
function blockUrlIfMatch(details){
    createTabRecordIfNeeded(details.tabId);
    if(/^[^:/]+:\/\/[^/]*stackexchange\.[^/.]+\//.test(details.url)){
        //Block this URL by navigating to the already current URL
        console.log('Blocking URL:',details.url);
        console.log('Returning to URL:',tabsInfo[details.tabId].completeUrl);
        if(details.frameId !==0){
            //This navigation is in a subframe. We currently handle that  by
            //  navigating to the page prior to the current one.
            //  Probably should handle this by changing the src of the frame.
            //  This would require injecting a content script to change the src.
            //  Would also need to handle frames within frames. 
            //Must navigate to priorCmpleteUrl as we can not load the current one.
            tabsInfo[details.tabId].completeUrl = tabsInfo[details.tabId].priorCompleteUrl;
        }
        var urlToUse = tabsInfo[details.tabId].completeUrl;
        urlToUse = (typeof urlToUse === 'string') ? urlToUse : '';
        chrome.tabs.update(details.tabId,{url: urlToUse},function(tab){
            if(chrome.runtime.lastError){
                if(chrome.runtime.lastError.message.indexOf('No tab with id:') > -1){
                    //Chrome is probably loading a page in a tab which it is expecting to
                    //  swap out with a current tab.  Need to decide how to handle this
                    //  case.
                    //For now just output the error message
                    console.log('Error:',chrome.runtime.lastError.message)
                } else {
                    console.log('Error:',chrome.runtime.lastError.message)
                }
            }
        });
        //Notify the user URL was blocked.
        notifyOfBlockedUrl(details.url);
    }
}

function notifyOfBlockedUrl(url){
    //This will fail if you have not provided an icon.
    chrome.notifications.create({
        type: 'basic',
        iconUrl: 'blockedUrl.png',
        title:'Blocked URL',
        message:url
    });
}


//Startup
chrome.webNavigation.onCompleted.addListener(completedLoadingUrlInTab);
chrome.webNavigation.onBeforeNavigate.addListener(blockUrlIfMatch);

//Get the URLs for all current tabs when add-on is loaded.
//Block any currently matching URLs.  Does not check for URLs in frames.
chrome.tabs.query({},tabs => {
    tabs.forEach(tab => {
        createTabRecordIfNeeded(tab.id);
        tabsInfo[tab.id].completeUrl = tab.url;
        blockUrlIfMatch({
            tabId : tab.id,
            frameId : 1, //use 1. This will result in going to '' at this time.
            url : tab.url
        });

    });
});

manifest.json:

{
    "description": "Watch webNavigation events and block matching URLs",
    "manifest_version": 2,
    "name": "webNavigation based block navigation to matched URLs",
    "version": "0.1",
    "permissions": [
        "notifications",
        "webNavigation",
        "tabs"
    ],
    "background": {
        "scripts": ["blockNavigation.js"]
    }
}
like image 153
Makyen Avatar answered Oct 12 '22 11:10

Makyen


It is possible to prevent navigation altogether. Use redirectURL and set a link which generates 204 (no content) response.

chrome.webRequest.onBeforeRequest.addListener(

  function(details) {

    //just don't navigate at all if the requested url is example.com
    if (details.url.indexOf("://example.com/") != -1) {

      return {redirectUrl: 'http://google.com/gen_204'};

    } else {

      return { cancel: false };

    }

  },
    { urls: ["<all_urls>"] },
    ["blocking"]
  );
like image 3
Maciej Krawczyk Avatar answered Oct 12 '22 10:10

Maciej Krawczyk