• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2020 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Handles registration, unregistration and lifecycle of the service worker.
16// This class contains only the controlling logic, all the code in here runs in
17// the main thread, not in the service worker thread.
18// The actual service worker code is in src/service_worker.
19// Design doc: http://go/perfetto-offline.
20
21import {reportError} from '../base/logging';
22
23import {globals} from './globals';
24
25// We use a dedicated |caches| object to share a global boolean beween the main
26// thread and the SW. SW cannot use local-storage or anything else other than
27// IndexedDB (which would be overkill).
28const BYPASS_ID = 'BYPASS_SERVICE_WORKER';
29
30export class ServiceWorkerController {
31  private _initialWorker: ServiceWorker|null = null;
32  private _bypassed = false;
33  private _installing = false;
34
35  // Caller should reload().
36  async setBypass(bypass: boolean) {
37    if (!('serviceWorker' in navigator)) return;  // Not supported.
38    this._bypassed = bypass;
39    if (bypass) {
40      await caches.open(BYPASS_ID);  // Create the entry.
41      for (const reg of await navigator.serviceWorker.getRegistrations()) {
42        await reg.unregister();
43      }
44    } else {
45      await caches.delete(BYPASS_ID);
46      this.install();
47    }
48    globals.rafScheduler.scheduleFullRedraw();
49  }
50
51  onStateChange(sw: ServiceWorker) {
52    globals.rafScheduler.scheduleFullRedraw();
53    if (sw.state === 'installing') {
54      this._installing = true;
55    } else if (sw.state === 'activated') {
56      this._installing = false;
57      // Don't show the notification if the site was served straight
58      // from the network (e.g., on the very first visit or after
59      // Ctrl+Shift+R). In these cases, we are already at the last
60      // version.
61      if (sw !== this._initialWorker && this._initialWorker) {
62        globals.frontendLocalState.newVersionAvailable = true;
63      }
64    }
65  }
66
67  monitorWorker(sw: ServiceWorker|null) {
68    if (!sw) return;
69    sw.addEventListener('error', (e) => reportError(e));
70    sw.addEventListener('statechange', () => this.onStateChange(sw));
71    this.onStateChange(sw);  // Trigger updates for the current state.
72  }
73
74  async install() {
75    if (!('serviceWorker' in navigator)) return;  // Not supported.
76
77    if (await caches.has(BYPASS_ID)) {
78      this._bypassed = true;
79      console.log('Skipping service worker registration, disabled by the user');
80      return;
81    }
82    navigator.serviceWorker.register('service_worker.js').then(registration => {
83      this._initialWorker = registration.active;
84
85      // At this point there are two options:
86      // 1. This is the first time we visit the site (or cache was cleared) and
87      //    no SW is installed yet. In this case |installing| will be set.
88      // 2. A SW is already installed (though it might be obsolete). In this
89      //    case |active| will be set.
90      this.monitorWorker(registration.installing);
91      this.monitorWorker(registration.active);
92
93      // Setup the event that shows the "A new release is available"
94      // notification.
95      registration.addEventListener('updatefound', () => {
96        this.monitorWorker(registration.installing);
97      });
98    });
99  }
100
101  get bypassed() {
102     return this._bypassed;
103  }
104  get installing() {
105    return this._installing;
106  }
107}