Jeff Johnson (My apps, PayPal.Me)

Works as currently designed

October 8 2022

On September 1, I filed a bug with Apple's Feedback Assistant (FB11426949) about Safari extensions titled "Web Extension storage callbacks in the wrong order". Usually Apple will email you when your feedback status changes, but in this case they didn't notify me. Instead, while I was manually browsing my bug reports I discovered that the status of my feedback had changed. I guess that Apple was ashamed to email me, because the "resolution" of the feedback was "Investigation complete - Works as currently designed". Apple didn't explain the "design" or leave any comment whatsoever on my feedback. This is truly baffling and frustrating.

I'll quote my full bug report here, and you can judge for yourself whether or not this is a bug.

In a Safari web extension on both macOS 12 and iOS 15 (I filed this bug under Mac because there was no cross-platform category), if you call storage.local.set and then storage.local.get, the callbacks are called in the opposite order, and indeed the opposite order from Chrome and Firefox extensions. Attached is a sample Xcode project demonstrating the bug. Just build and run the extension in Safari, and open the web inspector console.

Expected results:

[Log] first (content.js, line 6)
[Log] second – "bar" (content.js, line 9)

Actual results:

[Log] second – undefined (content.js, line 9)
[Log] first (content.js, line 6)

As you can see, the wrong callback order results in undefined rather than the expected result, which is bad. You can use the same sample web extension in Safari, Chrome, and Firefox to compare results. If you look at the online documentation for the extension storage API from Mozilla and Google, you can see that their examples depend on the callbacks occurring in the right order.

https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/StorageArea/set

browser.storage.local.set({kitten, monster})
  .then(setItem, onError);
 
browser.storage.local.get("kitten")
  .then(gotKitten, onError);
browser.storage.local.get("monster")
  .then(gotMonster, onError);

https://developer.chrome.com/docs/extensions/reference/storage/

chrome.storage.local.set({key: value}, function() {
  console.log('Value is set to ' + value);
});

chrome.storage.local.get(['key'], function(result) {
  console.log('Value currently is ' + result.key);
});

You can download the sample Xcode project I mentioned in the bug report. Below is the source of the extension content script.

(function() {
'use strict';

if (typeof browser === "object") {
    browser.storage.local.set({"foo": "bar"}).then(() => {
        console.log("first");
    });
    browser.storage.local.get("foo").then((result) => {
        console.log("second", result.foo);
    });
} else if (typeof chrome === "object") {
    chrome.storage.local.set({"foo": "bar"}, function() {
        console.log("first");
    });
    chrome.storage.local.get("foo", function(result) {
        console.log("second", result.foo);
    });
}

})();

By design, Safari web extensions use the same cross-platform API as Chrome and Firefox extensions so that extension developers can share code across browsers and easily port their extensions to Safari from other browsers. Thus, a behavioral difference between Safari and the other browsers means that extension developers can't share code, defeating the fundamental design of Safari web extensions. For this reason alone, I would argue that the behavior of Safari extension storage callbacks is a bug and not a feature.

This is far from the first time I've seen the "Works as currently designed" response for Apple. For example, recently there was iOS 16 text view breakage — which they ultimately fixed, after I blogged about it! Also recently were Apple re-enables Bluetooth on every OS update on purpose and macOS Monterey unannounced security misfeature, which unfortunately they haven't fixed. I can understand why the last two are "by design", despite vehemently disagreeing with the user-hostile design. However, I can't understand how web extension storage callbacks occurring in the opposite order is supposed to be "by design". That's just nutty.

Apple's Feedback Assistant, formerly known as Radar, has been called a "black hole". Its reputation is well deserved. Apple engineers constantly, aggressively solicit bug reports from external developers, but then those same Apple engineers often turn around and treat our bug reports with contempt. In this case, they didn't even deign to offer an explanation.

So why do we keep filing bug reports? I don't know. Perhaps for the same reason that Charlie Brown keeps trying to kick the football. Forever hopeful, forever hopeless.

Addendum October 14, 2022

On October 11, a few days after this blog post was published, I received an unsolicited update on my Feedback. So it seems that Apple is reading my blog! Sometimes (often) I think it's better to write publicly about bugs than to file private bug reports with Apple. ;-)

I won't quote the reply here, because there's now a public statement from Apple that you can read directly. I've discovered that cross-platform "Inconsistency" reports can be filed with the Web Extensions Community Group. I filed one, and an Apple engineer replied:

In Safari the storage is multi-threaded, database backed — truly async. Given the async nature of all of these calls, I don't think FIFO should be guaranteed.

The reply also explains how to fix the bug I reported, but I already knew how to fix it and indeed already fixed it in my own extension before I even reported the bug to Apple in the first place (though not exactly in the way suggested, because my extension's code is not as simple as the sample code given). I don't personally need the bug to be fixed, because I'm now aware of the bug and can avoid it in the future. It's possible to work around the bug; that's not the issue. One issue is that an extension developer can only work around the Safari bug if they know that the bug exists in their extension, and I didn't know until it was too late. I ported some source code from my working Chrome extension to my Safari extension, and the bug was very subtle, so I didn't notice it until much later. I filed the bug report so that other extension developers can avoid the problem that I already experienced.

Another problem is of course the inconsistency between Safari and every other browser. And also the fact that the sample code documenting the API presumes the very behavior that Safari goes against. The sample code in the MDN documentation is broken in Safari! How is that not a big problem in itself?

In general, I think that Safari's implementation of extension storage is weird. It appears that the purpose is to return get results ASAP, but what's the point of returning stale results quickly? Is it truly better to be wrong and fast than right and a bit slower? This has the feel of premature optimization.

In addition to the bug described above, I've experienced a number of problems with the Safari web extension storage API, including data loss! And some lesser but still annoying bugs. Oh, I almost forgot another data loss bug! I don't think the implementation has been done well. But my complaint here is not really about software quality, it's about Apple's resistance to acknowledging the existence of quality problems. There's nothing I hate more than the "works as currently designed" response. If there wasn't a problem, I wouldn't have filed a bug report. Do I have to file a second bug report that says "The current design is bad"?

Jeff Johnson (My apps, PayPal.Me)