Problem Block Url Manifest V3

865 views
Skip to first unread message

angel molero

unread,
Jun 8, 2023, 5:35:53 AM6/8/23
to Chromium Extensions

Hello everyone,

I am working on a Chrome extension with Manifest V3, which aims to limit the number of tabs open on a specific domain to improve the performance of a web application.

My current solution involves:
  1. Counting the number of tabs open on the specific domain using chrome.tabs.query.
  2. Using chrome.storage.session to keep track of whether we've hit the tab limit.
  3. Using chrome.webRequest.onBeforeRequest to intercept and block requests that are attempting to open a new tab once the limit has been reached. Applying blocking rules using chrome.declarativeNetRequest.updateDynamicRules.
Here is a code example:
chrome.webRequest.onBeforeRequest.addListener(async function (details) {
    if (details.url.includes("dominio")) {
        const tabs = await chrome.tabs.query({ url: ["dominio"] });
        const max_tabs_open = tabs.length > 2;
        const flagBlockUrl = await getFlagBlockUrl();

        if (max_tabs_open && !flagBlockUrl) {
            chrome.storage.session.set({ flagBlockUrl: true });
chrome.declarativeNetRequest.updateDynamicRules({
                    addRules: [
                        { id: 1, action: { type: 'block' }, condition: { urlFilter: 'XXX', resourceTypes: ['main_frame'] } }
                    ]
                });
        } else if (!max_tabs_open && flagBlockUrl) {
            chrome.storage.session.set({ flagBlockUrl: false });
            chrome.declarativeNetRequest.updateDynamicRules({ removeRuleIds: [1] });
        }
    }
}, { urls: ["dominio"] }, undefined);

async function getFlagBlockUrl() {
    const flag = await chrome.storage.session.get("flagBlockUrl");
    return flag == {} ?
        false : flag.flagBlockUrl != undefined ?
            flag.flagBlockUrl : false;
}

I am having an issue where there seems to be a delay in the application of the rules after they are set. As a result, I can still open a third tab despite it should be blocked according to my extension's logic.

I would like to know if there is any way to ensure that the rules are applied immediately after they are set.

I appreciate any suggestions or advice that the community can offer. 
Thanks!

wOxxOm

unread,
Jun 8, 2023, 7:07:02 AM6/8/23
to Chromium Extensions, angel molero
I would probably just use chrome.tabs.onCreated and/or chrome.tabs.onUpdated, then check `(tab.pendingUrl || tab.url)` inside the event listener.

wOxxOm

unread,
Jun 8, 2023, 7:09:39 AM6/8/23
to Chromium Extensions, wOxxOm, angel molero
...and just close the tab or call chrome.tabs.goBack or chrome.tabs.discard.

angel molero

unread,
Jun 9, 2023, 4:06:22 AM6/9/23
to Chromium Extensions, wOxxOm, angel molero
Hello,

Thank you for your suggestion. While the chrome.tabs.onCreated and chrome.tabs.onUpdated methods would indeed help me detect the creation of new tabs and potentially take an action, it seems like they won't be able to actually block the URL request from being sent to the server.

The aim of my extension is not just to limit the number of tabs, but to also prevent the associated URL request from being sent to the server after a certain limit is reached. My concern is about network load and server performance, and I want to prevent any additional load after the tab limit is reached.

Do you know of any way to actually cancel or block the URL request from being sent to the server using Chrome's extension APIs?

I appreciate any additional advice you can provide. Thanks!

wOxxOm

unread,
Jun 9, 2023, 4:37:08 AM6/9/23
to Chromium Extensions, angel molero, wOxxOm
Well then use chrome.tabs.onUpdated to count the tabs and toggle chrome.declarativeNetRequest.updateDynamicRules accordingly.

const SITE = 'https://example.com/';
const MAX_TABS = 2;
const BLOCK = [{

  id: 1,
  action: {type: 'block'},
  condition: {urlFilter: '|' + SITE, resourceTypes: ['main_frame']},
}];
const myTabs = chrome.tabs.query({url: SITE + '*'}).then(tabs =>
  tabs.reduce((res, t) => (res[t.id] = true, res), {}));

chrome.tabs.onUpdated.addListener(async (tabId, info, tab) => {
  const url = info.url || tab.pendingUrl;
  if (!url) return;
  if (myTabs.then) await myTabs;

  if (url.startsWith(SITE)) myTabs[tabId] = true;
  else if (myTabs[tabId]) delete myTabs[tabId];
  else return;

  chrome.declarativeNetRequest.updateDynamicRules({
    removeRuleIds: BLOCK.map(r => r.id),
    addRules: Object.keys(myTabs).length > MAX_TABS ? BLOCK : null,
  });
});

wOxxOm

unread,
Jun 9, 2023, 5:14:07 AM6/9/23
to Chromium Extensions, wOxxOm, angel molero
Oops, a bugfix:

