Chrome 标签页的永久唯一 ID,在浏览器会话之间持续存在
Persistent unique ID for Chrome tabs that lasts between browser sessions
我正在尝试确定某种方法,为满足以下条件的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 不是唯一的,我也可以相信它会在某个"足够接近"的索引下。我会做得更复杂,例如反向检查是否找不到选项卡,但到目前为止这工作正常。
- Django会话与浏览器本地存储
- web应用程序中的跨浏览器会话
- 移动浏览器没有;t支持会话变量
- 如何在没有JQuery的情况下,通过对服务器的基本ajax调用,根据浏览器上的用户活动保持服务器会话活动
- 如何在javascript中设置浏览器关闭时的会话超时
- 每个用户/浏览器会话只加载一次Javascript
- 浏览器关闭时终止会话
- 浏览器会话存储.在选项卡之间共享
- 在浏览器会话中保存表单数据
- 如何在django关闭浏览器时终止会话
- 无法在浏览器中更改快速会话到期日期
- 显示弹出脚本,使其在每个浏览器会话中只弹出一次
- ajax-在JAVA中关闭浏览器时关闭会话
- 如何使用java脚本更改浏览器的内存管理以增加会话存储和本地存储的内存
- 我们可以在浏览器会话中跨网页引用 JavaScript 变量吗?
- 保持浏览器会话持久性的地理位置
- 当有两个浏览器实例时,如何检查浏览器会话是否存在
- 当用户在浏览器上单击后退按钮时会话会发生什么
- Chrome 标签页的永久唯一 ID,在浏览器会话之间持续存在
- 在 ember-simple-auth 中关闭浏览器时使会话无效