Repository: GoogleChrome/workbox (language: javascript)

Issue:
Background-sync fallback not working in Chrome when Background Sync API is disabled
**Library Affected**:
*workbox-background-sync*

**Browser & Platform**:
Android WebView (Chrome Stable)

**Issue or Feature Request Description**:
The sw doesn't detect that the browser (Chrome) doesn't support (or has disabled) the Background Sync API, which results in both the BGS API and the fallback to not work.

The requests are added to the queue correctly (in IndexedDB) but the queue is never accessed/cleared.

**Error:**
`Unable to register sync event for 'myQueueName'. DOMException: Background Sync is disabled.`

**Details:**
Workbox v5.0.0
sw code is [here](https://gist.github.com/nip10/83a3b0e70ffa571ea0587b77feef9ba1), a basic example of the BGS package
Working on Chrome v80 (Windows 10 x64, and Android 10), using the BGS API
Working on Firefox v68.5.0 (Android 10) and Firefox v73.0.1 (Windows 10 x64), using the fallback (Firefox doesn't support the BGS API)
**Not** Working on Android WebView (Android 7, webview implementation: Chrome Stable, installed Chrome v80)

@philipwalton Here it is. Feel free to ask for more details and/or if you need me to debug further.


Discussion / hints:
Facing the same issue except with Electron rather than Android WebView.  Any updates on this ? 
Seems that simply checking for the presence of the `sync` property in 
[https://github.com/GoogleChrome/workbox/blob/3151c3a2740558b87a592f5091cc795815ca3ac5/packages/workbox-background-sync/src/Queue.ts#L363](url) is insufficient for these cases, as background sync is supported by the browser but disabled. Therefore, the check passes, an exception is thrown and the fallback never executes.

```typescript
  /**
   * In sync-supporting browsers, this adds a listener for the sync event.
   * In non-sync-supporting browsers, this will retry the queue on service
   * worker startup.
   *
   * @private
   */
  private _addSyncListener() {
    if ('sync' in self.registration) {
      self.addEventListener('sync', (event: SyncEvent) => {
      // ...
```
Apologies that there's hasn't been any activity on this since it was first reported.

I'm going to explore a few possible workarounds, including the feasibility of attempting a no-op sync registration wrapped in a `try`/`catch` in addition to the `'sync' in self.registration` check.
Hey All—Sorry again for the lack of progress.

I've been looking into this again, and I'm struggling with a "clean" solution here that detects the failure at runtime.

What I'm thinking instead is just to ship a new option in `BackgroundSyncPlugin` / `Queue` constructor called `forceSyncFallback`. The default value would be `false`, and that would give the same behavior as what we currently ship, with the `'sync' in self.registration` check used to determine whether or not to use the `sync` event or the fallback. Developers could set this value to `true` if they knew that they were deploying to an environment that was subject to this bug—specifically, I'm thinking it would be useful for developers deploying to Electron, since you'd presumably have a dedicated build setup for that deployment scenario.

It would not address the situation with the old Android WebView user agents mentioned above, however.

Does this sound like it would be useful enough to unblock at least the Electron developers?
+1 for the update for Electron


Fix patch:
diff --git a/packages/workbox-background-sync/src/Queue.ts b/packages/workbox-background-sync/src/Queue.ts
index 97dd895f1..4e0518396 100644
--- a/packages/workbox-background-sync/src/Queue.ts
+++ b/packages/workbox-background-sync/src/Queue.ts
@@ -27,8 +27,9 @@ interface OnSyncCallback {
 }
 
 export interface QueueOptions {
-  onSync?: OnSyncCallback;
+  forceSyncFallback?: boolean;
   maxRetentionTime?: number;
+  onSync?: OnSyncCallback;
 }
 
 interface QueueEntry {
@@ -79,6 +80,7 @@ class Queue {
   private readonly _onSync: OnSyncCallback;
   private readonly _maxRetentionTime: number;
   private readonly _queueStore: QueueStore;
+  private readonly _forceSyncFallback: boolean;
   private _syncInProgress = false;
   private _requestsAddedDuringSync = false;
 
@@ -100,8 +102,17 @@ class Queue {
    * @param {number} [options.maxRetentionTime=7 days] The amount of time (in
    *     minutes) a request may be retried. After this amount of time has
    *     passed, the request will be deleted from the queue.
+   * @param {boolean} [options.forceSyncFallback=false] If `true`, instead
+   *     of attempting to use background sync events, always attempt to replay
+   *     queued request at service worker startup. Most folks will not need
+   *     this, unless you explicitly target a runtime like Electron that
+   *     exposes the interfaces for background sync, but does not have a working
+   *     implementation.
    */
-  constructor(name: string, {onSync, maxRetentionTime}: QueueOptions = {}) {
+  constructor(
+    name: string,
+    {forceSyncFallback, onSync, maxRetentionTime}: QueueOptions = {},
+  ) {
     // Ensure the store name is not already being used
     if (queueNames.has(name)) {
       throw new WorkboxError('duplicate-queue-name', {name});
@@ -112,6 +123,7 @@ class Queue {
     this._name = name;
     this._onSync = onSync || this.replayRequests;
     this._maxRetentionTime = maxRetentionTime || MAX_RETENTION_TIME;
+    this._forceSyncFallback = Boolean(forceSyncFallback);
     this._queueStore = new QueueStore(this._name);
 
     this._addSyncListener();
@@ -379,7 +391,8 @@ class Queue {
    * Registers a sync event with a tag unique to this instance.
    */
   async registerSync(): Promise<void> {
-    if ('sync' in self.registration) {
+    // See https://github.com/GoogleChrome/workbox/issues/2393
+    if ('sync' in self.registration && !this._forceSyncFallback) {
       try {
         await self.registration.sync.register(`${TAG_PREFIX}:${this._name}`);
       } catch (err) {
@@ -397,13 +410,14 @@ class Queue {
 
   /**
    * In sync-supporting browsers, this adds a listener for the sync event.
-   * In non-sync-supporting browsers, this will retry the queue on service
-   * worker startup.
+   * In non-sync-supporting browsers, or if _forceSyncFallback is true, this
+   * will retry the queue on service worker startup.
    *
    * @private
    */
   private _addSyncListener() {
-    if ('sync' in self.registration) {
+    // See https://github.com/GoogleChrome/workbox/issues/2393
+    if ('sync' in self.registration && !this._forceSyncFallback) {
       self.addEventListener('sync', (event: SyncEvent) => {
         if (event.tag === `${TAG_PREFIX}:${this._name}`) {
           if (process.env.NODE_ENV !== 'production') {
@@ -450,8 +464,9 @@ class Queue {
       if (process.env.NODE_ENV !== 'production') {
         logger.log(`Background sync replaying without background sync event`);
       }
-      // If the browser doesn't support background sync, retry
-      // every time the service worker starts up as a fallback.
+      // If the browser doesn't support background sync, or the developer has
+      // opted-in to not using it, retry every time the service worker starts up
+      // as a fallback.
       void this._onSync({queue: this});
     }
   }


Test patch:
diff --git a/test/workbox-background-sync/sw/test-Queue.mjs b/test/workbox-background-sync/sw/test-Queue.mjs
index efa46d066..08ac4ce35 100644
--- a/test/workbox-background-sync/sw/test-Queue.mjs
+++ b/test/workbox-background-sync/sw/test-Queue.mjs
@@ -205,6 +205,13 @@ describe(`Queue`, function () {
         expect(onSync.calledOnce).to.be.true;
       }
     });
+
+    it(`should run 'onSync' on instantiation when forceSyncFallback is set`, async function () {
+      const onSync = sandbox.spy();
+      new Queue('foo', {onSync, forceSyncFallback: true});
+
+      expect(onSync.calledOnce).to.be.true;
+    });
   });
 
   describe(`pushRequest`, function () {