Chris_F
Chris_F

Reputation: 5557

Prevent initial webRequest prior to redirecting new tab/window only when opened by user clicking link

I am working on a Chrome extension. What I would like to accomplish is redirecting hyperlinks that are opened in a new window or new tab. I've experimented with the code below and while this does redirect the tab it does not prevent the original page request from being submitted which is something I would also like to accomplish.

chrome.webNavigation.onCreatedNavigationTarget.addListener(function(details) {
    chrome.tabs.update(details.tabId, {
        url: 'http://www.google.com/'
    });
});

I only want to redirect if the user opens a hyperlink in a new window (e.g. shift/ctrl+click, middle click, context menu, etc.). I do not want to redirect if the window, or tab, is being opened for a reason other than those.

Upvotes: 1

Views: 689

Answers (2)

Makyen
Makyen

Reputation: 33326

Redirect new tabs/windows opened by only links, but not directly

Unfortunately, without a content script in every page, you can not differentiate between a user clicking a link and other reasons for opening a new tab or window, prior to the webRequest being transmitted.

The type of webNavigation that is causing the tab or window to be opened is clearly indicated by the value of the transitionType property, in the details supplied to a webNavigation.onCommitted listener. If it was from the user clicking a link, the transitionType property will have the value of link. If the request is from a link is not information that is normally available to the background page prior to the webNavigation.onCommitted event.

Unfortunately, for what you desire, the webNavigation.onCommitted event fires after the webRequest for the page's URL is complete. Thus, without some way to know earlier that the transition is the result of a user clicking a link (e.g. using a content script), you can't know that the current transition is the result of the user clicking a link in time to choose to redirect the webRequest for the main URL of the page.

What you can do is always redirect the initial request to about:blank. Then, once you get the webNavigation.onCommitted event, you can make the choice, based on the value of the transitionType property, to change the tab's URL to the redirect URL you ultimately have in mind, or change it back to the URL which was the original intended page. This process will result in the loss of the Referer header representing the page on which the link was clicked.

Obviously, you could use your ultimate destination instead of about:blank. This may be better, but will result in a webRequest to that URL even if the tab ultimately ends up being put back to the original destination URL.

Here is code that will do what is described above:

background.js

var tabsBlockedOnce = new Set();
var tabsRedirected = new Map();
chrome.webRequest.onBeforeRequest.addListener(function(details){
    if(!tabsBlockedOnce.has(details.tabId)){
        tabsBlockedOnce.add(details.tabId);
        tabsRedirected.set(details.tabId,details.url);
        //Redirect
        return {redirectUrl:'about:blank'};
        //Block
        //return {cancel:true};
    }
},{urls:['<all_urls>'],types:['main_frame']},['blocking']);

chrome.webNavigation.onCommitted.addListener(function(details){
    if(tabsRedirected.has(details.tabId)){
        //Default is to not redirect
        let url = tabsRedirected.get(details.tabId);
        tabsRedirected.delete(details.tabId);
        if(details.transitionType === 'link'){
            //It was a link, go to where we want to redirect.
            url = 'http://www.google.com/'; 
        }
        //Send the tab where it is supposed to go
        chrome.tabs.update(details.tabId,{url:url});
    }
});

//Don't block the first request in any tab that already exists.
//  This is of primary benefit when the extension is first installed/reloaded.
chrome.tabs.query({},function(tabs){
    tabs.forEach(function(tab){
        tabsBlockedOnce.add(tab.id);
    });
});

manifest.json (partial):

"permissions": [
    "webNavigation",
    "webRequest",
    "webRequestBlocking"
],
"background": {
    "scripts": ["background.js"]
}

With a content script in every page, you could do it directly

In order to perform this operation directly (i.e. not interfere with the original webRequest if the page is not ultimately going to be redirected), you have to know that the reason for the webRequest is that a link was clicked prior to the webRequest.onBeforeRequest event fires. To get this information to your background script in time, you would have to inject a content script in every page and runtime.sendMessage() a message to your background script that a link is in the process of being clicked.

Based on testing, such a message will get to the background script prior to the webRequest.onBeforeRequest firing. Being able to do this with a content script depends on the exact timing of the asynchronous communication between the content script sending a message with runtime.sendMessage() and when the runtime.onMessage event fires vs. when the webRequest.onBeforeRequest event fires. Testing indicates that the mousedown, mouseup and click events (click is not always fired for all mouse buttons) can send a message that the background script receives prior to the webRequest.onBeforeRequest event fires. This timing is not guaranteed, but appears to work.

From a User Experience point of view this is often a bad idea

What you desire to do usurps the user's choice to use a UI interaction to specifically open a link in a new tab or window. Unless I was specifically looking for this functionality, I would find this very annoying and, almost certainly, immediately uninstall the extension. Usurping the user's agency to control their machine is something that should be done only under very limited circumstances. Unless you are in a specialized environment, what you are proposing will be contrary to most user's expectations. In addition, it may break interactions with some websites.

Upvotes: 1

Scott Marcus
Scott Marcus

Reputation: 65796

I don't know this for sure, but wouldn't you have to cancel the native event in favor of what you want to do, similar to event.preventDeafult()?

Upvotes: 0

Related Questions