如何通过 history.pushState 获得有关历史更改的通知?

IT技术 javascript firefox-addon browser-history pushstate
2021-02-09 16:03:11

因此,现在 HTML5 引入history.pushState了更改浏览器历史记录,网站开始将其与 Ajax 结合使用,而不是更改 URL 的片段标识符。

可悲的是,这意味着onhashchange.

我的问题是:有没有可靠的方法(hack?;))来检测网站何时使用history.pushState该规范没有说明有关引发的事件的任何内容(至少我找不到任何内容)。
我尝试创建一个外观并替换window.history为我自己的 JavaScript 对象,但它根本没有任何效果。

进一步解释:我正在开发一个 Firefox 插件,它需要检测这些变化并采取相应的行动。
我知道几天前有一个类似的问题,该问题询问侦听某些DOM 事件是否有效,但我宁愿不依赖它,因为可以出于多种不同的原因生成这些事件。

更新:

这是一个 jsfiddle(使用 Firefox 4 或 Chrome 8),它显示onpopstatepushState被调用时不会触发(或者我做错了什么?随时改进它!)。

更新 2:

另一个(侧面)问题是window.location在使用时没有更新pushState(但我已经在这里读到了这个,所以我认为)。

6个回答

5.5.9.1 事件定义

popstate导航到会话历史记录条目时,事件在某些情况下被解雇。

据此,当您使用pushState. 但是这样的事件pushstate会派上用场。因为它history是一个宿主对象,所以你应该小心对待它,但在这种情况下 Firefox 似乎很好。这段代码工作得很好:

(function(history){
    var pushState = history.pushState;
    history.pushState = function(state) {
        if (typeof history.onpushstate == "function") {
            history.onpushstate({state: state});
        }
        // ... whatever else you want to do
        // maybe call onhashchange e.handler
        return pushState.apply(history, arguments);
    };
})(window.history);

你的 jsfiddle变成了

window.onpopstate = history.onpushstate = function(e) { ... }

你可以window.history.replaceState用同样的方式进行猴子补丁

注意:当然你可以onpushstate简单地添加到全局对象中,你甚至可以通过add/removeListener

你说你替换了整个history对象。在这种情况下,这可能是不必要的。
2021-03-14 16:03:11
@galambalazs:可能是的。也许(不知道)window.history是只读的,但历史对象的属性不是......非常感谢这个解决方案:)
2021-03-14 16:03:11
@cprcrack 用于保持全局范围清洁。您必须保存本机pushState方法以备后用。因此,我选择将整个代码封装到 IIFE 中,而不是全局变量:en.wikipedia.org/wiki/Immediately-invoked_function_expression
2021-03-16 16:03:11
根据规范有一个“pagetransition”事件,它似乎还没有实现。
2021-03-17 16:03:11
@ user280109 - 如果我知道,我会告诉你。:) 我认为在 Opera atm 中没有办法做到这一点。
2021-03-25 16:03:11

终于找到了“正确”的方法来做到这一点!它需要为您的扩展程序添加权限并使用后台页面(不仅仅是内容脚本),但它确实有效。

您想要的事件是browser.webNavigation.onHistoryStateUpdated,当页面使用historyAPI 更改 URL时会触发该事件它只针对您有权访问的站点触发,如果需要,您还可以使用 URL 过滤器进一步减少垃圾邮件。它需要webNavigation权限(当然还有相关域的主机权限)。

事件回调获取选项卡 ID、正在“导航”到的 URL 以及其他此类详细信息。如果您需要在事件触发时在该页面上的内容脚本中执行操作,请直接从后台页面注入相关脚本,或者让内容脚本port在加载时打开一个到后台页面,让后台页面保存由选项卡 ID 索引的集合中的该端口,并在事件触发时通过相关端口(从后台脚本到内容脚本)发送消息。

正确,但这就是 OP 试图根据问题构建的内容。对于纯粹的网络内容,我认为您别无选择,只能包装函数和/或事件。
2021-04-05 16:03:11
不幸的是,该 API 仅适用于 Web 扩展和附加组件,而不是普通的 DOM API
2021-04-08 16:03:11

我用简单的代理来做到这一点。这是原型的替代品

window.history.pushState = new Proxy(window.history.pushState, {
  apply: (target: any, thisArg: any, argArray?: any) => {
    // trigger here what you need
    return target.apply(thisArg, argArray);
  },
});
这对我有用,只需要添加我的触发器window.onpopstate来覆盖按下后退按钮的用户。
2021-03-26 16:03:11

我曾经使用过这个:

var _wr = function(type) {
    var orig = history[type];
    return function() {
        var rv = orig.apply(this, arguments);
        var e = new Event(type);
        e.arguments = arguments;
        window.dispatchEvent(e);
        return rv;
    };
};
history.pushState = _wr('pushState'), history.replaceState = _wr('replaceState');

window.addEventListener('replaceState', function(e) {
    console.warn('THEY DID IT AGAIN!');
});

这几乎和galambalazs一样。

不过这通常是矫枉过正。它可能不适用于所有浏览器。(我只关心我的浏览器版本。)

(它留下一个 var _wr,所以你可能想要包装它或其他东西。我不在乎那个。)

这个答案几乎相同
2021-03-15 16:03:11

除了其他答案。我们可以使用 History 接口来代替存储原始函数。

history.pushState = function()
{
    // ...

    History.prototype.pushState.apply(history, arguments);
}