Chrome 标签页的永久唯一 ID,在浏览器会话之间持续存在

Persistent unique ID for Chrome tabs that lasts between browser sessions

本文关键字:会话 浏览器 之间 存在 ID 标签 唯一 Chrome      更新时间:2023-09-26

我正在尝试确定某种方法,为满足以下条件的Chrome标签页建立唯一ID:

  • 唯一标识每个选项卡
  • 浏览器重新启动之间对给定选项卡保持不变(会话还原选项卡)
  • 如果选项卡关闭,然后使用"撤消关闭的选项卡"重新打开,则保持不变 (Ctrl+Shift+T)
  • 如果选项卡重复,则保持不同

我已经做了一些相当积极的研究来找到一个全面的解决方案,但似乎没有什么能解决问题。以下是我尝试过的方法,按功效递增顺序排列:

  • 使用 Chrome 提供的 tab.id:在浏览器会话之间不会持续存在,也不会关闭/撤消关闭
  • 在 Cookie 中放置 GUID:每个选项卡不唯一,仅每个域/URL
  • 将 GUID 放入本地存储中:在浏览器会话和关闭/撤消关闭之间持续存在,但每个选项卡不唯一,仅每个域唯一
  • 将 GUID 放入会话存储:每个选项卡唯一,在关闭/撤消关闭中保持不变,对于重复的选项卡是唯一的,但在浏览器会话之间被清除
  • 使用可识别的网页文档属性作为唯一键:这是我迄今为止发现的最好的方法。可以通过内容脚本从以下值构造密钥:[location.href, document.referrer, history.length] .

关于最后一种方法,构造的键在所有共享公共 URL、引荐来源网址和历史记录长度的选项卡中都是唯一的。对于浏览器重新启动/会话还原和关闭/撤消关闭之间的给定选项卡,这些值将保持不变。虽然这个键是"漂亮"的唯一,但在某些情况下它是模棱两可的:例如,打开 3 个新选项卡 http://www.google.com 都会有相同的键(这种事情在实践中经常发生)。

"将 GUID 放入会话存储"方法还可用于消除具有相同构造键的多个选项卡之间的歧义,以处理当前浏览器会话期间的关闭/撤消关闭和重复选项卡情况。但这并不能解决浏览器重启之间的歧义问题。

在会话恢复期间,

可以通过观察 Chrome 在哪些窗口中一起打开哪些标签页,并根据预期的"同级"标签页(在上一次浏览器会话期间记录)对给定的模糊键推断哪个标签属于哪个窗口,可以部分缓解最后的歧义。正如您可能想象的那样,实施此解决方案非常复杂且相当狡猾。而且它只能在Chrome还原到不同窗口中的相同键标签之间消除歧义。这使得恢复到同一窗口的相同键的选项卡变得不可调和模棱两可。

有没有更好的方法?在浏览器重新启动(会话还原)和关闭/撤消关闭之间持续存在的保证唯一的、浏览器生成的、每选项卡的 GUID 将是理想的,但到目前为止,我还没有找到这样的东西。

这里的问题完成了大部分发现工作,并且接受的答案基本上完成了它,但是对于希望实现需要持久选项卡ID的东西的人来说,仍然存在很大的实现差距。我试图将其提炼成实际实现。

回顾一下:通过维护选项卡寄存器,选项卡可以(几乎)根据问题的要求唯一且一致地标识,该寄存器在本地持久存储中存储以下变量组合:

  • Tab.id
  • Tab.index
  • 在选项卡中打开的文档的"指纹" - [location.href, document.referrer, history.length]

可以使用侦听器跟踪这些变量并将其存储在注册表中,这些变量具有以下事件的组合:

  • onUpdated
  • onCreated
  • onMoved
  • onDetached
  • onAttached
  • onRemoved
  • onReplaced

仍然有办法欺骗这种方法,但在实践中它们可能非常罕见 - 主要是边缘情况。

