history.pushState를 통해 히스토리 변경 사항에 대한 알림을받는 방법은 무엇입니까?
이제 HTML5 history.pushState
가 브라우저 기록을 변경하기 위해 도입 했으므로 웹 사이트는 URL의 조각 식별자를 변경하는 대신 Ajax와 함께 이것을 사용하기 시작합니다.
안타깝게도이 전화를 더 이상 감지 할 수 없습니다 onhashchange
.
내 질문은 : 웹 사이트가 history.pushState
언제 사용되는지 감지하는 신뢰할 수있는 방법이 있습니까? 사양에는 발생한 이벤트에 대해 언급되어 있지 않습니다 (적어도 아무것도 찾을 수 없습니다).
파사드를 만들려고했는데 window.history
내 고유의 JavaScript 객체 로 바뀌 었지만 전혀 효과가 없었습니다.
추가 설명 : 이 변경 사항을 감지하고 그에 따라 행동 해야하는 Firefox 추가 기능을 개발 중입니다.
며칠 전에 일부 DOM 이벤트를 듣는 것이 효과적 일지에 대한 비슷한 질문이 있었지만 이러한 이벤트는 여러 가지 이유로 생성 될 수 있기 때문에 그에 의존하지 않을 것입니다.
최신 정보:
다음은 호출 onpopstate
될 때 트리거되지 않는 것을 보여주는 jsfiddle (Firefox 4 또는 Chrome 8 사용)입니다 pushState
(또는 내가 잘못하고 있습니까? 자유롭게 개선하십시오!).
업데이트 2 :
또 다른 (측면) 문제는 window.location
사용할 때 업데이트되지 않는다는 것 입니다 pushState
(그러나 이미 여기에 대해 읽었습니다).
5.5.9.1 이벤트 정의
popstate 세션 기록 항목을 탐색 할 때 이벤트는 어떤 경우에 발생합니다.
이것에 따르면,를 사용할 때 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
당신은 window.onpopstate
이벤트에 바인딩 할 수 있습니까?
https://developer.mozilla.org/en/DOM%3awindow.onpopstate
문서에서 :
윈도우의 popstate 이벤트를위한 이벤트 핸들러.
활성 내역 항목이 변경 될 때마다 popstate 이벤트가 창에 전달됩니다. 활성화 된 히스토리 항목이 history.pushState ()에 대한 호출로 작성되었거나 history.replaceState ()에 대한 호출의 영향을받는 경우, popstate 이벤트의 state 특성에는 히스토리 항목의 상태 오브젝트 사본이 포함됩니다.
나는 이것을 사용했다.
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
므로 랩이나 무언가를 감싸고 싶을 수도 있습니다. 나는 그것에 대해 신경 쓰지 않았습니다.)
이 주제에는보다 현대적인 솔루션이 필요하다고 생각합니다.
나는 nsIWebProgressListener
주변에 있었을 것이라고 확신 했지만 아무도 그것을 언급하지 않은 것에 놀랐습니다.
프레임 스크립트에서 (e10s 호환) :
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW | Ci.nsIWebProgress.NOTIFY_LOCATION);
Then listening in the onLoacationChange
onLocationChange: function onLocationChange(webProgress, request, locationURI, flags) {
if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT
That will apparently catch all pushState's. But there is a comment warning that it "ALSO triggers for pushState". So we need to do some more filtering here to ensure it's just pushstate stuff.
And: resource://gre/modules/WebNavigationContent.js
Finally found the "correct" way to do this! It requires adding a privilege to your extension and using the background page (not just a content script), but it does work.
The event you want is browser.webNavigation.onHistoryStateUpdated
, which is fired when a page uses the history
API to change the URL. It only fires for sites that you have permission to access, and you can also use a URL filter to further cut down on the spam if you need to. It requires the webNavigation
permission (and of course host permission for the relevant domain(s)).
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's answer monkey patches window.history.pushState
and window.history.replaceState
, but for some reason it stopped working for me. Here's an alternative that's not as nice because it uses polling:
(function() {
var previousState = window.history.state;
setInterval(function() {
if (previousState !== window.history.state) {
previousState = window.history.state;
myCallback();
}
}, 100);
})();
Since you're asking about a Firefox addon, here's the code that I got to work. Using unsafeWindow
is no longer recommended, and errors out when pushState is called from a client script after being modified:
Permission denied to access property history.pushState
Instead, there's an API called exportFunction which allows the function to be injected into window.history
like this:
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});
Well, I see many examples of replacing the pushState
property of history
but I'm not sure that's a good idea, I'd prefer to create a service event based with a similar API to history that way you can control not only push state but replace state as well and it open doors for many other implementations not relying on global history API. Please check the following example:
function HistoryAPI(history) {
EventEmitter.call(this);
this.history = history;
}
HistoryAPI.prototype = utils.inherits(EventEmitter.prototype);
const prototype = {
pushState: function(state, title, pathname){
this.emit('pushstate', state, title, pathname);
this.history.pushState(state, title, pathname);
},
replaceState: function(state, title, pathname){
this.emit('replacestate', state, title, pathname);
this.history.replaceState(state, title, pathname);
}
};
Object.keys(prototype).forEach(key => {
HistoryAPI.prototype = prototype[key];
});
If you need the EventEmitter
definition, the code above is based on the NodeJS event emitter: https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/events.js. utils.inherits
implementation can be found here: https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/util.js#L970
As standard states:
Note that just calling history.pushState() or history.replaceState() won't trigger a popstate event. The popstate event is only triggered by doing a browser action such as clicking on the back button (or calling history.back() in JavaScript)
we need to call history.back() to trigeer WindowEventHandlers.onpopstate
So insted of:
history.pushState(...)
do:
history.pushState(...)
history.pushState(...)
history.back()
'Programming' 카테고리의 다른 글
java 8 ZonedDateTime과 OffsetDateTime의 차이점은 무엇입니까? (0) | 2020.06.21 |
---|---|
MVC2에서 Html.Partial (view, model)과 Html.RenderPartial (view, model)의 차이점은 무엇입니까? (0) | 2020.06.21 |
브라우저에 마우스가없고 터치 전용임을 감지 (0) | 2020.06.21 |
Swift에서 가변 개수의 인수를 가진 함수에 배열 전달 (0) | 2020.06.21 |
SQL Server NOLOCK 및 조인 (0) | 2020.06.21 |