Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions proposals/FileSystemSubscriptionManager.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Proposal: FileSystemSubscriptionManager

## Authors

* Ming-Ying Chung (<[email protected]>)
* Austin Sullivan (<[email protected]>)

## Background

The [FileSystemObserver API][fso-api] allows browsing context to receive records of changes for the observed files or directories. However, changes which occur while the website has no open tabs are not visible to the website.

This document proposes integrating file system change observing with service worker registration, such that a service worker may be woken up on a change to the local file system.

[fso-api]: https://chromestatus.com/feature/4622243656630272

## Goal

Allow web applications that do not have an open tab to quickly respond to changes to registered files and directories on the local file system.

## Discussion

### Option 1. Enabling FileSystemObserver in Service Worker

By [current design][fso], an instance of FileSystemObserver should only report changes which occur while the observer is connected and the website has an open & active tab.

Enabling creations of FileSystemObserver in ServiceWorker will tie its lifetime with ServiceWorkerGlobalScope. Users might expect such observers to continue to watch files in the background. However, in browser implementation, service workers that haven’t received new events in a certain period of time, e.g. [30s in Chrome][chrome-sw], will likely be terminated.

Hence, this approach might only work for websites that either can ensure a long-running service worker or are not really interested in using service worker registration for observing the file change events after the websites are closed.

[fso]: https://docs.google.com/document/d/11o4HaslFwQxKIjJw0M9LAkkZp2ikCNwA9vvYsjA657w/edit?tab=t.0#heading=h.kktfoyrlhtnv
[chrome-sw]: https://developer.chrome.com/blog/longer-esw-lifetimes#background

### Option 2. Updating FileSystemObserver API to outlive Service Worker

Another approach is to ask what if updating FileSystemObserver to allow it to outlive without the limit of running in ServiceWorkerGlobalScope?

If such an option is implemented, there needs to be mechanisms to
Tell when the browser should stop watching file changes.
Handle file changes happen after the service worker is already terminated but the website is still open. Possibly needs a way to wake up a new service worker.

There are existing mechanisms to auto wake up new service workers. Hence the next option.

### Option 3. Utilizing Service Worker Registration

The ServiceWorkerRegistration interface represents registration of a service worker for a specific origin and scope. The browser maintains a persistent list of active ServiceWorkerRegistration even when the associated service worker is not actively running, and will wake up new service workers if a registered event happens.

## Proposed API

### Web IDL

First, define a new interface FileSystemSubscriptionManager that will be held under every ServiceWorkerRegistration, which manages subscriptions to changes to file systems:

```idl
partial interface ServiceWorkerRegistration {
// Returns a reference to FileSystemSubscriptionManager interface, which allows
// for subscribing to specific file changes.
[SameObject] readonly attribute FileSystemSubscriptionManager fileSystem;
};
```

```idl
// Provides methods for managing file system subscriptions.
interface FileSystemSubscriptionManager {
// Subscribes to changes to a FileSystemHandle with the browser with specific
// options. Returns a Promise that resolves when the subscription completes.
Promise<void> subscribe(FileSystemHandle handle,
FileSystemObserverObserverOptions options = {});
// Unsubscribes to changes to a FileSystemHandle. Returns a Promise that
// resolves when the unsubscription completes.
Promise<void> unsubscribe(FileSystemHandle handle);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is different from https://developer.mozilla.org/en-US/docs/Web/API/PushManager which puts the unsubscribe() on the subscription object, but the same as https://wicg.github.io/cookie-store/#CookieStoreManager .

I would be interested to know more about why these two APIs made different choices, and how that should guide our choices.

Copy link
Contributor Author

@mingyc mingyc Apr 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some arguments here

  • Adding unsubscribe() to FileSystemHandle seems to suggest that a handle can be subscribed anywhere? But it actually only works in certain context (SW).
  • To align with FileSystemObserver.unobserve(handle). As stated in the background sction, FileSysttemObserver inspires this proposal.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, subscribe() doesn't return anything in our case. That makes sense.

// Returns a Promise that resolves with a list of FileSystemSubscription
// representing all the current file system subscriptions with the browser.
Promise<sequence<FileSystemSubscription>> getSubscriptions();
};
```

```idl
// Represents a subscription to changes to a FileSystemHandle.
interface FileSystemSubscription {
required FileSystemHandle handle;
FileSystemObserverObserverOptions? options = null;
};
```

Second, allow service workers to fire a new type of event FileSystemChangeEvent, which includes a list of FileSystemChangeRecord.

```idl
partial interface ServiceWorkerGlobalScope {
// Fired when FileSystemSubscriptionManager observes changes.
attribute EventHandler attribute onfilesystemchange;
};
```

```idl
// Represents a file system change event.
interface FileSystemChangeEvent : ExtendableEvent {
constructor(DOMString type, FileSystemChangeEventInit init);
readonly attribute sequence<FileSystemChangeRecord> records;
};

interface FileSystemChangeEventInit : ExtendableEvent {
required sequence<FileSystemChangeRecord> records;
}
```

### Example: Observing Changes to a Directory

```javascript
// main.js
const directoryHandle = await window.showDirectoryPicker();
async function observeDirectoryChanges(directoryHandle) {
const registration = await navigator.ServiceWorker.register("/service-worker.js");
registration.fileSystem.subscribe(directoryHandle, {recursive: true});
}

// service-worker.js
self.addEventListener('onfilesystemchange', event => {
for (const record of event.records) {
assert(await directoryHandle.IsSameEntry(record.root));
handleRecord(record);
}
});
```
Loading