For example - if a user only had 32-bit Flash installed and they updated or installed 64-bit Firefox, they'd be annoyed that Flash "broke". That might not be the best or most current example, but it's that kind of thing that held up 64-bit default on Windows.
A lesser issue is that the x86-64 JIT for JS was generally worse in performance than the 32-bit x86 JIT, although this has been fixed for a few years now.
It's not that Firefox couldn't be 64-bit on Windows, it's that the developers didn't feel it was ready to ship it to a large userbase.
At the risk of sounding cynical, some people might consider that a reason to prefer the 64-bit version. ;-)
On Mac I believe both Firefox and Safari used a separate broker process to go between 32-bit plugins and the 64-bit browser. Is it that much messier to do on Windows?
It was much easier to do on OS X than on Windows. That's primarily because on OS X, at least at the time, Firefox was a Universal Binary, meaning there were complete 32-bit and 64-bit binaries for OS X in the same application package. We had been doing this for reasons I won't get into here, but they were unrelated to running plugins with mismatched bit-ness. That being the case though, we "just" (devil is in the details) started a 32-bit child process, which had all the same functionality (e.g. plugin code implementation) as the 64-bit process, and made sure that plugin API data going between the processes was always fixed length (e.g. int32 vs plain int) so there was no misinterpretation.
Mozilla could probably have done something very similar on Windows, but they weren't already installing both 32-bit and 64-bit binaries on every Windows machine. Doing that just for the sake of plugins would, at a minimum, have been a big bullet to bite. And there was/is no nice Universal Binary package equivalent on Windows.
Also, and this gets more nitty-gritty, the NPAPI plugin API on OS X was much easier to get working across two processes of different bit-ness. This was mainly because we had recently revised the OS X NPAPI (we called it Cocoa NPAPI or something like that) and got Adobe and many others to switch. The revised API was much easier to work with. The ancient Windows NPAPI was, and is, a total f*ing mess.
What is the advantage of having "int", "long" types that just give minimum byte requirements (with associated storage classes, etc.) instead of more specified types like int32_t and uint8_t? In the end you deal with fixed-size integers or not (using some bignum library), but if you deal with them you have to take their byte length into acount in your code anyway I would say (to deal with overflows and stuff).
The advantage is that these more specific types might not even exist. For instance, some machines had 36-bit words, so a 32-bit type would have to be emulated, at the cost of extra instructions.
It took a little while, but eventually C got https://en.wikibooks.org/wiki/C_Programming/stdint.h (in C99 :( ).
If it was ever so simple, specially given UB and compiler specific semantics across all those compilers and operating systems on the 90's (before C was hardly relevant outside UNIX).
stdint even includes 'int with at least x bits' & 'fastest int type with at least x bits'
The other compilers vary. The C standard only specifies that int has to be at least 32 bits. That's one of the many issues of porting from 32 to 64 bits.
That's not true for MinGW, and for good reasons (deviating from the ABI followed by the OS and all libraries would be stupid).
What makes porting complicated in general is dealing with pointers and handles. These are 32-bit or 64-bit, depending on the architecture. Unfortunately, it was pretty common for old code to assume that they're always 32-bit, and e.g. shove them into ints (WinAPI itself was guilty of this on many occasions).
Technically, such a thing was never portable - not even with longs - until C99 brought us intptr_t, there was no guarantee that any integral type was large enough to hold a pointer. But in practice, it worked, so people did it.
But the real reason 64-bit Firefox is the default on 64-bit Linux, is that 64-bit Linux usually doesn't have the 32-bit compatibility libraries installed by default. On Windows, on the other hand, 32-bit compatibility libraries are always available.
Speaking strictly about things surrounding your code, as opposed to the code itself, there's stuff you just don't have on Linux.
On Linux pretty much everything is open source and provided by your distro, built for your architecture. The need for compatibility layers are absolutely minimal. This has a resounding positive effect in that quite a lot of things can be assumed fixed and in place.
On Windows, not so much.
Most applications are pre-built by others. Most are 32-bit. So Microsoft tries to maintain backwards compatible execution environments for both 32-bit code and 64-bit code in Windows.
This means that 32-bit processes trying to access system DLLs, will need to find 32-bit system DLLs somewhere. Same for 64-bit. So you have duplication of system-DLLs, in different folders. You can't just assume a standard, fixed path will lead you to the right place.
For the COM subsystem, all the available components an application can access is stored in the registry, alongside their physical path on disk.
You may need different DLL-files for these COM components in 32-bit processes and 64-bit processes. So now you need the registry to point to different locations based on the bitness of the application running. So yeah. Portions (but not all parts!) of the registry is also duplicated with different references for different processes.
That means that when going from a 32-bit process to 64-bit process, all things you've previously written to the registry... May not be there for you to read in your 64-bit process.
And you better make sure your installer is the same bitness as your process... Or you know what? That 32-bit installer wont be allowed to prime the 64-bit registry your application needs!
This may mean that all COM components you've registered in your installer, now is inaccessible to your (and other) applications because they are 64-bit.
And I'm sure the list goes on. These are just the most obvious non-pointer based things I can think of which complicates matters.
In short: On Windows... Keeping everything 32-bit and closing your eyes to all terrible things mentioned above will ensure your application keeps working, because Microsoft put in the effort to guarantee that.
The second you step into 64-bit, you need to have all these things solved. There's no going half-way. So most people who has little to benefit from 64-bit executables simply don't.
Linux also uses different directories and has "potentially a lot of other issues". Windows uses LLP64 rather than LP64[1] but IMO that's just different not clearly/necessarily better or worse.
[1] https://blogs.msdn.microsoft.com/oldnewthing/20050131-00/?p=...