1

I have a MV2 extension with chrome.webRequest that works perfectly but fail on MV3 declarativeNetRequest getting around iframes.

The extension is like a multi-messenger that opens multiple iframes for various sites to merge in a single extension all popular messengers.

So I have a domain "example.com" and there I open multiple iframes, for example open an iframe with Twitter.com or Telegram.org. Since twitter.com or telegram.org set the X-Frame-Options to DENY those iframes don't show anything. With MV2 we could run chrome.webRequest and remove those headers:

chrome.webRequest.onHeadersReceived.addListener(
    function (details)
    {
        if (details.tabId && (details.tabId === tabId || details.tabId === -1 || tabMultiId.includes(details.tabId))) {
            var b = details.responseHeaders.filter((details) => !['x-frame-options', 'content-security-policy', 'x-content-security-policy', 'strict-transport-security', 'frame-ancestors'].includes(details.name.toLowerCase()));
            
            b.forEach(function(e){
              "set-cookie" === e.name &&  -1 !== e.value.indexOf("Secure") && (-1 !== e.value.indexOf("SameSite=Strict") ? 
                            (e.value = e.value.replace(/SameSite=Strict/g, "SameSite=None"))
                            : -1 !== e.value.indexOf("SameSite=Lax")
                            ? (e.value = e.value.replace(/SameSite=Lax/g, "SameSite=None"))
                            : (e.value = e.value.replace(/; Secure/g, "; SameSite=None; Secure")));
            });
            
            return {
                responseHeaders: b
            }
        }
    },
    {
        urls: [ "<all_urls>" ],
        tabId: tabId
    },
    ["blocking", "responseHeaders", "extraHeaders"]
);

I have tried to do exactly the same with MV3 but keep failing. My 2 attemps:

async function NetRequest() {
var blockUrls = ["*://*.twitter.com/*","*://*.telegram.org/*"];
var tabId = await getObjectFromLocalStorage('tabId');
var tabMultiId = [];
tabMultiId = JSON.parse(await getObjectFromLocalStorage('tabMultiId'));
tabMultiId.push(tabId);
blockUrls.forEach((domain, index) => {
    let id = index + 1;
    
        chrome.declarativeNetRequest.updateSessionRules({
        addRules:[
            {
            "id": id,
            "priority": 1,
            "action": {     "type": "modifyHeaders",
                            "responseHeaders": [
                                { "header": "X-Frame-Options", "operation": "remove" },
                                { "header": "Frame-Options", "operation": "remove" },
                                { "header": "content-security-policy", "operation": "remove" },
                                { "header": "content-security-policy-report-only", "operation": "remove" },
                                { "header": "x-content-security-policy", "operation": "remove" },
                                { "header": "strict-transport-security", "operation": "remove" },
                                { "header": "frame-ancestors", "operation": "remove" },
                                { "header": "set-cookie", "operation": "set", "value": "SameSite=None; Secure" }
                            ] 
            },
            "condition": {"urlFilter": domain, "resourceTypes": ["image","media","main_frame","sub_frame","stylesheet","script","font","xmlhttprequest","ping","websocket","other"], 
            "tabIds" : tabMultiId }
            }
            ],
        removeRuleIds: [id]
        });
    
});
}

async function launchWindow(newURL, windowDimensions, urlWindow, isIncognitoWindow, windowType) {
    chrome.windows.create({ url: newURL, type: windowType, incognito: isIncognitoWindow, width: windowDimensions.width, height: windowDimensions.height, left: windowDimensions.left, top: windowDimensions.top },
        async function (chromeWindow) {
            if (urlWindow != "install" || urlWindow != "update") {
                chrome.storage.local.set({ 'extensionWindowId': chromeWindow.id }, function () { });
                chrome.storage.local.set({ 'tabId': chromeWindow.tabs[0].id }, function () { });
                NetRequest();
            }
    });
}

Also tried:

const iframeHosts = [
        'twitter.com', 'telegram.org'
      ];

      const RULE = {
        id: 1,
        condition: {
          initiatorDomains: ['example.com'],
          requestDomains: iframeHosts,
          resourceTypes: ['sub_frame', 'main_frame'],
        },
        action: {
          type: 'modifyHeaders',
          responseHeaders: [
            {header: 'X-Frame-Options', operation: 'remove'},
            {header: 'Frame-Options', operation: 'remove'},
          ],
        },
      };
      chrome.declarativeNetRequest.updateDynamicRules({
        removeRuleIds: [RULE.id],
        addRules: [RULE],
      });

Permissions:

  "permissions": [
    "system.display",
    "scripting",
    "activeTab",
    "notifications",
    "contextMenus",
    "unlimitedStorage",
    "storage",
    "declarativeNetRequestWithHostAccess",
    "webNavigation",
  "alarms"
  ],
  "host_permissions": [
  "<all_urls>"
  ],

