Edge Interop Issues

As we finish up the next release of Windows 10 (Fall 2018), my team is hard at work triaging incoming bugs.

Many such bugs take the form “Edge does the wrong thing for this page. works okay.

This post is designed to be an (ever-growing) index of some of the behavioral deltas that are the root cause of such issues:

Edge doesn’t allow navigation to DATA urls, even when they’d otherwise be converted to file downloads.

Using pushState or replaceState with |undefined| as the URL argument shows “undefined” in the Address box in Edge/IE but not Chrome or Firefox.

IE/Edge strip the Content-Encoding header from a compressed response; Firefox and Chrome leave the header in. For XmlHttpRequest’s getAllResponseHeaders, IE and Firefox maintain the case of HTTP Response header names while Chrome/Edge/Safari do not.

Chrome recognizes that a file with a .JSON extension has the type application/json (and vice versa) while IE/Edge only recognize that when the registry is configured with that mapping.

Chrome includes a hack that works around certificates that do not exactly match the domain on which they are served. Firefox, Edge, and IE do not include this hack, leading to a Certificate Name Mismatch Error when loading:


Edge does not fully support the URL standard, meaning that URLs of the form http:/ (note the missing slash) do not work as expected.

(…to be continued…)



Script-Generated Download Files

As we finish up the next release of Windows 10, my team is hard at work triaging incoming bugs. Here’s a pattern that has come up a few times this month:

Bug: I click download in Edge:

DownloadButtonbut I end up on an error page:


Womp womp.

If you watch the network traffic, you’ll see that no request even hits the network in the failing case. But, if you carefully scroll that ugly error URL to see the middle, the source of the problem appears:

ms-appx-web://microsoft.microsoftedge/assets/errorpages/dnserror.html?ErrorStatus=0x80704006&NetworkStatusSupported=1#data:text/csv;charset=UTF-8, ID,Datetime,Type,Status,Note,From,To,Amount%20(total),Amount%20(fee),Funding%20Source,Destination%0D%0A

The error shows that Edge failed to navigate to a URL with the Data URI scheme.

Ever since we introduced support for DATA URLs a decade ago in Internet Explorer 8, they’ve been throttled with one major limitation: You cannot navigate to these URIs at the top level of the browser. Edge loosened things up so that Data URLs under 4096 characters can be used as the source of IFRAMEs, but the browser will not navigate to a data URL at the top level.

(Yes, this error page could use some love.)

Now, you might remember that last winter, Chrome took a change to forbid top-level navigation to data URIs (due to spoofing concerns), but that restriction contains one important exception: navigations that get turned into downloads (due to their MIME type being one other than something expected to render in the browser) are exempted. So this scenario sorta works in Chrome. (I say “sorta” because the authors of this site failed to specify a meaningful filename on the link, so the file downloads without the all-important .csv extension).


So, does IE/Edge’s restriction on Data URIs mean that webdevs cannot generate downloadable files dynamically in JavaScript in a way that works in all browsers?

No, of course not.

There are many alternative approaches, but one simple approach is to just use a blob URL, like so:

  var text2 = new Blob(["a,b,c,d"], { type: 'text/csv'});
  var down2 = document.createElement("a"); = "simple.csv";
  down2.href = window.URL.createObjectURL(text2);
  down2.innerText="I have a download attribute. Click me";

When the link is clicked, the CSV file is downloaded with a proper filename.



browsers, security, web

CORS and Vary

Yesterday, I started looking a site compatibility bug where a page’s layout is intermittently busted. Popping open the F12 Tools on the failing page, we see that a stylesheet is getting blocked because it lacks a CORS Access-Control-Allow-Origin response header:


We see that the client demands the header because the LINK element that references it includes a crossorigin=anonymous directive:

crossorigin="anonymous" href="//" type="text/css" />

Aside: It’s not clear why the site is using this directive. CORS is required to use  SubResource Integrity, but this resource does not include an integrity attribute. Perhaps the goal was to save bandwidth by not sending cookies to the “s” (static content) domain?

In any case, the result is that the stylesheet sometimes fails to load as you navigate back and forward.

Looking at the network traffic, we find that the static content domain is trying to follow the best practice Include Vary: Origin when using CORS for access control.

Unfortunately, it’s doing so in a subtly incorrect way, which you can see when diffing two request/response pairs for the stylesheet:


As you can see in the diff, the Origin token is added only to the response’s Vary directive when the request specifies an Origin header. If the request doesn’t specify an Origin, the server returns a response that lacks the Access-Control-* headers and also omits the Vary: Origin header.

That’s a problem. If the browser has the variant without the Access-Control directives in its cache, it will reuse that variant in response to a subsequent request… regardless of whether or not the subsequent request has an Origin header.

The rule here is simple: If your server makes a decision about what to return based on a what’s in a HTTP header, you need to include that header name in your Vary, even if the request didn’t include that header.


PS: This seems to be a pretty common misconfiguration, which is mentioned in the fetch spec:

CORS protocol and HTTP caches

If CORS protocol requirements are more complicated than setting `Access-Control-Allow-Origin` to * or a static origin, `Vary` is to be used.

Vary: Origin

In particular, consider what happens if `Vary` is not used and a server is configured to send `Access-Control-Allow-Origin` for a certain resource only in response to a CORS request. When a user agent receives a response to a non-CORS request for that resource (for example, as the result of a navigation request), the response will lack `Access-Control-Allow-Origin` and the user agent will cache that response. Then, if the user agent subsequently encounters a CORS request for the resource, it will use that cached response from the previous non-CORS request, without `Access-Control-Allow-Origin`.

