Skip to content

Commit 49420f0

Browse files
[Multi-Tab] Supporting non-initial initial queries (#990)
1 parent 0669009 commit 49420f0

File tree

5 files changed

+98
-33
lines changed

5 files changed

+98
-33
lines changed

packages/firestore/src/core/event_manager.ts

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import { SyncEngine } from './sync_engine';
1919
import { OnlineState, TargetId } from './types';
2020
import { DocumentViewChange } from './view_snapshot';
2121
import { ChangeType, ViewSnapshot } from './view_snapshot';
22-
import { DocumentSet } from '../model/document_set';
2322
import { assert } from '../util/assert';
2423
import { EventHandler } from '../util/misc';
2524
import { ObjectMap } from '../util/obj_map';
@@ -289,28 +288,13 @@ export class QueryListener {
289288
!this.raisedInitialEvent,
290289
'Trying to raise initial events for second time'
291290
);
292-
snap = new ViewSnapshot(
291+
snap = ViewSnapshot.fromInitialDocuments(
293292
snap.query,
294293
snap.docs,
295-
DocumentSet.emptySet(snap.docs),
296-
QueryListener.getInitialViewChanges(snap),
297294
snap.fromCache,
298-
snap.hasPendingWrites,
299-
/* syncChangesState= */ true,
300-
/* excludesMetadataChanges= */ false
295+
snap.hasPendingWrites
301296
);
302297
this.raisedInitialEvent = true;
303298
this.queryObserver.next(snap);
304299
}
305-
306-
/** Returns changes as if all documents in the snap were added. */
307-
private static getInitialViewChanges(
308-
snap: ViewSnapshot
309-
): DocumentViewChange[] {
310-
const result: DocumentViewChange[] = [];
311-
snap.docs.forEach(doc => {
312-
result.push({ type: ChangeType.Added, doc });
313-
});
314-
return result;
315-
}
316300
}

packages/firestore/src/core/sync_engine.ts

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -177,28 +177,40 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
177177
* server. All the subsequent view snapshots or errors are sent to the
178178
* subscribed handlers. Returns the targetId of the query.
179179
*/
180-
listen(query: Query): Promise<TargetId> {
180+
async listen(query: Query): Promise<TargetId> {
181181
this.assertSubscribed('listen()');
182-
assert(
183-
!this.queryViewsByQuery.has(query),
184-
'We already listen to the query: ' + query
185-
);
186182

187-
return this.localStore.allocateQuery(query).then(queryData => {
183+
let targetId;
184+
let viewSnapshot;
185+
186+
const queryView = this.queryViewsByQuery.get(query);
187+
if (queryView) {
188+
// PORTING NOTE: With Mult-Tab Web, it is possible that a query view
189+
// already exists when EventManager calls us for the first time. This
190+
// happens when the primary tab is already listening to this query on
191+
// behalf of another tab and the user of the primary also starts listening
192+
// to the query. EventManager will not have an assigned target ID in this
193+
// case and calls `listen` to obtain this ID.
194+
targetId = queryView.targetId;
195+
this.sharedClientState.addLocalQueryTarget(targetId);
196+
viewSnapshot = queryView.view.computeInitialSnapshot();
197+
} else {
198+
const queryData = await this.localStore.allocateQuery(query);
188199
const status = this.sharedClientState.addLocalQueryTarget(
189200
queryData.targetId
190201
);
191-
return this.initializeViewAndComputeInitialSnapshot(
202+
targetId = queryData.targetId;
203+
viewSnapshot = await this.initializeViewAndComputeInitialSnapshot(
192204
queryData,
193205
status === 'current'
194-
).then(viewSnapshot => {
195-
if (this.isPrimary) {
196-
this.remoteStore.listen(queryData);
197-
}
198-
this.viewHandler!([viewSnapshot]);
199-
return queryData.targetId;
200-
});
201-
});
206+
);
207+
if (this.isPrimary) {
208+
this.remoteStore.listen(queryData);
209+
}
210+
}
211+
212+
this.viewHandler!([viewSnapshot]);
213+
return targetId;
202214
}
203215

204216
private initializeViewAndComputeInitialSnapshot(

packages/firestore/src/core/view.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,9 +383,25 @@ export class View {
383383
return changes;
384384
}
385385

386+
// PORTING NOTE: Multi-tab only.
386387
synchronizeWithRemoteKeys(remoteKeys: DocumentKeySet): void {
387388
this._syncedDocuments = remoteKeys;
388389
}
390+
391+
/**
392+
* Returns a view snapshot as if this query was just listened to. Contains
393+
* a document add for every existing document and the `fromCache` and
394+
* `hasPendingWrites` status of the already established view.
395+
*/
396+
// PORTING NOTE: Multi-tab only.
397+
computeInitialSnapshot(): ViewSnapshot {
398+
return ViewSnapshot.fromInitialDocuments(
399+
this.query,
400+
this.documentSet,
401+
this.syncState === SyncState.Local,
402+
!this.mutatedKeys.isEmpty()
403+
);
404+
}
389405
}
390406

391407
function compareChangeType(c1: ChangeType, c2: ChangeType): number {

packages/firestore/src/core/view_snapshot.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,30 @@ export class ViewSnapshot {
149149
readonly excludesMetadataChanges: boolean
150150
) {}
151151

152+
/** Returns a view snapshot as if all documents in the snapshot were added. */
153+
static fromInitialDocuments(
154+
query: Query,
155+
documents: DocumentSet,
156+
fromCache: boolean,
157+
hasPendingWrites: boolean
158+
): ViewSnapshot {
159+
const changeSet = new DocumentChangeSet();
160+
documents.forEach(doc => {
161+
changeSet.track({ type: ChangeType.Added, doc });
162+
});
163+
164+
return new ViewSnapshot(
165+
query,
166+
documents,
167+
DocumentSet.emptySet(documents),
168+
changeSet.getChanges(),
169+
fromCache,
170+
hasPendingWrites,
171+
/* syncStateChanged */ true,
172+
/* excludesMetadataChanges= */ false
173+
);
174+
}
175+
152176
isEqual(other: ViewSnapshot): boolean {
153177
if (
154178
this.fromCache !== other.fromCache ||

packages/firestore/test/unit/specs/listen_spec.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,35 @@ describeSpec('Listens:', [], () => {
514514
}
515515
);
516516

517+
specTest('Query is joined by primary client', ['multi-client'], () => {
518+
const query = Query.atPath(path('collection'));
519+
const docA = doc('collection/a', 1000, { key: 'a' });
520+
const docB = doc('collection/b', 2000, { key: 'b' });
521+
const docC = doc('collection/c', 3000, { key: 'c' });
522+
523+
return client(0)
524+
.expectPrimaryState(true)
525+
.client(1)
526+
.userListens(query)
527+
.expectEvents(query, { fromCache: true })
528+
.client(0)
529+
.expectListen(query)
530+
.watchAcksFull(query, 100, docA)
531+
.client(1)
532+
.expectEvents(query, { added: [docA] })
533+
.client(0)
534+
.watchSends({ affects: [query] }, docB)
535+
.watchSnapshots(2000)
536+
.userListens(query)
537+
.expectEvents(query, { added: [docA, docB] })
538+
.watchSends({ affects: [query] }, docC)
539+
.watchSnapshots(3000)
540+
.expectEvents(query, { added: [docC] })
541+
.client(1)
542+
.expectEvents(query, { added: [docB] })
543+
.expectEvents(query, { added: [docC] });
544+
});
545+
517546
specTest(
518547
'Query only raises events in participating clients',
519548
['multi-client'],

0 commit comments

Comments
 (0)