let myTabs = chrome.tabs.query({url: SITE + '*'}).then(tabs =>
  tabs.reduce((res, t) => (res[t.id] = true, res), myTabs = {}));

angel molero

unread,
Jun 9, 2023, 6:13:01 AM6/9/23
to Chromium Extensions, wOxxOm, angel molero
Thank you for your response. However, I am still encountering the same issue - the third tab is still being displayed, even though it shouldn't. I have even implemented the 'onRemoved' event listener as follows:

const SITE = 'https://es.lipsum.com/';
const MAX_TABS = 2;
const BLOCK = [{

    id: 1,
    action: { type: 'block' },
    condition: { urlFilter: '|' + SITE, resourceTypes: ['main_frame'] },
}];

let myTabs = chrome.tabs.query({ url: SITE + '*' }).then(tabs =>
    tabs.reduce((res, t) => (res[t.id] = true, res), {}));

chrome.tabs.onUpdated.addListener(async (tabId, info, tab) => {
    const url = info.url || tab.pendingUrl;
    if (!url) return;
    if (myTabs.then) {
        await myTabs;
        console.log(myTabs);
    }


    if (url.startsWith(SITE)) myTabs[tabId] = true;
    else if (myTabs[tabId]) delete myTabs[tabId];
    else return;

    chrome.declarativeNetRequest.updateDynamicRules({
        removeRuleIds: BLOCK.map(r => r.id),
        addRules: Object.keys(myTabs).length > MAX_TABS ? BLOCK : null,
    });
});

chrome.tabs.onRemoved.addListener(async (tabId, removeInfo) => {
    if (myTabs.then) {
        await myTabs;
        delete myTabs[tabId];
        console.log("onRemoved ", myTabs);
    }

    if (Object.keys(myTabs).length === MAX_TABS) {
        chrome.declarativeNetRequest.updateDynamicRules({
            removeRuleIds: BLOCK.map(r => r.id),
            addRules:  null
        });
    }
   
});

I understand that the rule applies at that very moment and not afterwards.
However, the problem persists. The third tab continues to load, and it should be blocked according to my extension logic. Here is a screenshot that demonstrates the issue:

foto.png
 I would appreciate further advice or suggestions to rectify this issue. 
Thanks!

wOxxOm

unread,
Jun 9, 2023, 7:12:25 AM6/9/23
to Chromium Extensions, angel molero, wOxxOm
Debug it in devtools and find out what's wrong. It seems the conditions are incorrect. The first one should be >= MAX_TABS and not > MAX_TABS. The second should be === MAX_TABS - 1.

wOxxOm

unread,
Jun 9, 2023, 7:13:15 AM6/9/23
to Chromium Extensions, wOxxOm, angel molero
...and by debugging I mean interactive debugging with breakpoints.

angel molero

unread,
Jun 12, 2023, 6:38:49 AM6/12/23
to Chromium Extensions, wOxxOm, angel molero
Thank you for your response. After an extensive period of testing and adjustments based on your feedback, we've implemented new code that more effectively addresses the original issue.

However, we've identified a specific scenario where the current code doesn't behave as intended. This scenario happens when two tabs are already open on the target domain and a new tab is attempted to be opened from one of these existing two tabs. In this case, the new tab opens, which shouldn't occur as the 'onCreated' event is applying the rule, as can be seen in the attached console image. My suspicion is that this issue may be due to an async matter.

Apart from this case, the code seems to work as expected.

Code:
const SITE = 'https://*.lipsum.com/';
const MAX_TABS = 2;
const BLOCK = [{

    id: 1,
    action: { type: 'block' },
    condition: { urlFilter: '|' + SITE, resourceTypes: ['main_frame'] },
}];

let myTabs = chrome.tabs.query({ url: SITE + '*' }).then(tabs =>
    tabs.reduce((res, t) => (res[t.id] = true, res), {}));

chrome.tabs.onUpdated.addListener(async (tabId, info, tab) => {
    const url = info.url || tab.pendingUrl;
    if (!url) return;
    if (myTabs.then) {
        await myTabs;
        console.log("onUpdated getTabs", myTabs);
    }


    if (url.includes("lipsum.com")) {
         myTabs[tabId] = true;
         console.log("onUpdated tabsActual", myTabs);
    }
    else if (myTabs[tabId]) {
        delete myTabs[tabId];
        console.log("onUpdated after remove actualTabs", myTabs);
    }
    else return;

    chrome.declarativeNetRequest.updateDynamicRules({
        removeRuleIds: BLOCK.map(r => r.id),
        addRules: Object.keys(myTabs).length > MAX_TABS ? BLOCK : null,
    }, () => {
        console.log("Apply rules in updated ", Object.keys(myTabs).length > MAX_TABS ? BLOCK : null);
    });
});

chrome.tabs.onCreated.addListener(async (tab) => {
    if (myTabs.then) {
        await myTabs;
        console.log("onCreated getTabs", myTabs);
    }

    if (Object.keys(myTabs).length == MAX_TABS) {
        chrome.declarativeNetRequest.updateDynamicRules({
            removeRuleIds: BLOCK.map(r => r.id),
            addRules:  BLOCK
        }, () => {console.log("Add Rules")});
    }
   
});

