6

I'm developing a Chrome extension;

I need to detect when a tab is duplicated, I'm looking for a tab detection event like onduplicate.addListener()?

Is that possible with the Chrome extension API?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Dev hercule
  • 107
  • 1
  • 11
  • As you can see in the [documentation](https://developer.chrome.com/extensions/tabs), there's no such event. You'll have to experiment with other events to find a combination of checks. – wOxxOm May 29 '17 at 08:41
  • Thank you for your reply, can you exlain me more what do you mean by "You'll have to experiment with other events to find a combination of checks" have you any examples? – Dev hercule May 29 '17 at 08:45
  • Experiment means just that: declare and attach listeners with console.log inside for all chrome.tabs events and see what happens on tab duplication that differentiates it. And no, I don't have any examples. – wOxxOm May 29 '17 at 08:48
  • I've already done that, but no solution – Dev hercule May 29 '17 at 09:08
  • Seriously? It doesn't seem you've really tried. The duplicated tab should have the same URL as the tab preceding it (the tab `.index` property) so it should be possible to use that and something else to differentiate. – wOxxOm May 29 '17 at 09:12
  • i have tried and I have already thought about it, It is not a good idea, because the same URL can be opened in many tabs, so it will be confused if I compare the duplicated URL tab with all tabs URLs. Many tabs have the same url but only one is duplicated. Another problem, if i open a new tab with the same URL, it will be detected as duplicated tab, however, in reality it is not :( – Dev hercule May 29 '17 at 09:46
  • Huh, here's an apparently working solution: tab duplication preserves `sessionStorage` of the page so simply store some unique variable in your content script in each page and check if it's present in the beginning of your content script. – wOxxOm May 29 '17 at 11:04

2 Answers2

3

This is the closest implementation:

const newTabsId = new Set();

// Observe all new tabs with opener ID
chrome.tabs.onCreated.addListener(tab => {
    if(tab.openerTabId) {
        newTabsId.add(tab.id);
    }
});

// Waiting for a new tab completeness
chrome.tabs.onUpdated.addListener((tabId, changes, tab) => {
    if(newTabsId.has(tabId) && changes.status === 'complete') { 
        if(!tab.openerTabId) {
            return;
        }
        // Retrieve opener (original) tab
        getTabById(tab.openerTabId)
            .then(originalTab => {
                if(
                    originalTab.url === tab.url &&          // original and new tab should have same URL
                    originalTab.index + 1 === tab.index &&  // new tab should have next index
                    tab.active && tab.selected              // new tab should be active and selected
                                                            // also we may compare scroll from top, but for that we need to use content-script
                ) {
                    console.log('Duplicate:', tab);
                }
            });
        // Remove this tab from observable list
        newTabsId.delete(tabId);
    }
});

// Syntax sugar
function getTabById(id) {
    return new Promise((resolve, reject) => {
        chrome.tabs.get(id, resolve);
    });
}

// Cleanup memory: remove from observables if tab has been closed
chrome.tabs.onRemoved.addListener(tabId => {
    newTabsId.delete(tabId);
});

EDIT 1: But yeah, there is no clear solution now to detect real duplicate

Denis L
  • 3,209
  • 1
  • 25
  • 37
  • It is a good idea, I tested it but there is a probeme there... it works when the duplicated Tab is active and selected, However, it does'nt work when we duplicate a non active non selected Tab: right click on a inactive tab – Dev hercule May 29 '17 at 10:01
  • 1
    Huh.. cannot reproduce it: on my PC every duplicated tab became a selected, even if I used right click on innactive – Denis L May 29 '17 at 10:03
  • mmmm Ok, Is there a way to force selecting or acticing the tab after a right click? I mean with chrome extension – Dev hercule May 29 '17 at 10:07
  • @Devhercule nope – Denis L May 29 '17 at 10:14
  • @Deliaz the openerTabId is the id of the tab that active at the moment. So if I right-click on an inactive tab and press duplicate (or use API method "chrome.tabs.duplicate") then the openerTabId will be not correct and your solution won't work. – Dmitry Artamoshkin Sep 24 '18 at 08:43
  • @DmitryArtamoshkin, thank for the comment, but that is not correct. According to the [chrome.tabs](https://developer.chrome.com/extensions/tabs#type-Tab) docs the `openerTabId` corresponds to the "The ID of the tab that opened this tab, if any." – Denis L Sep 24 '18 at 11:51
  • @Deliaz yes, but they don't specify what does it mean "tab that opened this tab". I've run the experiment and see that the openerTabId is equal to the active tab id despite that I duplicated another tab. (Chrome 69.0.3497.100, Win7 x64). I saw it in previous versions as well. – Dmitry Artamoshkin Sep 24 '18 at 17:24
  • You can also use [BroadcastChannel](https://stackoverflow.com/a/62231610/975097) to detect duplicate tabs. – Anderson Green Feb 07 '21 at 01:55
3

Tab duplication preserves sessionStorage of the page so simply store some unique variable in your content script in each page and check if it's present in the beginning of your content script.

manifest:

"content_scripts": [{
  "matches": ["<all_urls>"],
  "run_at": "document_start",
  "js": ["content.js"]
}],

content script:

if (sessionStorage[chrome.runtime.id]) {
  chrome.runtime.sendMessage({
    action: 'checkDup',
    tabId: Number(sessionStorage[chrome.runtime.id]),
  }, isDupe => {
    console.log(isDupe);
  });
} else {
  chrome.runtime.sendMessage({
    action: 'getTabId'
  }, tabId => {
    sessionStorage[chrome.runtime.id] = tabId;
  });
}

background/event script:

chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
  switch (msg.action) {
    case 'getTabId':
      sendResponse(sender.tab.id);
      return;
    case 'checkDup':
      chrome.tabs.get(msg.tabId, tab => {
        if (tab 
        && tab.index == sender.tab.index - 1 
        && tab.url == sender.tab.url) {
          sendResponse(true);
          console.log('Tab duplicated: ', tab, '->', sender.tab);
        }
      });
      return true; // keep the message channel open
  }
});
wOxxOm
  • 65,848
  • 11
  • 132
  • 136
  • Thank you for this code, but it still present some errors: 1/it doesn't work when we duplicatea tab many times 2/ when we dupllicate tab1, we have tab2, when we duplicate tab2 we have tab3 (however in session storage tab3 has tab1 as parent instead of tab2) 3/ the extension does'nt work when we close tab1, -> indefined in the consol (because tab1 not exist) and always tab1 in session storage (instead of tab2 or teb3). I hope that i was clear in my description, thank you anyway for your help :) – Dev hercule May 29 '17 at 15:00
  • 2
    This is just an example that illustrates the basic idea. It's up to you to solve all those edge cases. I see no reason why it can't be done. Be creative and persistent. – wOxxOm May 29 '17 at 15:02