History API is now supported in every popular browser. It seems there's no need for hash fallbacks, _escaped_fragment_ tricks or other workarounds anymore. Cool libraries from 2013 like History.js seem useless now. But there are some things where I'm not sure - for example title handling seems tricky beacuse apparently title argument in pushState doesn't do anything.
My question is, can I actually rely on the History API to behave consistently across browsers, or do I still need some browser-specific code? This also means: do I need integration tests running in different browsers to test my code then? And if there are inconsistencies, what are they? (Note I'm interested only in modern browsers, so no IE<11).
Maybe someone who implemented routing for a big SPA could share their experience?
Note: This is not a complete answer so I'm not expecting a bounty but it still answers some concerns so I decided to put it as an answer
There are still some differences as with most APIs (you won't believe how inconsistent classList is between browsers); the question is mostly how severe they are.
pushState is most commonly used in SPAs and they seem to ignore the object parameter and mostly handle the URL. This means bugs related to state object handling may be less "visible" to the general public.
The only issue I've recently found that influenced what I was doing is that in IE & Edge (even 14) history.state is a getter so it gets a fresh object instance on every access. This means you can't cache the state object and compare it to history.state to see if a new one was pushed. Here's the bug report: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/944050/
Also, note that no browser supports setting title via pushState and no one ever will, it's too late. If a browser started supporting that, pushState-using sites would suddenly clear all titles in browser history because code is passing empty strings there. You just have to accept that the second parameter is useless.
Actually you can see all the supported browsers by history lib: https://github.com/browserstate/history.js/#browsers-tested-and-working-in
And buglist solved by this lib, from the same page:
HTML5 Browsers
Chrome 8 sometimes does not contain the correct state data when traversing back to the initial state
Safari 5, Safari iOS 4 and Firefox 3 and 4 do not fire the onhashchange event when the page is loaded with a hash
Safari 5 and Safari iOS 4 do not fire the onpopstate event when the hash has changed unlike the other browsers
Safari 5 and Safari iOS 4 fail to return to the correct state once a hash is replaced by a replaceState call / bug report
Safari 5 and Safari iOS 4 sometimes fail to apply the state change under busy conditions / bug report
Google Chrome 8,9,10 and Firefox 4 prior to the RC will always fire onpopstate once the page has loaded / change recommendation
Safari iOS 4.0, 4.1, 4.2 have a working HTML5 History API - although the actual back buttons of the browsers do not work, therefore we treat them as HTML4 browsers
None of the HTML5 browsers actually utilise the title argument to the pushState and replaceState calls
HTML4 Browsers
Old browsers like MSIE 6,7 and Firefox 2 do not have a onhashchange event
MSIE 6 and 7 sometimes do not apply a hash even it was told to (requiring a second call to the apply function)
Non-Opera HTML4 browsers sometimes do not apply the hash when the hash is not urlencoded
All Browsers
State data and titles do not persist once the site is left and then returned (includes page refreshes)
State titles are never applied to the document.title
This might tell you something about existing differences.
There were some issues with History API in Androids before 4 version, but generally it work well across all main browsers.
Related
Problem:
I need a client-side Javascript solution (jQuery is fine) where an event in one browser window/tab can be broadcast to other windows/tabs on the same domain. For example, if a cart is updated in tab A, tabs B and C get notified so we can update some info on the page, or notify the user that page is stale, or something like that.
What I've tried:
The BroadcastChannel API fits my needs perfectly, but does not work in IE 11 or Safari.
So I tried this polyfill so I could use the BroadcastChannel API everywhere. It worked in IE, but not in Safari (I believe BroadcastChannel remained undefined).
I then tried sysend.js , which uses BroadCastChannel if it's available, otherwise fakes it using localStorage. Their demo page works fine in Safari, but on my site, I found it worked in Safari 9, but not 10-12 (tested using BrowserStack and one real Mac running Safari 12). Debugging their script, it seems the storage event that's supposed to fire when localStorage in a different tab is changed simply doesn't fire. But this is actually only a problem when you have document.domain set, which I do.
I believe this is the same as this old Chrome bug. But whereas Chrome had that issue from 2012-2017, Safari apparently introduced it around 2017?
I haven't found anyone else discussing this bug in Safari, but I can prove this pretty easily. Open two tabs that use the same document.domain value and run these scripts:
Tab A:
$(window).on("storage", function (e) {
alert('storage was modified');
});
Tab B:
localStorage.setItem("test", "123");
In Safari 9, Tab A will pop the alert. In Safari 10+, it won't.
If I remove the document.domain, it works. Note that we are using document.domain on these pages, but they are actually on the same domain in this case. However document.domain is needed for other scenarios across the site, so I can't remove it.
I also tried looking at store.js. It has an event system, but it only seemed to work within the same tab (in any browser). Unless I'm missing something.
So, is there any BroadcastChannel polyfill or similar library that actually works in Safari 10+ when you have a document.domain set? Or any other way to do this?
Notes:
I'm aware that BroadcastChannel and the "storage" event for localStorage only fire for tabs other than the current one. That is not my issue, and is actually desirable to me.
I've also seen posts that make me believe an alternate solution relying on localStorage likely will not work in Private Browsing mode in Safari. EDIT: It appears this was fixed in Safari 11 so it does work, but in my tests it didn't share localStorage with any other tabs, even other Private tabs in the same window. So that's not much help. Ideally, a solution would account for this as well, but at this point I'd be happy with anything that worked in Safari 10+ for me. I did see an example in the store.js project where they said they made it so it would fallback to cookies in that case, so it sounds possible at least.
I tried to think about other ways to do this with a setInterval that checks the localStorage for updates every few seconds or something. Even in theory that seems really hacky and unreliable (how would you know when all pages have "received" the update so you can clear it out?). And how would you know when to do it this hacky way instead of one of the preferred methods using localStorage? Safari 10+ is going to report that it supports localStorage so you can't really feature detect it, right? It "supports" it, it just doesn't work correctly.
I found a workaround, but I'll leave this question open because I'd still love to see a better answer.
As a last resort, I ended up toggling between two different ways of performing the messaging, depending on browser.
Basically, if it's Safari I use https://github.com/pubkey/broadcast-channel (you can get the minified vanilla JS version from https://github.com/pubkey/broadcast-channel/blob/master/dist/lib/browser.min.js). This seems to work in all versions even when you have a document.domain set. I think it uses indexDB in this case, which seems like total overkill, but I don't seem to have a choice.
It also works in Safari private windows in newer versions of Safari. I have try catches in place in all my script for older versions of Safari in private mode where it would otherwise throw errors.
If it's not Safari, I use sysend.js which uses BroadcastChannel by default and falls back to localStorage for cases like IE 11.
Please not not turn this into a discussion about which browser is better and the ethics of forcing a browser. It's an intranet, and it's what I am required to do so everyone calm down =o)
I need to prevent employees from trying to bypass the check to not use their preferred browser instead of the company mandated one + version. The decision was made based on security, compatibility, costs, and the use of company made Firefox extensions.
I came across this Force users to use Chrome/FireFox instead of IE which I can do easily in PHP to force use of Firefox, however it relies on the useragent which can easily be changed in numerous browsers and with the use of plugins.
Is there a JavaScript solution that I can use that DOES NOT check the useragent or any value that can be 'easily' modified by a user/plugin? It would need to detect if the browser is Firefox and what version it is. Site uses jQuery so if it can be done using that, however not required then by all means yes. I just am not aware of what ways to detect a browser and it's version that there are without checking useragent.
I remember way back in the day for detecting Netscape or some browser checking for document.all was used instead of useragent, so I'm guessing something similar, which only Firefox will have.
Thank you very much in advance!
Try this: http://jsfiddle.net/DerekL/62exH/
Because Firefox handles onpopstate differently than other browsers, you can then use this to detect if it is Firefox or not. This can not be changed by user or script. But the disadvantage is you can only get the version number by doing navigator.appVersion.
You can only try but cannot succeed in forcing a browser. That being said you can strip down the CSS in other browsers which may completely make your site close to unusable in other browsers.
To make your CSS only work with Firefox you can try approaches given # Targeting only Firefox with CSS
When I use JSConsole and type window.history it dosen't contain any method for pushState. So what have happend? Is it removed in Android 4.0 in the default browser or how can I use it?
Yes, it is a regression bug, but they don't seem to be very interested in fixing it:
http://code.google.com/p/android/issues/detail?id=23979 vote for it.
Since most manufacturers usually customize the browser in one or another way, they might implement it themselves. I have however never came across any ICS phone which fully supports the history api. can't say I have been looking though.
EDIT: It has been claimed that it is fixed in 4.0.4, however only in the sense that the methods are there and do what you expect them to do, but the URL bar is not updated.
History API is well supported in Android 2.2 and 2.3, but versions 3 and 4 do not. There is no word on whether it will be put back in.
read more
But I think if you use chrome in Android 4 you can use this feature.
Canvas clearing gets vastly different perfomance on different browsers. See http://jsperf.com/canvas-clearing2 .
I need to clear a canvas every frame and how I do it has a huge impact on mobile safari vs Desktop safari performance. Desktop Safari likes canvas.width = width but mobile safari prefers canvas.drawRect() .
Is there a way to detect what browser is what an run different JavaScript based on it? I would prefer to do this through JavaScript rather than server side.
Also, I've found that jQuery's $.browser doesn't help because it doesn't distinguish between mobile safari and desktop safari. the navigator object has similar problems.
Targeting specific browsers is always a bit of a problem. While there are certainly ways to do it, it's not a particularly maintainable way to do it because there are lots of different browsers and versions of each browser and those browsers change over time, thus which browsers are optimized by which code can be changing all the time. This creates quite a maintenance headache. For example, mobile Safari on an iPod Touch has very different performance characteristics than mobile Safari on each different generation of iPhone or iPad.
So ... instead of trying to detect the type of browser, it's much better to either do feature detection or performance detection and dynamically adjust based on how any given browser reacts. Done right, this can work equally well for all browsers, even browsers you've never seen or that aren't even released yet.
In your case, you could devise a quick performance test that tests the performance of each of your two methods. If there's really a big performance difference between the two methods, then you could probably tell the difference in a matter of a few hundred milliseconds, set a cookie on the local browser indicating the method that works best and then just use the preferred method in that browser from then on. If you wanted to, you could let the cookie expire every few months (so it would get retested every once in a while) or you could put the exact browser version into the cookie too and reruns the tests and set a new cookie if the browser version every changed (software upgrades).
In this way, your code would always be using the fastest version of your code in all browsers, now and forever without you ever having to maintain/test zillions of browsers to know which should be used for each.
I'm with jfriend00 on this if you're looking at longer-lasting, closer-to-sure-proof solutions. However, you can still pull quite a bit of information with certain functions in Javascript and use that to your advantage.
Check this out:
http://notnotmobile.appspot.com/
Other Resources
Navigator Object: http://www.w3schools.com/js/js_browser.asp
Browser Detect: http://www.quirksmode.org/js/detect.html
I've recently seen some information about avoiding coding to specific browsers an instead using feature/bug detection. It seems that John Resig, the creator of jQuery is a big fan of feature/bug detection (he has an excellent talk that includes a discussion of this on YUI Theater). I'm curious if people are finding that this approach makes sense in practice? What if the bug no longer exists in the current version of the browser (It's an IE6 problem but not 7 or 8)?
Object detection's greatest strength is that you only use the objects and features that are available to you by the client's browser. In other words given the following code:
if (document.getFoo) {
// always put getFoo in here
} else {
// browsers who don't support getFoo go here
}
allows you to cleanly separate out the browsers without naming names. The reason this is cool is because as long as a browser supports getFoo you don't have to worry which one it is. This means that you may actually support browsers you have never heard of. If you target user agent strings find the browser then you only can support the browsers you know.
Also if a browser that previously did not support getFoo gets with the program and releases a new version that does, you don't have to change your code at all to allow the new browser to take advantage of the better code.
What if the bug no longer exists in the current version of the browser (It's an IE6 problem but not 7 or 8)?
That's the whole point of feature/bug detection. If the browser updates its features or fixes a bug, then the feature/bug detecting code is already up to date. If you're doing browser sniffing on the other hand, you have to change your code every time the capabilities of a browser changes.
Version and User Agent parsing remind me of stereotyping or racial profiling. Object detection is the way to go in my opinion. The book jQuery in Action does a good job of pointing out the fine details of why.
Well, if it is a problem in IE 6, but not IE 7 or IE 8 then the feature/bug detection mechanism will mark it as not a problem and use the appropriate functions.
It makes sense in practice, browser sniffing causes many issues, what if you don't update your signatures in time for some new browser that is released? You are now locking out potential customers, and visitors because you believe that their browser is unable to support something that indeed it can.
So yes, it makes sense, and your second question is moot because of the fact that it does the detection in the first place, if it is no longer a bug everything will work as expected without the work around that is in place if the bug was there!