跨浏览器会话持久化的 Chrome 标签页唯一标识符

13

我正在寻找一种方法来为Chrome标签页建立唯一的ID,以满足以下条件:

  • 唯一地识别每个标签页
  • 对于给定的标签页,在浏览器重启(会话恢复的标签页)时保持不变
  • 如果一个标签页被关闭然后通过撤销关闭标签页(Ctrl+Shift+T)重新打开,则保持不变
  • 如果一个标签页被复制,则保持不同

我已经进行了一些积极的研究,以找到一个全面的解决方案,但似乎没有任何一种方法能够完全实现这一目标。以下是我尝试过的方法,按照有效性递增的顺序列出:

  • 使用Chrome提供的tab.id:不能在浏览器会话或关闭/撤消关闭之间保持持久。
  • 在cookie中放置GUID:每个标签页不是唯一的,只针对域名/URL是唯一的。
  • 在localStorage中放置GUID:在浏览器重启和关闭/撤消关闭之间保持持久,但每个标签页不是唯一的,仅针对域名是唯一的。
  • 在sessionStorage中放置GUID:每个标签页都是唯一的,在关闭/撤消关闭之间保持不变,对于复制的标签页也是唯一的,但在浏览器会话之间被清除。
  • 使用可识别的网页文档属性作为唯一键:这是迄今为止我找到的最佳方法。可以通过内容脚本从以下值构建一个键:[location.href, document.referrer, history.length]

关于最后一种方法,构建的键在所有共享公共URL、引用和历史记录长度的选项卡中是唯一的。这些值将在浏览器重启/会话恢复和关闭/撤销关闭时保持不变。虽然这个键是"相当"唯一的,但存在模糊性的情况:例如,打开3个指向http://www.google.com的新标签页将共有相同的键(这种情况在实践中经常发生)。

"将 GUID 存储在 sessionStorage" 方法可以用于在当前浏览器会话期间进一步消除针对关闭/撤销关闭和重复选项卡情况所构造的相同 key 的多个选项卡之间的歧义。但这并不能解决浏览器重新启动时的歧义问题。

在会话恢复期间,观察 Chrome 在哪些窗口中打开了哪些选项卡,并根据预期的“兄弟”选项卡的存在来推断给定模棱两可的键中哪个选项卡属于哪个窗口,可以在某种程度上减轻此类问题的影响(这些信息在上一个浏览器会话期间记录)。你可以想象,实现这个解决方案是相当复杂和不可靠的。而且它只能区分 Chrome 恢复到不同窗口的具有相同键的选项卡。这意味着恢复到同一窗口的具有相同键的选项卡仍然无法消除歧义。

是否有更好的方法?理想情况下,可以保证唯一、由浏览器生成、在浏览器重新启动(会话恢复)和关闭/撤销关闭之间持久存在的每个选项卡 GUID 将是最理想的,但到目前为止我还没有找到类似的东西。

3个回答

7
这里的问题大部分由问题本身解决,而被接受的答案基本上完成了它,但对于想要实现需要持久标签ID的人来说,仍存在一个很大的实现差距。我试图将其概括为实际实现。
回顾一下:可以通过在本地持久存储中维护标签寄存器来存储以下变量组合以几乎唯一和一致地识别标签,这是问题所需的:
- Tab.id - Tab.index - 打开标签中文档的“指纹” - [location.href,document.referrer,history.length]
这些变量可以使用以下事件的组合上的监听器来跟踪和存储在寄存器中:
- onUpdated - onCreated - onMoved - onDetached - onAttached - onRemoved - onReplaced
仍有方法可以欺骗此方法,但在实践中它们可能非常罕见 - 大多数情况都是边缘情况。

由于似乎不只我一个人需要解决这个问题,我将我的实现构建为库,意在它可以在任何Chrome扩展中使用。它采用MIT许可证,可在GitHub上获取以进行分支和拉取请求(事实上,任何反馈都是受欢迎的——肯定有可能改进)。


2

如果我正确理解您的问题,您的第5种方法应该可以解决问题,但需要满足以下两个条件:

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

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


很棒的想法。不幸的是,浏览器会话之间的windowId会发生变化,但是如果我能够通过记住并比较浏览器重新启动前后每个窗口中有哪些选项卡来推断windowId在会话之间的变化,我应该能够像您建议的那样使用.index 来消除给定窗口中两个完全相同的标签的歧义。非常感谢。 - joelpt
我应该能够将“重启前”记忆的windowId与打开的“重启后”windowId相关联,如果我可以识别出任何具有唯一url + referrer + historylength键的单个选项卡。由于该选项卡是唯一具有该特定键的选项卡,我知道它属于哪个窗口;因此,我知道任何具有相同“旧”的windowId的记忆选项卡也属于同一个“新”窗口。 - joelpt
1
@joelpt 是的,windowId 不可靠,但如果你连接 chrome.window.onCreatedchrome.window.onRemoved 事件监听器,你可以轻松地在所有窗口上模拟一个索引(顺序 ID)。 - Silviu-Marian
浏览器重新启动后,窗口的枚举顺序是否相同? - Gene Golovchinsky
@GeneGolovchinsky 测试一下,通常应该可以。 - Silviu-Marian

0
将以下代码作为 manifest.json 中的持久性背景脚本:
"background": {
        "scripts": [ "background.js" ],
        "persistent": true
},

这是 background.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不唯一,我也可以相信它将位于某个“足够接近”的索引下。我会做得更复杂,例如反向检查是否找到了选项卡,但到目前为止,这种方法运行良好。


在您的情况下,URL的哈希值似乎是不必要的中间人,可以省略。而且,URL的唯一性不足以在用户会话之间识别唯一的选项卡。 - Dmitry Davydov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接