如何修复backbone.js的iOS 9 window.location问题

时间:2015-09-28 04:11:09

标签: javascript ios backbone.js

在iOS 9测试版中,引入了一个错误(https://openradar.appspot.com/22186109),这意味着将window.location.hash设置为某个新值,直到"某个时间"后来。 "某些时间的确切含义"是争论,但似乎至少要到下一个js事件循环滴答。

举个例子,你会做这样的事情:

location.hash = "abcd";
console.log(location.hash); // probably not "abcd"

setImmediate(function () {
  console.log(location.hash); // probably "abcd"
});

这在我们的网站中引起了一个问题,即使用带有hashchange导航的backbone.js路由器。想象一下,在这个例子的开头,路线是' foo'和之前的' foo'在历史上是' quux'。

我们这样做:

router.navigate('bar', { trigger: true, replace: false });

将调用backbone.js历史类并调用路由器来处理导航:

function onFooRoute() {
  router.navigate('baz', { trigger: true, replace: true });
}

所以我们开始了#foo',去了' bar'然后去了巴斯' (对于最后一个,将replace设置为true)。

我们希望我们应该最终选择“baz”,然后回到历史会让我们回到“foo”。

我们看到的问题是完成了所有这些,一旦所有事情都安定下来,通过window.history.back()返回历史记录,我们就会回到' quux'而不是' foo'。看来,由于这个bug,条目' bar'不会进入浏览器历史记录堆栈,因此导航到' baz'取代' foo'而不是' bar'。

我的问题是如何最好地解决这个问题?我可以修补backbone.js来解决这个问题吗?

1 个答案:

答案 0 :(得分:0)

我们通过覆盖骨干中的几种方法解决了这个问题:

function isIOS9UIWebView() {
    var userAgent = window.navigator.userAgent;
    return /(iPhone|iPad|iPod).* OS 9_\d/.test(userAgent) && !/Version\/9\./.test(userAgent);
}

//override the backbone.history.loadUrl() and backbone.history.navigate()
//to fix the navigation issue (location.hash not change immediately) on iOS9
if (isIOS9UIWebView()) {
    Backbone.history.loadUrl = function (fragment?: any, oldHash?: any) {
        fragment = this.fragment = this.getFragment(fragment);
        return _.any(this.handlers, function (handler: any): boolean {
            if (handler.route.test(fragment)) {
                function runCallback() {
                    handler.callback(fragment);
                }

                function wait() {
                    if (oldHash === location.hash) {
                        window.setTimeout(wait, 50);
                    } else {
                        runCallback();
                    }
                }
                wait();
                return true;
            }
        });
    };

    Backbone.history.navigate =
    // Attempt to load the current URL fragment. If a route succeeds with a
    // match, returns `true`. If no defined routes matches the fragment,
    // returns `false`.
    function (fragment, options) {
        var pathStripper = /#.*$/;
        if (!Backbone.History.started) return false;
        if (!options || options === true) options = { trigger: !!options };

        var url = this.root + (fragment = this.getFragment(fragment || ''));

        // Strip the hash for matching.
        fragment = fragment.replace(pathStripper, '');

        if (this.fragment === fragment) return;
        this.fragment = fragment;

        // Don't include a trailing slash on the root.
        if (fragment === '' && url !== '/') url = url.slice(0, -1);
        var oldHash = location.hash;
        // If pushState is available, we use it to set the fragment as a real URL.
        if (this._hasPushState) {
            this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);

            // If hash changes haven't been explicitly disabled, update the hash
            // fragment to store history.
        } else if (this._wantsHashChange) {
            this._updateHash(this.location, fragment, options.replace);
            if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
                // Opening and closing the iframe tricks IE7 and earlier to push a
                // history entry on hash-tag change.  When replace is true, we don't
                // want this.
                if (!options.replace) this.iframe.document.open().close();
                this._updateHash(this.iframe.location, fragment, options.replace);
            }

            // If you've told us that you explicitly don't want fallback hashchange-
            // based history, then `navigate` becomes a page refresh.
        } else {
            return this.location.assign(url);
        }

        if (options.trigger) return this.loadUrl(fragment, oldHash);
    }
}