Any of this attempts worked. Greetings and thank you very much for anyone that try to help.

user1327579
  • 53
  • 1
  • 5
  • Show your `host_permissions` and how you initialize `tabMultiId`. – wOxxOm Nov 10 '22 at 15:36
  • Hi @wOxxOm just edited with those 2 information. Tried with the tip you gave from this post but dind't work: https://stackoverflow.com/questions/15532791/getting-around-x-frame-options-deny-in-a-chrome-extension/69177790#69177790 – user1327579 Nov 10 '22 at 20:29
  • When does this code run (which listener) and where tab ids are saved to storage? Also note that devtools doesn't show modifications of the headers. – wOxxOm Nov 10 '22 at 22:43
  • Hi @wOxxOm thank you for the reply. Just updated the second block of code with your doubts. If devtools doesn't show header modifications is there any way to see that? Greetings. – user1327579 Nov 11 '22 at 18:35
  • Your NetRequest may run too late, after the URL already started loading. You should create a blank tab i.e. `url: 'about:blank'`, get its tabId, set the rules, then use chrome.tabs.update to change this tab's url. To see the modified headers either use `chrome://net-export` or an external tool like Fiddler, Charles, WireShark. – wOxxOm Nov 11 '22 at 19:34
  • Hi @wOxxOm tried that, but also didn't worked. Do you know or have you a super simple MV3 extension that simply put for example Twitter or Telegram on a iframe and working? On MV3 I'm only getting: Refused to display 'https://xxxx.com/' in a frame because it set 'X-Frame-Options' to 'deny'. – user1327579 Nov 22 '22 at 23:01
  • Start by calling updateDynamicRules in chrome.runtime.onInstalled and make sure it works for you, then debug the problem with setting the tabId. I can't help without seeing the new code. – wOxxOm Nov 22 '22 at 23:43
  • Hi, I have made 2 simple Apps. One in MV3 not working. And one on MV2 working. MV2: https://drive.google.com/file/d/16gb7kZXiCY1PrxR-Cx0zyyjKrAXN9x4Q/view?usp=sharing MV3: https://drive.google.com/file/d/1wm_68JvIN8DsattQvSDJpjDzaGgaCqZ-/view?usp=sharing – user1327579 Dec 14 '22 at 21:26

1 Answers1

1
  1. You need to unregister service worker for the site and clear its cache using chrome.browsingData API.
  2. Syntax for urlFilter is different, so your "*://*.twitter.com/*" is incorrect and should be "||twitter.com/", however a better solution is to use requestDomains because it allows specifying multiple sites in just one rule.

// manifest.json

  "permissions": ["browsingData", "declarativeNetRequest"],
  "host_permissions": ["*://*.twitter.com/", "*://*.telegram.org/"],

// extension script

async function configureNetRequest(tabId) {
  const domains = [
    'twitter.com',
    'telegram.org',
  ];
  const headers = [
    'X-Frame-Options',
    'Frame-Options',
  ];
  await chrome.declarativeNetRequest.updateSessionRules({
    removeRuleIds: [1],
    addRules: [{
      id: 1,
      action: {
        type: 'modifyHeaders',
        responseHeaders: headers.map(h => ({ header: h, operation: 'remove'})),
      },
      condition: {
        requestDomains: domains,
        resourceTypes: ['sub_frame'],
        tabIds: [tabId],
      },
    }],
  });
  await chrome.browsingData.remove({
    origins: domains.map(d => `https://${d}`),
  }, {
    cacheStorage: true,
    serviceWorkers: true,
  });
}

// Usage

chrome.windows.create({ url: 'about:blank' }, async w => {
  await configureNetRequest(w.tabs[0].id);
  await chrome.tabs.update(w.tabs[0].id, { url: 'https://some.real.url/' });
});
wOxxOm
  • 65,848
  • 11
  • 132
  • 136
  • I'm trying to do something similar, but I want an image linter to run on for whatever website in the current tab (iframe headers block on some sites). So I won't have a list of URLs, it's just the activeTab URL. Is there a way to do this? I'm assuming I'm hitting the same roadblock of needing to open the new tab, configure the request and modify response headers, then navigate, then run the iframe script @wOxxOm – Sia Aug 04 '23 at 15:11
  • @Sia, sounds like removing `requestDomains` is all you need. If not, ask a new question. – wOxxOm Aug 04 '23 at 16:01
  • FYI, got it working by not reloading the tab https://github.com/siakaramalegos/resp-image-lint-extension/blob/main/background.js – Sia Aug 04 '23 at 19:22