chrome.tabs.onRemoved.addListener(async (tabId, removeInfo) => {
    if (myTabs.then) {
        await myTabs;
        delete myTabs[tabId];
        console.log("onRemoved getTabs", myTabs);
    }

    if (Object.keys(myTabs).length <= MAX_TABS) {
        chrome.declarativeNetRequest.updateDynamicRules({
            removeRuleIds: BLOCK.map(r => r.id),
            addRules:  null
        }, () => {console.log("Remove Rules")});
    }
});

Attached is a screenshot providing a visual insight into the current behavior.

test1.png
test2.png
We would appreciate any further insight you could provide on how to handle the scenario we've identified.

wOxxOm

unread,
Jun 12, 2023, 7:43:01 AM6/12/23
to Chromium Extensions, angel molero, wOxxOm
Judging by the code it seems you didn't see my message with modified conditions: https://groups.google.com/a/chromium.org/g/chromium-extensions/c/H3EfLyBGxfk/m/ZT-Rg-IZAQAJ

angel molero

unread,
Jun 12, 2023, 11:47:10 AM6/12/23
to Chromium Extensions, wOxxOm, angel molero
Thank you for your response. I believe there's an issue with the 'onCreated' event and one of the conditions you suggested (Object.keys(myTabs).length === MAX_TABS - 1). When I open the second tab, the rule is already applied as it finds an existing tab with that URL. Consequently, when I try to navigate to the domain, it gets blocked, even though I haven't reached the maximum number of tabs yet. Interestingly, upon reloading the blocked page, the functionality behaves correctly. Given this behavior, I would like to know if you have any suggestions on how I could resolve this issue. I appreciate your help.

Code:
        console.log("Apply rules in updated ", Object.keys(myTabs).length >= MAX_TABS ? BLOCK : null);
    });
});

chrome.tabs.onCreated.addListener(async (tab) => {
    if (myTabs.then) {
        await myTabs;
        console.log("onCreated getTabs", myTabs);
    }

    if (Object.keys(myTabs).length === MAX_TABS - 1) {
        chrome.declarativeNetRequest.updateDynamicRules({
            removeRuleIds: BLOCK.map(r => r.id),
            addRules:  BLOCK
        }, () => {console.log("Add Rules")});
    }
   
});

chrome.tabs.onRemoved.addListener(async (tabId, removeInfo) => {
    if (myTabs.then) {
        await myTabs;
        delete myTabs[tabId];
        console.log("onRemoved getTabs", myTabs);
    }

    if (Object.keys(myTabs).length <= MAX_TABS) {
        chrome.declarativeNetRequest.updateDynamicRules({
            removeRuleIds: BLOCK.map(r => r.id),
            addRules:  null
        }, () => {console.log("Remove Rules")});
    }
});

test1.png
THANKS!!!

wOxxOm

unread,
Jun 12, 2023, 12:57:22 PM6/12/23
to Chromium Extensions, angel molero, wOxxOm
Your onCreated listener doesn't check the URL. See how onUpdated does it in my examples.

angel molero

unread,
Jun 14, 2023, 6:03:46 AM6/14/23
to Chromium Extensions, wOxxOm, angel molero

Thank you for your response. I have already tried what you suggested, but it does not seem to work correctly, as it continues to exhibit the same behavior that I previously described. Do you have any additional suggestions or guidance on how to resolve this specific issue? Your help is greatly appreciated.
Thanks!

wOxxOm

unread,
Jun 14, 2023, 8:29:48 AM6/14/23
to Chromium Extensions, angel molero, wOxxOm
Use devtools with breakpoints or console.log to debug the problem. Note that when using console.log you need to print JSON.parse(JSON.stringify(obj)) to see the actual value at that moment.

angel molero

unread,
Jun 14, 2023, 10:55:07 AM6/14/23
to Chromium Extensions, wOxxOm, angel molero
Thank you for your response.

Regardless of devtools or the logger, I have a lack of understanding of how the browser behaves. This case I am reporting is the same issue being reported in another forum  DNR dynamic redirect cache problem (google.com)  . It appears that if you manually open a tab and insert the URL with its corresponding navigation, the rule is correctly applied during the creation of the tab and therefore, in my case, the URL is blocked.

However, in the scenario where you right-click to open a new tab or use Ctrl+Click on the link in question, the rule is not applied, even though the events are launched in the same way as when opening and navigating manually, as demonstrated by the previously attached logs.

Is it possible that the browser operates differently under the hood and in some way, the request is already being made prior to the opening of the tab? If this is the case, how can I control the action of opening a link in a new tab or Ctrl+Click to apply the rule prior to navigation? I have already tried using a content script to detect if these actions on a link trigger something in the HTML element's events, but without success.

Thanks!
Reply all
Reply to author
Forward
0 new messages