方法如下:
[2020 年 11 月更新: 此解决方案依赖于能够设置 document.domain。不幸的是,这样做的能力现在已被弃用。]
对于给定超级域(例如 example.com)的子域之间的共享,您可以在这种情况下使用一种技术。它可以应用于localStorage、IndexedDB、SharedWorker、BroadcastChannel 等,所有这些都提供了同源页面之间的共享功能,但由于某种原因,不尊重对document.domain 的任何修改会让他们直接使用超级域作为他们的来源。
(1) 选择一个“主”域作为数据所属的域:即 https://example.com 或 https://www.example.com 将保存您的 localStorage 数据。假设您选择https://example.com。
(2) 对所选域的页面正常使用 localStorage。
(3) 在所有 https://www.example.com 页面(other 域)上,使用 javascript 设置document.domain = "example.com";。然后还创建一个隐藏的<iframe>,并将其导航到所选https://example.com 域上的一些 页面(没关系什么 页面,只要您可以在其中插入少量的 javascript sn-p。如果您正在创建站点,只需为此目的专门制作一个空页面即可。扩展程序或 Greasemonkey 样式的用户脚本,因此无法控制 example.com 服务器上的页面,只需选择您能找到的最轻量级的页面并将脚本插入其中即可。某种“未找到”页面可能会很好)。
(4) 隐藏 iframe 页面上的脚本只需要 (a) 设置 document.domain = "example.com";,并且 (b) 完成后通知父窗口。之后,父窗口可以不受限制地访问 iframe 窗口及其所有对象!所以最小的 iframe 页面是这样的:
<!doctype html>
<html>
<head>
<script>
document.domain = "example.com";
window.parent.iframeReady(); // function defined & called on parent window
</script>
</head>
<body></body>
</html>
如果编写用户脚本,您可能不想将iframeReady() 等外部可访问函数添加到unsafeWindow,因此通知主窗口用户脚本的更好方法可能是使用自定义事件:
window.parent.dispatchEvent(new CustomEvent("iframeReady"));
您可以通过将自定义“iframeReady”事件的侦听器添加到您的主页窗口来检测。
(注意:即使 iframe 的域已经是 example.com,您也需要设置 document.domain = "example.com":为 document.domain 分配值会隐式设置源的 port 为 null,并且两个端口必须匹配 iframe 及其父级才能被视为同源。请参阅此处的注释:https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#Changing_origin)
(5) 一旦隐藏的 iframe 通知其父窗口它已准备好,父窗口中的脚本可以使用 iframe.contentWindow.localStorage、iframe.contentWindow.indexedDB、iframe.contentWindow.BroadcastChannel、iframe.contentWindow.SharedWorker 而不是 window.localStorage、@987654346 @ 等...所有这些对象的范围都将限定为所选的 https://example.com 来源 - 因此它们将为您的所有页面拥有相同的共享来源!
此技术最尴尬的部分是您必须等待 iframe 加载后才能继续。因此,例如,您不能只是愉快地开始在 DOMContentLoaded 处理程序中使用 localStorage。此外,您可能需要添加一些错误处理来检测隐藏的 iframe 是否无法正确加载。
显然,您还应该确保隐藏的 iframe 在页面的生命周期内没有被删除或导航... OTOH 我不知道结果会是什么,但很可能会发生不好的事情。
并且,需要注意的是:设置/更改document.domain 可以使用Feature-Policy 标头阻止,在这种情况下,该技术将无法如所述使用。
但是,这种技术有一个更为复杂的概括,Feature-Policy 无法阻止它,而且它还允许完全不相关的域共享数据、通信和共享工作人员(即不仅仅是公共超级域的子域)。 @Mayank Jain 已经在他们的回答中描述了它,即:
一般的想法是,就像上面一样,您创建一个隐藏的 iframe 来提供正确的访问来源;但不是直接获取 iframe 窗口的属性,而是使用 iframe 内的脚本来完成所有工作,并且只使用 postMessage() 和 addEventListener("message",...) 在 iframe 和主窗口之间进行通信。
这是因为postMessage() 甚至可以在不同来源的窗口之间使用。但它也明显更加复杂,因为您必须通过在 iframe 和主窗口之间创建的某种消息传递基础架构传递所有内容,而不是直接在主窗口的代码中使用 localStorage、IndexedDB 等 API。