-
Notifications
You must be signed in to change notification settings - Fork 930
Add experimentalForceOwningTab
for Web Worker support
#2197
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
f0287d4
15cf32e
66655d0
9ac6e2d
d813905
347b722
91404e3
00c6dc7
6dc5e51
fef8971
8366646
741e88e
86ca4aa
16479d4
0784827
59d07ee
5dae5b6
71785f2
ca02909
c5f491a
08f8fb9
398c330
6b6db57
b178a75
7d8e41a
6d20b20
016aed8
31a8772
c7cbf9a
be3a0d4
47cd1f6
0f45287
9cfe34d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6226,6 +6226,15 @@ declare namespace firebase.firestore { | |
* multiple tabs, please use `synchronizeTabs: true` instead. | ||
*/ | ||
experimentalTabSynchronization?: boolean; | ||
|
||
/** | ||
* Whether to force enabling persistence for the client even if another | ||
* client is already running with persistence enabled. This cannot be used | ||
* with `synchronizeTabs:true` and is primarily intended for use with Web | ||
* Workers. Setting this to 'true' will enable persistence, but cause other | ||
* tabs using persistence to fail. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note: If/when we ship this for real we should add more detail about exactly how other tabs will fail (so that developers can potentially write code to detect it, etc.). |
||
*/ | ||
experimentalForce?: boolean; | ||
} | ||
|
||
export type LogLevel = 'debug' | 'error' | 'silent'; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -197,6 +197,7 @@ export class IndexedDbPersistence implements Persistence { | |
queue: AsyncQueue; | ||
serializer: JsonProtoSerializer; | ||
sequenceNumberSyncer: SequenceNumberSyncer; | ||
force: boolean; | ||
}): Promise<IndexedDbPersistence> { | ||
if (!IndexedDbPersistence.isAvailable()) { | ||
throw new FirestoreError( | ||
|
@@ -213,14 +214,15 @@ export class IndexedDbPersistence implements Persistence { | |
options.lruParams, | ||
options.queue, | ||
options.serializer, | ||
options.sequenceNumberSyncer | ||
options.sequenceNumberSyncer, | ||
options.force | ||
); | ||
await persistence.start(); | ||
return persistence; | ||
} | ||
|
||
private readonly document: Document | null; | ||
private readonly window: Window; | ||
private readonly window: Window | null; | ||
|
||
// Technically these types should be `| undefined` because they are | ||
// initialized asynchronously by start(), but that would be more misleading | ||
|
@@ -254,7 +256,7 @@ export class IndexedDbPersistence implements Persistence { | |
private readonly queryCache: IndexedDbQueryCache; | ||
private readonly indexManager: IndexedDbIndexManager; | ||
private readonly remoteDocumentCache: IndexedDbRemoteDocumentCache; | ||
private readonly webStorage: Storage; | ||
private readonly webStorage: Storage | null; | ||
readonly referenceDelegate: IndexedDbLruDelegate; | ||
|
||
private constructor( | ||
|
@@ -265,7 +267,8 @@ export class IndexedDbPersistence implements Persistence { | |
lruParams: LruParams, | ||
private readonly queue: AsyncQueue, | ||
serializer: JsonProtoSerializer, | ||
private readonly sequenceNumberSyncer: SequenceNumberSyncer | ||
private readonly sequenceNumberSyncer: SequenceNumberSyncer, | ||
private readonly force: boolean | ||
) { | ||
this.referenceDelegate = new IndexedDbLruDelegate(this, lruParams); | ||
this.dbName = persistenceKey + IndexedDbPersistence.MAIN_DATABASE; | ||
|
@@ -281,14 +284,19 @@ export class IndexedDbPersistence implements Persistence { | |
this.indexManager, | ||
/*keepDocumentChangeLog=*/ this.allowTabSynchronization | ||
); | ||
this.window = platform.window; | ||
if (platform.window && platform.window.localStorage) { | ||
this.window = platform.window; | ||
this.webStorage = this.window.localStorage; | ||
this.webStorage = platform.window.localStorage; | ||
} else { | ||
throw new FirestoreError( | ||
Code.UNIMPLEMENTED, | ||
'IndexedDB persistence is only available on platforms that support LocalStorage.' | ||
); | ||
this.webStorage = null; | ||
if (force === false) { | ||
log.error( | ||
LOG_TAG, | ||
'LocalStorage is unavailable. As a result, persistence may not work ' + | ||
'reliably. In particular enablePersistence() could fail immediately ' + | ||
'after refreshing the page.' | ||
); | ||
} | ||
} | ||
} | ||
|
||
|
@@ -299,7 +307,6 @@ export class IndexedDbPersistence implements Persistence { | |
*/ | ||
private start(): Promise<void> { | ||
assert(!this.started, 'IndexedDbPersistence double-started!'); | ||
assert(this.window !== null, "Expected 'window' to be defined"); | ||
|
||
return SimpleDb.openOrCreate( | ||
this.dbName, | ||
|
@@ -310,7 +317,7 @@ export class IndexedDbPersistence implements Persistence { | |
this.simpleDb = db; | ||
// NOTE: This is expected to fail sometimes (in the case of another tab already | ||
// having the persistence lock), so it's the first thing we should do. | ||
return this.updateClientMetadataAndTryBecomePrimary(); | ||
return this.updateClientMetadataAndTryBecomePrimary(this.force); | ||
}) | ||
.then(() => { | ||
this.attachVisibilityHandler(); | ||
|
@@ -392,7 +399,9 @@ export class IndexedDbPersistence implements Persistence { | |
* primary state listener if the client either newly obtained or released its | ||
* primary lease. | ||
*/ | ||
private updateClientMetadataAndTryBecomePrimary(): Promise<void> { | ||
private updateClientMetadataAndTryBecomePrimary( | ||
force = false | ||
): Promise<void> { | ||
return this.simpleDb.runTransaction('readwrite', ALL_STORES, txn => { | ||
const metadataStore = clientMetadataStore(txn); | ||
return metadataStore | ||
|
@@ -417,7 +426,7 @@ export class IndexedDbPersistence implements Persistence { | |
}); | ||
} | ||
}) | ||
.next(() => this.canActAsPrimary(txn)) | ||
.next(() => (force ? true : this.canActAsPrimary(txn))) | ||
.next(canActAsPrimary => { | ||
const wasPrimary = this.isPrimary; | ||
this.isPrimary = canActAsPrimary; | ||
|
@@ -525,11 +534,13 @@ export class IndexedDbPersistence implements Persistence { | |
// Ideally we'd delete the IndexedDb and LocalStorage zombie entries for | ||
// the client atomically, but we can't. So we opt to delete the IndexedDb | ||
// entries first to avoid potentially reviving a zombied client. | ||
inactiveClients.forEach(inactiveClient => { | ||
this.window.localStorage.removeItem( | ||
this.zombiedClientLocalStorageKey(inactiveClient.clientId) | ||
); | ||
}); | ||
if (this.webStorage) { | ||
inactiveClients.forEach(inactiveClient => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Turn this into a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. |
||
this.webStorage!.removeItem( | ||
this.zombiedClientLocalStorageKey(inactiveClient.clientId) | ||
); | ||
}); | ||
} | ||
} | ||
} | ||
|
||
|
@@ -590,6 +601,9 @@ export class IndexedDbPersistence implements Persistence { | |
} | ||
|
||
if (!this.isLocalClient(currentPrimary)) { | ||
if (this.force) { | ||
return true; | ||
} | ||
thebrianchen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (!currentPrimary!.allowTabSynchronization) { | ||
// Fail the `canActAsPrimary` check if the current leaseholder has | ||
// not opted into multi-tab synchronization. If this happens at | ||
|
@@ -974,6 +988,9 @@ export class IndexedDbPersistence implements Persistence { | |
* handler. | ||
*/ | ||
private attachWindowUnloadHook(): void { | ||
if (!this.window) { | ||
return; | ||
} | ||
if (typeof this.window.addEventListener === 'function') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure what effect this has on code size, but you could remove some of extra lines in this PR but using optional chaining:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed some of the if statements, but the linter complains if you use optional chaining without assigning it to a variable that gets used. |
||
this.windowUnloadHandler = () => { | ||
// Note: In theory, this should be scheduled on the AsyncQueue since it | ||
|
@@ -992,6 +1009,9 @@ export class IndexedDbPersistence implements Persistence { | |
} | ||
|
||
private detachWindowUnloadHook(): void { | ||
if (!this.window) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be an assert in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. |
||
return; | ||
} | ||
if (this.windowUnloadHandler) { | ||
assert( | ||
typeof this.window.removeEventListener === 'function', | ||
|
@@ -1008,6 +1028,9 @@ export class IndexedDbPersistence implements Persistence { | |
* cleanup logic in `shutdown()`. | ||
*/ | ||
private isClientZombied(clientId: ClientId): boolean { | ||
if (!this.webStorage) { | ||
return false; | ||
} | ||
try { | ||
const isZombied = | ||
this.webStorage.getItem(this.zombiedClientLocalStorageKey(clientId)) !== | ||
|
@@ -1031,6 +1054,9 @@ export class IndexedDbPersistence implements Persistence { | |
* clients are ignored during primary tab selection. | ||
*/ | ||
private markClientZombied(): void { | ||
if (!this.webStorage) { | ||
return; | ||
} | ||
try { | ||
this.webStorage.setItem( | ||
this.zombiedClientLocalStorageKey(this.clientId), | ||
|
@@ -1044,6 +1070,9 @@ export class IndexedDbPersistence implements Persistence { | |
|
||
/** Removes the zombied client entry if it exists. */ | ||
private removeClientZombiedEntry(): void { | ||
if (!this.webStorage) { | ||
return; | ||
} | ||
try { | ||
this.webStorage.removeItem( | ||
this.zombiedClientLocalStorageKey(this.clientId) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -66,7 +66,14 @@ export class SimpleDb { | |
// suggests IE9 and older WebKit browsers handle upgrade | ||
// differently. They expect setVersion, as described here: | ||
// https://developer.mozilla.org/en-US/docs/Web/API/IDBVersionChangeRequest/setVersion | ||
const request = window.indexedDB.open(name, version); | ||
|
||
let request: IDBOpenDBRequest; | ||
// Support Web Workers, which don't have `window` global variable. | ||
if (typeof indexedDB !== 'undefined') { | ||
request = indexedDB.open(name, version); | ||
} else { | ||
request = window.indexedDB.open(name, version); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since Maybe we have a fake There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I demand answers too :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looked into this some more and since Deleted. |
||
} | ||
|
||
request.onsuccess = (event: Event) => { | ||
const db = (event.target as IDBOpenDBRequest).result; | ||
|
@@ -132,6 +139,10 @@ export class SimpleDb { | |
|
||
/** Returns true if IndexedDB is available in the current environment. */ | ||
static isAvailable(): boolean { | ||
// If IndexedDb is available directly (ex: in case of web workers). | ||
if (typeof indexedDB !== 'undefined') { | ||
return true; | ||
} | ||
if (typeof window === 'undefined' || window.indexedDB == null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Per above, since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We still need this for cases where There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we just need to register the IndexedDb shim directly on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added comment for why it's already registered. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't that mean that "if (typeof window === 'undefined' || window.indexedDB == null)" is now no longer needed? Is your distinction from your first response here still relevant? if so, we should probably talk this through offline. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As per offline conversation, removed the window logic, and flipped the indexedDB check to return false. By returning true automatically when IndexedDB is available, we are skipping all of the UA checks. Also removed the |
||
return false; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was the intention to use backticks around
true
? That would work.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.