现在HTML5引入了历史。pushState来改变浏览器的历史记录,网站开始与Ajax结合使用,而不是改变URL的片段标识符。
不幸的是,这意味着onhashchange再也不能检测到这些调用了。
我的问题是:有没有可靠的方法(hack?);))来检测网站何时使用history.pushState?规范没有说明引发的任何事件(至少我没有找到任何事件)。
我试图创造一个立面,并替换了窗户。我自己的JavaScript对象的历史,但它根本没有任何影响。
进一步解释:我正在开发一个Firefox插件,它需要检测这些变化并采取相应的行动。
我知道几天前有一个类似的问题,问监听一些DOM事件是否有效,但我宁愿不依赖于它,因为这些事件可以出于许多不同的原因生成。
更新:
这里是一个jsfiddle(使用Firefox 4或Chrome 8),显示onpopstate没有触发pushState被调用(或我做错了什么?请随意改进!)。
更新2:
另一个问题是那个窗口。使用pushState时,location不会更新(但我已经在这里读到这个,所以我认为)。
谢谢@KalanjDjordjeDjordje的回答。我试图让他的想法成为一个完整的解决方案:
const onChangeState = (state, title, url, isReplace) => {
// define your listener here ...
}
// set onChangeState() listener:
['pushState', 'replaceState'].forEach((changeState) => {
// store original values under underscored keys (`window.history._pushState()` and `window.history._replaceState()`):
window.history['_' + changeState] = window.history[changeState]
window.history[changeState] = new Proxy(window.history[changeState], {
apply (target, thisArg, argList) {
const [state, title, url] = argList
onChangeState(state, title, url, changeState === 'replaceState')
return target.apply(thisArg, argList)
},
})
})
5.5.9.1事件定义
在导航到会话历史条目时,popstate事件在某些情况下被触发。
根据这个,当你使用pushState时,没有理由触发popstate。但像“推州”这样的事件迟早会派上用场。因为历史记录是一个宿主对象,所以您应该小心使用它,但是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让它处理更多的事件
我宁愿不覆盖本机历史方法,所以这个简单的实现创建了我自己的函数,称为eventtedpush state,它只是调度一个事件并返回history. pushstate()。这两种方式都很好,但我发现这种实现更简洁,因为原生方法将继续像未来开发人员所期望的那样执行。
function eventedPushState(state, title, url) {
var pushChangeEvent = new CustomEvent("onpushstate", {
detail: {
state,
title,
url
}
});
document.dispatchEvent(pushChangeEvent);
return history.pushState(state, title, url);
}
document.addEventListener(
"onpushstate",
function(event) {
console.log(event.detail);
},
false
);
eventedPushState({}, "", "new-slug");
终于找到了“正确的”(没有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.
我不认为这是一个好主意,即使你可以修改本机函数,你应该始终保持你的应用程序范围,所以一个好的方法是不使用全局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函数,则不会获得事件触发器(但如果发生这种情况,则应该检查代码)