【问题标题】:Persistent unique ID for Chrome tabs that lasts between browser sessions在浏览器会话之间持续的 Chrome 标签的持久唯一 ID
【发布时间】:2012-06-15 20:13:44
【问题描述】:

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

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

我进行了一些相当激进的研究以找到一个全面的解决方案,但似乎没有什么能完全奏效。以下是我尝试过的方法,按效果递增的顺序排列:

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

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

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

在会话恢复期间,可以通过观察 Chrome 在哪些窗口中同时打开哪些选项卡,并根据预期的“兄弟”选项卡的存在推断给定的模棱两可键哪个选项卡属于哪个窗口(在上一个浏览器会话)。正如您可能想象的那样,实施此解决方案非常复杂且相当狡猾。它只能消除 Chrome 恢复到不同窗口的相同键选项卡之间的歧义。这使得恢复到同一个窗口中的相同键的选项卡变得不可调和的模棱两可。

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

【问题讨论】:

    标签: javascript google-chrome google-chrome-extension guid


    【解决方案1】:

    把它作为一个持久的后台脚本放在 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 不是唯一的,我可以相信它会在某个“足够接近”的索引下。我会做的更复杂,例如反向检查是否找不到选项卡,但到目前为止这工作正常。

    【讨论】:

      【解决方案2】:

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

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

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

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

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

      仍有一些方法可以欺骗这种方法,但实际上它们可能非常罕见 - 主要是边缘情况。

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

      【讨论】:

        【解决方案3】:

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

        • chrome.tabs.windowId(标签所在窗口的 ID)
        • chrome.tabs.index(窗口内标签的从零开始的索引)

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

        【讨论】:

        • 好主意。不幸的是,windowId 在浏览器会话之间发生了变化,但是如果我可以通过记住和比较浏览器重新启动之前和之后每个窗口中的选项卡来推断会话之间的 windowId 更改,我应该能够使用 .index 来消除两个其他相同的选项卡的歧义在给定的窗口中。非常感谢。
        • 我要补充一点,如果我可以识别任何单个,我应该能够将“重启前”记住的 windowId 与打开的“重启后”windowId 相关联具有唯一 url+referrer+historylength 键的选项卡。由于所述选项卡是唯一具有该特定键的选项卡,因此我知道它属于哪个窗口;因此,我知道任何记住的具有相同“旧”windowId 的选项卡也属于同一个“新”窗口。
        • @joelpt 是的,windowId 是不可靠的,但如果您连接 chrome.window.onCreatedchrome.window.onRemoved 事件侦听器,您可以轻松地在所有窗口上模拟索引(订单 ID)。
        • 在浏览器重新启动之间是否以相同的顺序枚举窗口?
        • @GeneGolovchinsky 测试并查看。通常它应该
        猜你喜欢
        • 1970-01-01
        • 2023-03-26
        • 2020-07-19
        • 1970-01-01
        • 2013-12-18
        • 2011-03-01
        • 2011-08-05
        • 1970-01-01
        相关资源
        最近更新 更多