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

不幸的是,这意味着onhashchange再也不能检测到这些调用了。

我的问题是:有没有可靠的方法(hack?);))来检测网站何时使用history.pushState?规范没有说明引发的任何事件(至少我没有找到任何事件)。 我试图创造一个立面,并替换了窗户。我自己的JavaScript对象的历史,但它根本没有任何影响。

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

更新:

这里是一个jsfiddle(使用Firefox 4或Chrome 8),显示onpopstate没有触发pushState被调用(或我做错了什么?请随意改进!)。

更新2:

另一个问题是那个窗口。使用pushState时,location不会更新(但我已经在这里读到这个,所以我认为)。


当前回答

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

window.history.pushState = new Proxy(window.history.pushState, {
  apply: (target, thisArg, argArray) => {
    // trigger here what you need
    return target.apply(thisArg, argArray);
  },
});

其他回答

既然你问的是Firefox插件,下面是我要使用的代码。不再推荐使用unsafeWindow,并且当pushState被修改后从客户端脚本调用时会出现错误:

访问属性history.pushState的权限被拒绝

相反,有一个名为exportFunction的API,它允许将函数注入到window中。历史是这样的:

var pushState = history.pushState;

function pushStateHack (state) {
    if (typeof history.onpushstate == "function") {
        history.onpushstate({state: state});
    }

    return pushState.apply(history, arguments);
}

history.onpushstate = function(state) {
    // callback here
}

exportFunction(pushStateHack, unsafeWindow.history, {defineAs: 'pushState', allowCallbacks: true});

我不认为这是一个好主意,即使你可以修改本机函数,你应该始终保持你的应用程序范围,所以一个好的方法是不使用全局pushState函数,而是使用你自己的一个:

function historyEventHandler(state){ 
    // your stuff here
} 

window.onpopstate = history.onpushstate = historyEventHandler

function pushHistory(...args){
    history.pushState(...args)
    historyEventHandler(...args)
}
<button onclick="pushHistory(...)">Go to happy place</button>

请注意,如果任何其他代码使用本机pushState函数,则不会获得事件触发器(但如果发生这种情况,则应该检查代码)

终于找到了“正确的”(没有monkeypatching,没有破坏其他代码的风险)方法来做到这一点!它需要为你的扩展添加一个特权(是的,有人在评论中指出了这一点,这是为扩展API,这是所要求的)和使用后台页面(不仅仅是一个内容脚本),但它确实工作。

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

The event callback gets the tab ID, the URL that is being "navigated" to, and other such details. If you need to take an action in the content script on that page when the event fires, either inject the relevant script directly from the background page, or have the content script open a port to the background page when it loads, have the background page save that port in a collection indexed by tab ID, and send a message across the relevant port (from the background script to the content script) when the event fires.

galambalazs的答案monkey补丁window.history. pushstate和window.history。但由于某种原因,它不再为我工作了。这里有一个替代方案,不太好,因为它使用轮询:

(function() {
    var previousState = window.history.state;
    setInterval(function() {
        if (previousState !== window.history.state) {
            previousState = window.history.state;
            myCallback();
        }
    }, 100);
})();

你可以绑定到窗户上。onpopstate事件吗?

https://developer.mozilla.org/en/DOM%3awindow.onpopstate

从文档中可以看出:

popstate的事件处理程序 事件。 对象的popstate事件被分派到 窗口每次激活历史记录 输入的变化。如果历史记录条目 被激活是由呼叫创建的 到history.pushState()或被影响 通过调用history.replaceState(), popstate事件的状态属性 包含历史记录项的副本 状态对象。