由于看起来我不是唯一需要解决这个问题的人,因此我将我的实现构建为一个库,目的是它可以在任何Chrome扩展程序中使用。它是 MIT 许可的,可在 GitHub 上用于分叉和拉取请求(事实上,欢迎任何反馈 - 肯定有可能的改进)。

如果我正确理解您的问题,您的第 5 种方法应该可以解决问题,但要遵循以下两个标准:

  • chrome.tabs.windowId(选项卡所在的窗口的 ID)
  • chrome.tabs.index(选项卡在其窗口中的从零开始的索引)

所有这些值都需要存储在扩展中。除此之外,您还必须将扩展连接到chrome.tabs.onUpdated()并在拖动选项卡、跨所有者窗口移动等时相应地更新。

将其作为 manifest.json 中的持久后台脚本:

"background": {
        "scripts": [ "background.js" ],
        "persistent": true
},

这是背景.js。希望代码是不言自明的。

var tabs_hashes = {};
var tabs_hashes_save_queued = false;
function Start(){
    chrome.tabs.query({windowType: "normal"}, function(querytabs){
        querytabs.forEach(function(tab){
            tabs_hashes[tab.id] = GetHash(tab.url);
        });
        if (localStorage.getItem("tabs_hashes") !== null){
            var ref_load = JSON.parse(localStorage["tabs_hashes"]);
            var ref_tabId = {};

            querytabs.forEach(function(tab){
                for (var t = 0; t < ref_load.length; t++){
                    if (ref_load[t][1] === tabs_hashes[tab.id]){
                        ref_tabId[ref_load[t][0]] = tab.id;
                        ref_load.splice(t, 1);
                        break;
                    }
                }
            });
            // do what you have to do to convert previous tabId to the new one
            // just use ref_tabId[your_previous_tabId] to get the current corresponding new tabId
            console.log(ref_tabId);
        }
    });
}

function SaveHashes(){
    if (!tabs_hashes_save_queued && Object.keys(tabs_hashes).length > 0){
        tabs_hashes_save_queued = true;
        chrome.tabs.query({windowType: "normal"}, function(querytabs){
            var data = [];
            querytabs.forEach(function(tab){
                if (tabs_hashes[tab.id]){
                    data.push([tab.id, tabs_hashes[tab.id]]);
                } else {
                    data.push([tab.id, GetHash(tab.url)]);
                }
            });
            localStorage["tabs_hashes"] = JSON.stringify(data);
            setTimeout(function(){ tabs_hashes_save_queued = false; }, 1000);
        });
    }
}
function GetHash(s){
    var hash = 0;
    if (s.length === 0){
        return 0;
    }
    for (var i = 0; i < s.length; i++){
        hash = (hash << 5)-hash;
        hash = hash+s.charCodeAt(i);
        hash |= 0;
    }
    return Math.abs(hash);
}

chrome.tabs.onCreated.addListener(function(tab){
    SaveHashes();
});
chrome.tabs.onAttached.addListener(function(tabId){
    SaveHashes();
});
chrome.tabs.onRemoved.addListener(function(tabId){
    delete tabs_hashes[tabId];
    SaveHashes();
});
chrome.tabs.onDetached.addListener(function(tabId){
    SaveHashes();
});
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo){
    if (changeInfo.pinned != undefined || changeInfo.url != undefined){
        delete tabs_hashes[tabId];
        SaveHashes();
    }
});
chrome.tabs.onMoved.addListener(function(tabId){
    SaveHashes();
});
chrome.tabs.onReplaced.addListener(function(addedTabId, removedTabId){
    delete tabs_hashes[removedTabId];
    SaveHashes();
});

Start();

我使用数组来保存数据,因为通过这种方式我可以保留选项卡顺序,如果数据保存在对象中,这不太可能。在浏览器重新启动后加载数据时,即使 url 不是唯一的,我也可以相信它会在某个"足够接近"的索引下。我会做得更复杂,例如反向检查是否找不到选项卡,但到目前为止这工作正常。