Safari web extension bug

September 17 2020 by Jeff Johnson

Yesterday Apple released Safari 14 for macOS Catalina and Mojave. This may be surprising, if you weren't expecting Safari 14 until macOS Big Sur, but in retrospect, last year Apple released Safari 13 for Mojave and High Sierra before Catalina, and the previous year Apple released Safari 12 for High Sierra and Sierra before Mojave, so we should have known that Safari 14 was coming "early". Apple actually releases major macOS Safari updates simultaneously with major iOS updates (such as iOS 14 yesterday), because iOS also contains major Safari updates. The iOS and macOS versions of Safari share code — and thus security vulnerabilities! — so Apple has to release security fixes on both platforms simultaneously, otherwise one platform's update would zero-day the other platform.

The Safari 14 release notes didn't mention an important new feature: support for web extensions. Probably because there aren't any Safari web extensions yet! But support is indeed there for web extensions in Safari 14 for Catalina and Mojave. Previous versions of Safari already had support for Apple-specific app extensions, such as my own StopTheMadness, but Safari 14 also supports the cross-platform WebExtensions API used by Firefox and Google Chrome. Unfortunately, that support is not complete. Ever since the Big Sur beta was released at WWDC in June, I've been working on a brand new Safari web extension that I'm really excited about, and I think you will be too. I would love to release it now that Safari 14 is publicly available, but I can't, because I found a bug in Safari's web extension support that's a showstopper for my extension. In short, the bug is that run_at document_start is not reliable in Safari. According to the Chrome extension documentation, if an extension has "run_at":"document_start" in its manifest, then "Scripts are injected after any files from css, but before any other DOM is constructed or any other script is run."

You can download a sample Xcode project that demonstrates the bug. The project contains both a Safari app extension and a Safari web extension, so you can see how the app extension always does run at document_start, while the web extension doesn't. Here are the steps to reproduce:

  1. Build and run the RunAtTest app.
  2. Open Safari 14.
  3. Open Safari Preferences to the Advanced pane, and enable "Show Develop menu in menu bar".
  4. Open the "Develop" menu, enable "Allow Unsigned Extensions" at the bottom, and enter your keychain password when prompted.
  5. Open Safari Preferences to the Extensions pane, and enable the two new extensions.
  6. Select WebExtensions in the extensions list, and enable "Always Allow on Every Website".

The extension scripts log a warning to the Web Inspector Console with the value of document.documentElement.childElementCount, which is the number of children of the <html> element. The expected value is 0 when run at document_start. The Safari bug is that sometimes the web extension has a value of greater than 0. In other words, the <html> already has <head> and <body> elements. You can see the entire HTML by logging document.documentElement.outerHTML. The bug occurs most frequently when you enter a URL in the Safari address bar, as opposed to clicking a link.

I can't say why exactly I need document_start; that would ruin the surprise! But I do need it, and can't ship my new Safari web extension without it. I reported the bug to Apple privately a month ago, but unfortunately the bug still exists. Therefore, since Safari 14 is now public, I'm going to lobby publicly for Apple to fix the bug ASAP. Apple, you're depriving all of your poor Safari users of my great new extension! You know my extensions are great: just look at StopTheMadness. Moreover, unless and until this bug is fixed, other extension developers who already rely on document_start in Google Chrome will not be able to successfully port their extensions to Safari.

Jeff Johnson (My apps, PayPal.Me)