But if `Vary: Origin` is used in the same scenario described above, it will cause the user agent to fetch a response that includes `Access-Control-Allow-Origin`, rather than using the cached response from the previous non-CORS request that lacks `Access-Control-Allow-Origin`.

However, if `Access-Control-Allow-Origin` is set to * or a static origin for a particular resource, then configure the server to always send `Access-Control-Allow-Origin` in responses for the resource — for non-CORS requests as well as CORS requests — and do not use `Vary`.


browsers, security

Edge EV UI Requires SmartScreen

A user recently noticed that when loading in Microsoft Edge, the UI shown was the default HTTPS UI (a gray lock):


Instead of the fancier “green” UI shown for servers that present Extended Validation (EV) certificates:EV-for-Paypal

The user observed this on some Windows 10 machines but not others.

The variable that differed between those machines was the state of the Menu > Settings > Advanced > Windows Defender SmartScreen setting.

Edge only shows the green EV user interface when SmartScreen is enabled.

IE 11

Internet Explorer 11 on Windows 10 behaves the same way as prior versions of IE going back to IE7– the green EV UI requires either SmartScreen be enabled or that the option Tools > Internet Options > Advanced > Security > Check for Server Certificate Revocation be enabled.


The Chrome team recently introduced a new setting, exposed via the chrome://flags/#simplify-https-indicator page, that controls how EV certificates are displayed in their Security Chip. A user (or a field trial) can configure sites with EV certificates to display using the default HTTPS UI.




browsers, security

Stop Spilling the Beans

I’ve written about Same Origin Policy a bunch over the years, with a blog series mapping it to the Read/Write/Execute mental model.

More recently, I wrote about why Content-Type headers matter for same-origin-policy enforcement.

I’ve just read a great paper on cross-origin infoleaks and current/future mitigations. If you’re interested in browser security, it’s definitely worth a read.

browsers, dev, security

Building your .APP website with NameCheap and GitHub Pages–A Visual Guide

I recently bought a few new domain names under the brand new .app top-level-domain (TLD). The .app TLD is awesome because it’s on the HSTSPreload list, meaning that browsers will automatically use only HTTPS for every request on every domain under .app, keeping connections secure and improving performance.

I’m not doing anything terribly exciting with these domains for now, but I’d like to at least put up a simple welcome page on each one. Now, in the old days of HTTP, this was trivial, but because .app requires HTTPS, that means I must get a certificate for each of my sites for them to load at all.

Fortunately, GitHub recently started supporting HTTPS on GitHub Pages with custom domains, meaning that I can easily get a HTTPS site up in running in just a few minutes.

1. Log into GitHub, go to your Repositories page and click New:

2. Name your new repository something reasonable:

3. Click to create a simple README file:

4. Edit the file

5. Click Commit new file

6. Click Settings on the repository

7. Scroll to the GitHub Pages section and choose master branch and click Save:

8. Enter your domain name in the Custom domain box and click Save

9. Login to NameCheap (or whatever DNS registrar you used) and click Manage for the target domain name:

10. Click the Advanced DNS tab:

11. Click Add New Record:


12. Enter four new A Records for host of @ with the list of IP addresses GitHub pages use:

13. Click Save All Changes.

14. Click Add New Record and add a new CNAME Record. Enter the host www and a target value of Click Save All Changes: 

15. Click the trash can icons to delete the two default DNS entries that NameCheap had for your domain previously:

16. Try loading your new site.

  • If you get a connection error, wait a few minutes for DNS to propagate and re-verify the DNS records you just added.
  • If you get a certificate error, look at the certificate. It’s probably the default GitHub certificate. If so, look in the GitHub Pages settings page and you may see a note that your certificate is awaiting issuance by If so, just wait a little while.

  • After the certificate is issued, your site without errors:


Go forth and build great (secure) things!

-Eric Lawrence

browsers, perf, reviews

Going Offline with ServiceWorker

In the IE8 era, I had a brief stint as an architect on the IE team, trying to figure out a coherent strategy and a deployable set of technologies that would allow web developers to build offline-capable web applications. A few of those ideas turned into features, several turned into unimplemented patents, and a few went nowhere at all.

A decade later, it’s clear that ServiceWorker is going to be the core engine beneath all future top-tier web applications. ServiceWorker brings the power of Fiddler’s AutoResponder and FiddlerScript features to JavaScript code running directly within the user’s browser. Designed by real web developers for real web developers, it delivers upon scenarios that previously required native applications. And browser support is looking great:


As I started looking at ServiceWorker, I was concerned about its complexity but I was delighted to discover a straightforward, very approachable reference on designing a ServiceWorker-backed application: Going Offline by Jeremy Keith. The book is short (I’m busy), direct (“Here’s a problem, here’s how to solve it“), opinionated in the best way (landmine-avoiding “Do this“), and humorous without being confusing. As anyone who has received unsolicited (or solicited) feedback from me about their book knows, I’m an extremely picky reader, and I have no significant complaints on this one. Highly recommended.

Unfortunately, the book isn’t available at list price on Amazon, but buying directly from the publisher is straightforward. The EBook is $11.00 and the paperback+ebook bundle is $28.80+shipping.