背靠背和pushState——一个关于两段历史的故事

.back, and .pushState - a tale of two histories

本文关键字:一个 于两段 历史 的故事 pushState 背靠背      更新时间:2023-09-26

与其说这是一个问题,不如说这是对一个有趣问题的发现。这也是一种"从我的失败中学习"

我正在尝试为IE编写HTML5历史鸭子穿孔的单元测试(使用window.hash代替状态维护)。duckpunch按预期工作,在用户测试期间,我在IE、Chrome和Firefox中得到了一致的结果。

问题出在单元测试上。在它们中,我对history.pushState()、.replaceState、.back()和.forward()进行了各种组合。这些在Firefox和IE中运行良好,但Chrome给了我完全不一致的结果。下面的答案解释了原因。

考虑以下内容:

var originalPath = window.location.pathname.toString();
history.pushState({ value: 'Foo' }, '', 'state-1');
history.pushState({ value: 'Bar' }, '', 'state-2');
history.pushState({ value: 'Baz' }, '', 'state-3');
history.back();
history.back();
console.log(history.state.value);
//So we can hit refresh and see the results again...
setTimeout(function () {
    history.replaceState(null, '', originalPath);
}, 250);

人们会期望这段代码返回"Foo"——在Firefox和我的IE duck punch中,这正是它所做的——但在Chrome中,它的响应是"Baz"。

经过一番调查,我解决了这个问题:IE和Firefox同步更新历史记录,如果需要加载任何页面,则异步更新。Chrome似乎立即异步运行。

证据:

window.onpopstate = function () {
    console.log('ping');
}
history.pushState({ value: 'Foo' }, '', 'state-1');
history.back();
console.log('pong');

在Firefox中,返回"ping"pong'-表示事件是作为history.back()调用的一部分进行调度的。在Chrome中,返回"pong"ping’—表示事件已放入队列中进行调度。

如果这个事件调度模型没有被用来管理历史和位置对象的状态,这不会那么糟糕——但显然是这样

window.onpopstate = function () {
    console.log('Event...', history.state, window.location.pathname.toString());
}
history.pushState({ value: 'Foo' }, '', 'state-1');
history.back();
console.log('Inline...', history.state, window.location.pathname.toString());

这是一个有趣的怪癖,需要使用jQueryDeferred链来正确处理我的单元测试。我对此不是特别高兴,但你能做什么?

为了处理单元测试中的异步返回事件,我使用了HistoryJS和Jasmine。这是一个更新历史事件中的计数器以跟踪chrome何时处理事件的案例,以及Jasmine的异步支持以阻止单元测试,直到我们看到事件更改:

递增计数器:

History.Adapter.bind(window, 'statechange', function() {
    // Increment the counter
    window.historyEventCounter++;
});

Jasmine异步单元测试。waitsFor将阻塞,直到历史事件发生:

describe('back navigation', function () {
    it('should change url', function () {
        var initialHistoryEventCount;
        // Cache the initial count
        runs(function() {
            initialHistoryEventCount = window.historyEventCounter;
            History.back();
        });
        // Block until the event counter changes
        waitsFor(function() {
            return (initialHistoryEventCount !== app.historyEventCounter);
        }, "The page should be navigated back", 1000);
        // Now confirm the expected back behaviour. Event failures will time-out.
        runs(function() {
            expect(window.location.href).toEqual("http:// my back url");
            // ... more tests about the page state
        });
    }
}
相关文章: