@modelprofile.com/browser-runtime 3.0.1 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +18 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.runtime.d.ts +42 -1
- package/dist_ts/classes.runtime.js +435 -33
- package/dist_ts/errors.d.ts +1 -1
- package/dist_ts/errors.js +2 -1
- package/dist_ts/interfaces.d.ts +2 -1
- package/package.json +2 -2
- package/readme.hints.md +2 -0
- package/readme.md +4 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.runtime.ts +541 -36
- package/ts/errors.ts +2 -0
- package/ts/interfaces.ts +4 -0
package/ts/classes.runtime.ts
CHANGED
|
@@ -146,6 +146,7 @@ interface IQueuedOperationRecord {
|
|
|
146
146
|
onOperationStarted?: (operationId: string) => void;
|
|
147
147
|
signal: AbortSignal;
|
|
148
148
|
execute: (signal: AbortSignal, session: ILiveBrowserSessionLike) => Promise<unknown>;
|
|
149
|
+
finalize?: () => Promise<void>;
|
|
149
150
|
resolve(value: unknown): void;
|
|
150
151
|
reject(error: unknown): void;
|
|
151
152
|
state: 'queued' | 'starting' | 'active' | 'settled';
|
|
@@ -154,7 +155,7 @@ interface IQueuedOperationRecord {
|
|
|
154
155
|
|
|
155
156
|
interface IOutstandingFrameRecord {
|
|
156
157
|
acknowledgement: plugins.smartpuppeteer.ILiveBrowserFrameAcknowledgementRequest;
|
|
157
|
-
timer
|
|
158
|
+
timer?: ReturnType<typeof setTimeout>;
|
|
158
159
|
session: ILiveBrowserSessionLike;
|
|
159
160
|
incarnationGeneration: number;
|
|
160
161
|
}
|
|
@@ -172,6 +173,23 @@ interface IFrameSubscriptionRecord {
|
|
|
172
173
|
closed: boolean;
|
|
173
174
|
}
|
|
174
175
|
|
|
176
|
+
interface IFrameRefreshRecord {
|
|
177
|
+
lease: ILeaseRecord;
|
|
178
|
+
subscription: IFrameSubscriptionRecord;
|
|
179
|
+
session: ILiveBrowserSessionLike;
|
|
180
|
+
incarnationGeneration: number;
|
|
181
|
+
initialTabId: string;
|
|
182
|
+
initialGeneration: number;
|
|
183
|
+
initialViewportRevision: number;
|
|
184
|
+
startingHighestSequence: number;
|
|
185
|
+
pendingAcknowledgements: Set<Promise<TFrameAcknowledgementOutcome>>;
|
|
186
|
+
refreshStarted: boolean;
|
|
187
|
+
admissionClosed: boolean;
|
|
188
|
+
abandoned: boolean;
|
|
189
|
+
boundaryCandidate?: plugins.smartpuppeteer.ILiveBrowserFrameIdentity;
|
|
190
|
+
failure?: BrowserRuntimeError;
|
|
191
|
+
}
|
|
192
|
+
|
|
175
193
|
interface IResourceSlot {
|
|
176
194
|
projectId: string;
|
|
177
195
|
browserResourceId: string;
|
|
@@ -195,6 +213,7 @@ interface IResourceSlot {
|
|
|
195
213
|
operationQueue: IQueuedOperationRecord[];
|
|
196
214
|
operationSchedulerRunning: boolean;
|
|
197
215
|
frameSubscription?: IFrameSubscriptionRecord;
|
|
216
|
+
frameRefresh?: IFrameRefreshRecord;
|
|
198
217
|
idleTimer?: ReturnType<typeof setTimeout>;
|
|
199
218
|
lifecycleTail: Promise<void>;
|
|
200
219
|
lifecycleOperationCount: number;
|
|
@@ -959,11 +978,14 @@ export class BrowserRuntime {
|
|
|
959
978
|
|
|
960
979
|
public async disconnectPeer(peerIdArg: string): Promise<void> {
|
|
961
980
|
const peerId = this.validateIdentifier(peerIdArg, 'peerId');
|
|
962
|
-
const matching =
|
|
963
|
-
|
|
964
|
-
|
|
981
|
+
const matching = new Set([
|
|
982
|
+
...[...this.capabilitiesById.values()].filter((record) => (
|
|
983
|
+
record.peerId === peerId
|
|
984
|
+
)),
|
|
985
|
+
...[...this.failedRevocations].filter((record) => record.peerId === peerId),
|
|
986
|
+
]);
|
|
965
987
|
const results = await Promise.allSettled(
|
|
966
|
-
matching.map((record) => this.revokeCapabilityRecord(record, false)),
|
|
988
|
+
[...matching].map((record) => this.revokeCapabilityRecord(record, false)),
|
|
967
989
|
);
|
|
968
990
|
const errors = results
|
|
969
991
|
.filter((result): result is PromiseRejectedResult => result.status === 'rejected')
|
|
@@ -1153,6 +1175,39 @@ export class BrowserRuntime {
|
|
|
1153
1175
|
);
|
|
1154
1176
|
}
|
|
1155
1177
|
|
|
1178
|
+
/** @internal */
|
|
1179
|
+
public async leaseRefreshFrameStream(
|
|
1180
|
+
lease: ILeaseRecord,
|
|
1181
|
+
operationOptions: IBrowserRuntimeOperationOptions = {},
|
|
1182
|
+
): Promise<void> {
|
|
1183
|
+
this.requireHuman(lease);
|
|
1184
|
+
this.requireValidLease(lease);
|
|
1185
|
+
const slot = lease.slot;
|
|
1186
|
+
const subscription = slot.frameSubscription;
|
|
1187
|
+
if (!subscription || subscription.closed || subscription.lease !== lease) {
|
|
1188
|
+
throw new BrowserRuntimeError('BUSY');
|
|
1189
|
+
}
|
|
1190
|
+
let refresh: IFrameRefreshRecord | undefined;
|
|
1191
|
+
return this.runOperation(
|
|
1192
|
+
lease,
|
|
1193
|
+
'refreshFrameStream',
|
|
1194
|
+
'frame-stream',
|
|
1195
|
+
operationOptions,
|
|
1196
|
+
async (signal, session) => {
|
|
1197
|
+
refresh = this.beginFrameRefresh(
|
|
1198
|
+
slot,
|
|
1199
|
+
lease,
|
|
1200
|
+
subscription,
|
|
1201
|
+
session,
|
|
1202
|
+
);
|
|
1203
|
+
await this.executeFrameRefresh(refresh, signal);
|
|
1204
|
+
},
|
|
1205
|
+
async () => {
|
|
1206
|
+
if (refresh) await this.finalizeFrameRefresh(slot, refresh);
|
|
1207
|
+
},
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1156
1211
|
/** @internal */
|
|
1157
1212
|
public async leaseDispatchRawInput(
|
|
1158
1213
|
lease: ILeaseRecord,
|
|
@@ -1191,7 +1246,7 @@ export class BrowserRuntime {
|
|
|
1191
1246
|
let subscription: IFrameSubscriptionRecord;
|
|
1192
1247
|
try {
|
|
1193
1248
|
this.requireValidLease(lease);
|
|
1194
|
-
if (slot.frameSubscription) throw new BrowserRuntimeError('BUSY');
|
|
1249
|
+
if (slot.frameSubscription || slot.frameRefresh) throw new BrowserRuntimeError('BUSY');
|
|
1195
1250
|
subscription = {
|
|
1196
1251
|
lease,
|
|
1197
1252
|
listener,
|
|
@@ -1226,14 +1281,34 @@ export class BrowserRuntime {
|
|
|
1226
1281
|
if (!subscription || subscription.lease !== lease) return false;
|
|
1227
1282
|
const outstanding = subscription.outstanding.get(key);
|
|
1228
1283
|
if (!outstanding) return false;
|
|
1229
|
-
subscription.outstanding.delete(key);
|
|
1230
|
-
clearTimeout(outstanding.timer);
|
|
1231
1284
|
const { session, incarnationGeneration } = outstanding;
|
|
1232
|
-
const
|
|
1285
|
+
const refresh = this.matchingFrameRefresh(
|
|
1286
|
+
slot,
|
|
1287
|
+
subscription,
|
|
1288
|
+
session,
|
|
1289
|
+
incarnationGeneration,
|
|
1290
|
+
);
|
|
1291
|
+
if (refresh?.admissionClosed) {
|
|
1292
|
+
subscription.outstanding.delete(key);
|
|
1293
|
+
if (outstanding.timer) clearTimeout(outstanding.timer);
|
|
1294
|
+
outstanding.timer = undefined;
|
|
1295
|
+
return false;
|
|
1296
|
+
}
|
|
1297
|
+
subscription.outstanding.delete(key);
|
|
1298
|
+
if (outstanding.timer) clearTimeout(outstanding.timer);
|
|
1299
|
+
outstanding.timer = undefined;
|
|
1300
|
+
const refreshOwned = Boolean(refresh && !refresh.abandoned);
|
|
1301
|
+
const outcome = await this.beginFrameAcknowledgement(
|
|
1233
1302
|
session,
|
|
1234
1303
|
outstanding.acknowledgement,
|
|
1304
|
+
refreshOwned ? refresh : undefined,
|
|
1235
1305
|
);
|
|
1236
1306
|
if (outcome.status === 'fulfilled') return outcome.accepted;
|
|
1307
|
+
if (refreshOwned) return false;
|
|
1308
|
+
if (refresh && slot.frameRefresh === refresh) {
|
|
1309
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
1310
|
+
return false;
|
|
1311
|
+
}
|
|
1237
1312
|
await this.options.beforeFrameFailureTermination?.();
|
|
1238
1313
|
await this.handleFrameFailure(slot, session, incarnationGeneration, subscription.lease);
|
|
1239
1314
|
return false;
|
|
@@ -1641,6 +1716,7 @@ export class BrowserRuntime {
|
|
|
1641
1716
|
classification: TBrowserRuntimeOperationClassification,
|
|
1642
1717
|
operationOptions: IBrowserRuntimeOperationOptions,
|
|
1643
1718
|
execute: (signal: AbortSignal, session: ILiveBrowserSessionLike) => Promise<T>,
|
|
1719
|
+
finalize?: () => Promise<void>,
|
|
1644
1720
|
): Promise<T> {
|
|
1645
1721
|
if (!operationOptions || typeof operationOptions !== 'object') {
|
|
1646
1722
|
throw new BrowserRuntimeError('INVALID_INPUT');
|
|
@@ -1679,6 +1755,7 @@ export class BrowserRuntime {
|
|
|
1679
1755
|
onOperationStarted: operationOptions.onOperationStarted,
|
|
1680
1756
|
signal,
|
|
1681
1757
|
execute: execute as IQueuedOperationRecord['execute'],
|
|
1758
|
+
finalize,
|
|
1682
1759
|
resolve: (value) => resolve(value as T),
|
|
1683
1760
|
reject,
|
|
1684
1761
|
state: 'queued',
|
|
@@ -1814,6 +1891,7 @@ export class BrowserRuntime {
|
|
|
1814
1891
|
let failed = false;
|
|
1815
1892
|
let failure: unknown;
|
|
1816
1893
|
let executionStarted = false;
|
|
1894
|
+
let executionSettled = false;
|
|
1817
1895
|
try {
|
|
1818
1896
|
await this.runBeforeOperation(operationIdentity, combinedSignal);
|
|
1819
1897
|
const executionRelease = await slot.mutex.acquire();
|
|
@@ -1830,7 +1908,9 @@ export class BrowserRuntime {
|
|
|
1830
1908
|
) throw new BrowserRuntimeError('ABORTED');
|
|
1831
1909
|
executionStarted = true;
|
|
1832
1910
|
executionPromise = queued.execute(combinedSignal, session);
|
|
1833
|
-
operation.promise = executionPromise.then(() => undefined, () => undefined)
|
|
1911
|
+
operation.promise = executionPromise.then(() => undefined, () => undefined).finally(() => {
|
|
1912
|
+
executionSettled = true;
|
|
1913
|
+
});
|
|
1834
1914
|
} finally {
|
|
1835
1915
|
executionRelease();
|
|
1836
1916
|
}
|
|
@@ -1876,6 +1956,18 @@ export class BrowserRuntime {
|
|
|
1876
1956
|
} else {
|
|
1877
1957
|
await cleanupSlot();
|
|
1878
1958
|
}
|
|
1959
|
+
if (
|
|
1960
|
+
queued.finalize
|
|
1961
|
+
&& (!executionStarted || executionSettled)
|
|
1962
|
+
&& slot.operation !== operation
|
|
1963
|
+
) {
|
|
1964
|
+
try {
|
|
1965
|
+
await queued.finalize();
|
|
1966
|
+
} catch (error) {
|
|
1967
|
+
failed = true;
|
|
1968
|
+
failure = error;
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1879
1971
|
const errorCode = failed ? this.operationErrorCode(failure) : undefined;
|
|
1880
1972
|
await this.emitAudit(Object.freeze({
|
|
1881
1973
|
...operationIdentity,
|
|
@@ -2194,6 +2286,271 @@ export class BrowserRuntime {
|
|
|
2194
2286
|
};
|
|
2195
2287
|
}
|
|
2196
2288
|
|
|
2289
|
+
private beginFrameRefresh(
|
|
2290
|
+
slot: IResourceSlot,
|
|
2291
|
+
lease: ILeaseRecord,
|
|
2292
|
+
subscription: IFrameSubscriptionRecord,
|
|
2293
|
+
session: ILiveBrowserSessionLike,
|
|
2294
|
+
): IFrameRefreshRecord {
|
|
2295
|
+
if (
|
|
2296
|
+
slot.frameRefresh
|
|
2297
|
+
|| slot.frameSubscription !== subscription
|
|
2298
|
+
|| subscription.closed
|
|
2299
|
+
|| subscription.lease !== lease
|
|
2300
|
+
|| slot.lease !== lease
|
|
2301
|
+
|| slot.session !== session
|
|
2302
|
+
) throw new BrowserRuntimeError('BUSY');
|
|
2303
|
+
const state = session.getState();
|
|
2304
|
+
const activeTab = state.tabs.find((tab) => tab.id === state.activeTabId);
|
|
2305
|
+
if (
|
|
2306
|
+
!activeTab
|
|
2307
|
+
|| !activeTab.active
|
|
2308
|
+
|| activeTab.status !== 'open'
|
|
2309
|
+
|| !activeTab.streaming
|
|
2310
|
+
) throw new BrowserRuntimeError('BUSY');
|
|
2311
|
+
const refresh: IFrameRefreshRecord = {
|
|
2312
|
+
lease,
|
|
2313
|
+
subscription,
|
|
2314
|
+
session,
|
|
2315
|
+
incarnationGeneration: slot.incarnationGeneration,
|
|
2316
|
+
initialTabId: activeTab.id,
|
|
2317
|
+
initialGeneration: activeTab.generation,
|
|
2318
|
+
initialViewportRevision: state.viewportRevision,
|
|
2319
|
+
startingHighestSequence: subscription.highestSequence,
|
|
2320
|
+
pendingAcknowledgements: new Set(),
|
|
2321
|
+
refreshStarted: false,
|
|
2322
|
+
admissionClosed: false,
|
|
2323
|
+
abandoned: false,
|
|
2324
|
+
};
|
|
2325
|
+
slot.frameRefresh = refresh;
|
|
2326
|
+
return refresh;
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
private async executeFrameRefresh(
|
|
2330
|
+
refresh: IFrameRefreshRecord,
|
|
2331
|
+
signal: AbortSignal,
|
|
2332
|
+
): Promise<void> {
|
|
2333
|
+
let operationError: unknown;
|
|
2334
|
+
try {
|
|
2335
|
+
await this.drainFrameRefreshOutstanding(refresh);
|
|
2336
|
+
if (refresh.failure) throw refresh.failure;
|
|
2337
|
+
signal.throwIfAborted();
|
|
2338
|
+
refresh.refreshStarted = true;
|
|
2339
|
+
const identity = this.validateFrameAcknowledgement(
|
|
2340
|
+
await refresh.session.refreshScreencast({ signal }),
|
|
2341
|
+
true,
|
|
2342
|
+
);
|
|
2343
|
+
this.validateFrameRefreshBoundary(refresh, identity);
|
|
2344
|
+
await this.reconcileFrameRefreshOutstanding(refresh, identity);
|
|
2345
|
+
if (refresh.failure) throw refresh.failure;
|
|
2346
|
+
} catch (error) {
|
|
2347
|
+
operationError = error;
|
|
2348
|
+
if (signal.aborted && refresh.refreshStarted) {
|
|
2349
|
+
try {
|
|
2350
|
+
const candidate = refresh.boundaryCandidate;
|
|
2351
|
+
if (!candidate) throw new BrowserRuntimeError('FRAME_STREAM_FAILED');
|
|
2352
|
+
this.validateFrameRefreshBoundary(refresh, candidate);
|
|
2353
|
+
await this.reconcileFrameRefreshOutstanding(refresh, candidate);
|
|
2354
|
+
} catch {
|
|
2355
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2356
|
+
}
|
|
2357
|
+
} else if (!signal.aborted && !refresh.failure) {
|
|
2358
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2359
|
+
}
|
|
2360
|
+
} finally {
|
|
2361
|
+
refresh.admissionClosed = true;
|
|
2362
|
+
await Promise.allSettled([...refresh.pendingAcknowledgements]);
|
|
2363
|
+
}
|
|
2364
|
+
if (refresh.failure) throw refresh.failure;
|
|
2365
|
+
if (operationError !== undefined) throw operationError;
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
private async drainFrameRefreshOutstanding(refresh: IFrameRefreshRecord): Promise<void> {
|
|
2369
|
+
const outstanding = [...refresh.subscription.outstanding.values()];
|
|
2370
|
+
refresh.subscription.outstanding.clear();
|
|
2371
|
+
for (const frame of outstanding) {
|
|
2372
|
+
if (frame.timer) clearTimeout(frame.timer);
|
|
2373
|
+
frame.timer = undefined;
|
|
2374
|
+
}
|
|
2375
|
+
await Promise.all(outstanding.map((frame) => this.beginFrameAcknowledgement(
|
|
2376
|
+
frame.session,
|
|
2377
|
+
frame.acknowledgement,
|
|
2378
|
+
refresh,
|
|
2379
|
+
)));
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
private validateFrameRefreshBoundary(
|
|
2383
|
+
refresh: IFrameRefreshRecord,
|
|
2384
|
+
identity: plugins.smartpuppeteer.ILiveBrowserFrameIdentity,
|
|
2385
|
+
): void {
|
|
2386
|
+
const candidate = refresh.boundaryCandidate;
|
|
2387
|
+
const state = refresh.session.getState();
|
|
2388
|
+
const activeTab = state.tabs.find((tab) => tab.id === state.activeTabId);
|
|
2389
|
+
if (
|
|
2390
|
+
!candidate
|
|
2391
|
+
|| this.frameAcknowledgementKey(candidate) !== this.frameAcknowledgementKey(identity)
|
|
2392
|
+
|| identity.tabId !== refresh.initialTabId
|
|
2393
|
+
|| identity.sequence <= refresh.startingHighestSequence
|
|
2394
|
+
|| identity.generation <= refresh.initialGeneration
|
|
2395
|
+
|| identity.viewportRevision !== refresh.initialViewportRevision
|
|
2396
|
+
|| state.activeTabId !== refresh.initialTabId
|
|
2397
|
+
|| state.viewportRevision !== refresh.initialViewportRevision
|
|
2398
|
+
|| !activeTab
|
|
2399
|
+
|| !activeTab.active
|
|
2400
|
+
|| activeTab.status !== 'open'
|
|
2401
|
+
|| !activeTab.streaming
|
|
2402
|
+
|| activeTab.generation !== identity.generation
|
|
2403
|
+
|| activeTab.appliedViewportRevision !== identity.viewportRevision
|
|
2404
|
+
) throw new BrowserRuntimeError('FRAME_STREAM_FAILED');
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
private async reconcileFrameRefreshOutstanding(
|
|
2408
|
+
refresh: IFrameRefreshRecord,
|
|
2409
|
+
identity: plugins.smartpuppeteer.ILiveBrowserFrameIdentity,
|
|
2410
|
+
): Promise<void> {
|
|
2411
|
+
const retirements: Array<Promise<TFrameAcknowledgementOutcome>> = [];
|
|
2412
|
+
for (const [key, frame] of refresh.subscription.outstanding) {
|
|
2413
|
+
const acknowledgement = frame.acknowledgement;
|
|
2414
|
+
if (
|
|
2415
|
+
acknowledgement.tabId === identity.tabId
|
|
2416
|
+
&& acknowledgement.generation === identity.generation
|
|
2417
|
+
&& acknowledgement.viewportRevision === identity.viewportRevision
|
|
2418
|
+
&& acknowledgement.sequence >= identity.sequence
|
|
2419
|
+
) continue;
|
|
2420
|
+
refresh.subscription.outstanding.delete(key);
|
|
2421
|
+
if (frame.timer) clearTimeout(frame.timer);
|
|
2422
|
+
frame.timer = undefined;
|
|
2423
|
+
retirements.push(this.beginFrameAcknowledgement(
|
|
2424
|
+
frame.session,
|
|
2425
|
+
acknowledgement,
|
|
2426
|
+
refresh,
|
|
2427
|
+
));
|
|
2428
|
+
}
|
|
2429
|
+
await Promise.all(retirements);
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
private beginFrameAcknowledgement(
|
|
2433
|
+
session: ILiveBrowserSessionLike,
|
|
2434
|
+
acknowledgement: plugins.smartpuppeteer.ILiveBrowserFrameAcknowledgementRequest,
|
|
2435
|
+
refresh?: IFrameRefreshRecord,
|
|
2436
|
+
): Promise<TFrameAcknowledgementOutcome> {
|
|
2437
|
+
const operation = this.waitForFrameAcknowledgement(session, acknowledgement);
|
|
2438
|
+
if (!refresh || refresh.admissionClosed || refresh.abandoned) return operation;
|
|
2439
|
+
const tracked = operation.then((outcome) => {
|
|
2440
|
+
if (outcome.status !== 'fulfilled') this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2441
|
+
return outcome;
|
|
2442
|
+
});
|
|
2443
|
+
refresh.pendingAcknowledgements.add(tracked);
|
|
2444
|
+
void tracked.then(
|
|
2445
|
+
() => refresh.pendingAcknowledgements.delete(tracked),
|
|
2446
|
+
() => refresh.pendingAcknowledgements.delete(tracked),
|
|
2447
|
+
);
|
|
2448
|
+
return tracked;
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
private failFrameRefresh(
|
|
2452
|
+
refresh: IFrameRefreshRecord,
|
|
2453
|
+
code: 'FRAME_STREAM_FAILED' | 'FRAME_TOO_LARGE',
|
|
2454
|
+
): void {
|
|
2455
|
+
refresh.failure ??= new BrowserRuntimeError(code);
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
private async finalizeFrameRefresh(
|
|
2459
|
+
slot: IResourceSlot,
|
|
2460
|
+
refresh: IFrameRefreshRecord,
|
|
2461
|
+
): Promise<void> {
|
|
2462
|
+
if (refresh.failure) {
|
|
2463
|
+
await this.options.beforeFrameFailureTermination?.();
|
|
2464
|
+
const claimed = await this.handleFrameFailure(
|
|
2465
|
+
slot,
|
|
2466
|
+
refresh.session,
|
|
2467
|
+
refresh.incarnationGeneration,
|
|
2468
|
+
refresh.lease,
|
|
2469
|
+
refresh,
|
|
2470
|
+
);
|
|
2471
|
+
if (!claimed) {
|
|
2472
|
+
this.abandonFrameRefresh(slot, refresh);
|
|
2473
|
+
throw new BrowserRuntimeError('ABORTED');
|
|
2474
|
+
}
|
|
2475
|
+
throw refresh.failure;
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
let current = false;
|
|
2479
|
+
const release = await slot.mutex.acquire();
|
|
2480
|
+
try {
|
|
2481
|
+
try {
|
|
2482
|
+
this.requireValidLease(refresh.lease);
|
|
2483
|
+
current = slot.frameRefresh === refresh
|
|
2484
|
+
&& !refresh.abandoned
|
|
2485
|
+
&& slot.frameSubscription === refresh.subscription
|
|
2486
|
+
&& !refresh.subscription.closed
|
|
2487
|
+
&& slot.session === refresh.session
|
|
2488
|
+
&& slot.incarnationGeneration === refresh.incarnationGeneration;
|
|
2489
|
+
} catch {
|
|
2490
|
+
current = false;
|
|
2491
|
+
}
|
|
2492
|
+
if (current) {
|
|
2493
|
+
slot.frameRefresh = undefined;
|
|
2494
|
+
for (const [key, frame] of refresh.subscription.outstanding) {
|
|
2495
|
+
if (!frame.timer) this.armFrameAcknowledgementTimer(refresh.subscription, key, frame);
|
|
2496
|
+
}
|
|
2497
|
+
} else {
|
|
2498
|
+
this.abandonFrameRefresh(slot, refresh);
|
|
2499
|
+
}
|
|
2500
|
+
} finally {
|
|
2501
|
+
release();
|
|
2502
|
+
}
|
|
2503
|
+
if (!current) throw new BrowserRuntimeError('ABORTED');
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
private abandonFrameRefresh(slot: IResourceSlot, expected?: IFrameRefreshRecord): void {
|
|
2507
|
+
const refresh = slot.frameRefresh;
|
|
2508
|
+
if (!refresh || (expected && refresh !== expected)) return;
|
|
2509
|
+
refresh.admissionClosed = true;
|
|
2510
|
+
refresh.abandoned = true;
|
|
2511
|
+
slot.frameRefresh = undefined;
|
|
2512
|
+
if (
|
|
2513
|
+
slot.frameSubscription === refresh.subscription
|
|
2514
|
+
&& !refresh.subscription.closed
|
|
2515
|
+
&& slot.session === refresh.session
|
|
2516
|
+
&& slot.incarnationGeneration === refresh.incarnationGeneration
|
|
2517
|
+
) {
|
|
2518
|
+
for (const [key, frame] of refresh.subscription.outstanding) {
|
|
2519
|
+
if (!frame.timer) this.armFrameAcknowledgementTimer(refresh.subscription, key, frame);
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
private armFrameAcknowledgementTimer(
|
|
2525
|
+
subscription: IFrameSubscriptionRecord,
|
|
2526
|
+
key: string,
|
|
2527
|
+
frame: IOutstandingFrameRecord,
|
|
2528
|
+
): void {
|
|
2529
|
+
if (frame.timer || subscription.closed || !subscription.outstanding.has(key)) return;
|
|
2530
|
+
const timer = setTimeout(() => {
|
|
2531
|
+
this.trackCleanup(this.retireOutstandingFrame(subscription, key, true).then(() => undefined));
|
|
2532
|
+
}, this.options.frameAcknowledgementTimeoutMs);
|
|
2533
|
+
timer.unref();
|
|
2534
|
+
frame.timer = timer;
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
private matchingFrameRefresh(
|
|
2538
|
+
slot: IResourceSlot,
|
|
2539
|
+
subscription: IFrameSubscriptionRecord,
|
|
2540
|
+
session: ILiveBrowserSessionLike,
|
|
2541
|
+
incarnationGeneration: number,
|
|
2542
|
+
): IFrameRefreshRecord | undefined {
|
|
2543
|
+
const refresh = slot.frameRefresh;
|
|
2544
|
+
return refresh
|
|
2545
|
+
&& !refresh.abandoned
|
|
2546
|
+
&& refresh.subscription === subscription
|
|
2547
|
+
&& refresh.lease === subscription.lease
|
|
2548
|
+
&& refresh.session === session
|
|
2549
|
+
&& refresh.incarnationGeneration === incarnationGeneration
|
|
2550
|
+
? refresh
|
|
2551
|
+
: undefined;
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2197
2554
|
private handleSessionEvent(
|
|
2198
2555
|
slot: IResourceSlot,
|
|
2199
2556
|
session: ILiveBrowserSessionLike,
|
|
@@ -2211,6 +2568,12 @@ export class BrowserRuntime {
|
|
|
2211
2568
|
return;
|
|
2212
2569
|
}
|
|
2213
2570
|
const subscription = slot.frameSubscription;
|
|
2571
|
+
const refreshForSession = slot.frameRefresh
|
|
2572
|
+
&& !slot.frameRefresh.abandoned
|
|
2573
|
+
&& slot.frameRefresh.session === session
|
|
2574
|
+
&& slot.frameRefresh.incarnationGeneration === slot.incarnationGeneration
|
|
2575
|
+
? slot.frameRefresh
|
|
2576
|
+
: undefined;
|
|
2214
2577
|
if (event.type === 'frame') {
|
|
2215
2578
|
let acknowledgement: plugins.smartpuppeteer.ILiveBrowserFrameAcknowledgementRequest;
|
|
2216
2579
|
try {
|
|
@@ -2219,38 +2582,72 @@ export class BrowserRuntime {
|
|
|
2219
2582
|
}
|
|
2220
2583
|
acknowledgement = this.validateFrameAcknowledgement(event.frame);
|
|
2221
2584
|
} catch {
|
|
2585
|
+
if (refreshForSession) {
|
|
2586
|
+
this.failFrameRefresh(refreshForSession, 'FRAME_STREAM_FAILED');
|
|
2587
|
+
return;
|
|
2588
|
+
}
|
|
2222
2589
|
const failedLease = slot.session === session ? (slot.lease ?? null) : null;
|
|
2223
2590
|
this.trackCleanup(this.handleFrameFailure(
|
|
2224
2591
|
slot,
|
|
2225
2592
|
session,
|
|
2226
2593
|
slot.incarnationGeneration,
|
|
2227
2594
|
failedLease,
|
|
2228
|
-
));
|
|
2595
|
+
).then(() => undefined));
|
|
2229
2596
|
return;
|
|
2230
2597
|
}
|
|
2231
2598
|
if (!subscription || subscription.closed) {
|
|
2599
|
+
if (refreshForSession) {
|
|
2600
|
+
this.failFrameRefresh(refreshForSession, 'FRAME_STREAM_FAILED');
|
|
2601
|
+
return;
|
|
2602
|
+
}
|
|
2232
2603
|
this.trackCleanup(this.acknowledgeFrameInBackground(slot, session, acknowledgement));
|
|
2233
2604
|
return;
|
|
2234
2605
|
}
|
|
2606
|
+
const refresh = this.matchingFrameRefresh(
|
|
2607
|
+
slot,
|
|
2608
|
+
subscription,
|
|
2609
|
+
session,
|
|
2610
|
+
slot.incarnationGeneration,
|
|
2611
|
+
);
|
|
2612
|
+
if (refreshForSession && refreshForSession !== refresh) {
|
|
2613
|
+
this.failFrameRefresh(refreshForSession, 'FRAME_STREAM_FAILED');
|
|
2614
|
+
return;
|
|
2615
|
+
}
|
|
2235
2616
|
try {
|
|
2236
2617
|
this.requireValidLease(subscription.lease);
|
|
2237
2618
|
} catch {
|
|
2619
|
+
if (refresh) {
|
|
2620
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2621
|
+
return;
|
|
2622
|
+
}
|
|
2238
2623
|
this.trackCleanup(this.acknowledgeFrameInBackground(slot, session, acknowledgement));
|
|
2239
2624
|
this.trackCleanup(this.revokeCapabilityRecord(subscription.lease.capability));
|
|
2240
2625
|
return;
|
|
2241
2626
|
}
|
|
2242
2627
|
const key = this.frameAcknowledgementKey(acknowledgement);
|
|
2243
2628
|
if (acknowledgement.sequence <= subscription.highestSequence) {
|
|
2629
|
+
if (refresh) {
|
|
2630
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2631
|
+
return;
|
|
2632
|
+
}
|
|
2244
2633
|
this.trackCleanup(this.handleFrameFailure(
|
|
2245
2634
|
slot,
|
|
2246
2635
|
session,
|
|
2247
2636
|
slot.incarnationGeneration,
|
|
2248
2637
|
subscription.lease,
|
|
2249
|
-
));
|
|
2638
|
+
).then(() => undefined));
|
|
2250
2639
|
return;
|
|
2251
2640
|
}
|
|
2252
2641
|
subscription.highestSequence = acknowledgement.sequence;
|
|
2253
2642
|
if (event.frame.data.byteLength > this.options.maxFrameBytes) {
|
|
2643
|
+
if (refresh) {
|
|
2644
|
+
this.failFrameRefresh(refresh, 'FRAME_TOO_LARGE');
|
|
2645
|
+
this.pushToSubscription(subscription, {
|
|
2646
|
+
type: 'error',
|
|
2647
|
+
error: { code: 'FRAME_TOO_LARGE', fatal: true, tabId: event.frame.tabId },
|
|
2648
|
+
});
|
|
2649
|
+
return;
|
|
2650
|
+
}
|
|
2254
2651
|
this.trackCleanup(this.acknowledgeFrameInBackground(
|
|
2255
2652
|
slot,
|
|
2256
2653
|
session,
|
|
@@ -2263,28 +2660,64 @@ export class BrowserRuntime {
|
|
|
2263
2660
|
});
|
|
2264
2661
|
return;
|
|
2265
2662
|
}
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
);
|
|
2663
|
+
if (refresh?.admissionClosed) return;
|
|
2664
|
+
const refreshAdmission = refresh && !refresh.admissionClosed && !refresh.abandoned
|
|
2665
|
+
? refresh
|
|
2666
|
+
: undefined;
|
|
2667
|
+
if (refreshAdmission) {
|
|
2668
|
+
if (refreshAdmission.failure) return;
|
|
2669
|
+
if (
|
|
2670
|
+
subscription.outstanding.size + refreshAdmission.pendingAcknowledgements.size
|
|
2671
|
+
>= this.options.maxOutstandingFrames
|
|
2672
|
+
) {
|
|
2673
|
+
this.failFrameRefresh(refreshAdmission, 'FRAME_STREAM_FAILED');
|
|
2674
|
+
return;
|
|
2675
|
+
}
|
|
2676
|
+
} else {
|
|
2677
|
+
if (refresh && subscription.outstanding.size >= this.options.maxOutstandingFrames) {
|
|
2678
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2679
|
+
}
|
|
2680
|
+
while (subscription.outstanding.size >= this.options.maxOutstandingFrames) {
|
|
2681
|
+
const oldestKey = subscription.outstanding.keys().next().value as string | undefined;
|
|
2682
|
+
if (!oldestKey) break;
|
|
2683
|
+
this.trackCleanup(
|
|
2684
|
+
this.retireOutstandingFrame(subscription, oldestKey, false).then(() => undefined),
|
|
2685
|
+
);
|
|
2686
|
+
}
|
|
2272
2687
|
}
|
|
2273
|
-
const
|
|
2274
|
-
this.trackCleanup(this.retireOutstandingFrame(subscription, key, true).then(() => undefined));
|
|
2275
|
-
}, this.options.frameAcknowledgementTimeoutMs);
|
|
2276
|
-
timer.unref();
|
|
2277
|
-
subscription.outstanding.set(key, {
|
|
2688
|
+
const outstanding: IOutstandingFrameRecord = {
|
|
2278
2689
|
acknowledgement,
|
|
2279
|
-
timer,
|
|
2280
2690
|
session,
|
|
2281
2691
|
incarnationGeneration: slot.incarnationGeneration,
|
|
2282
|
-
}
|
|
2283
|
-
|
|
2692
|
+
};
|
|
2693
|
+
subscription.outstanding.set(key, outstanding);
|
|
2694
|
+
if (!refreshAdmission) this.armFrameAcknowledgementTimer(subscription, key, outstanding);
|
|
2695
|
+
const delivered = this.pushToSubscription(subscription, { type: 'frame', frame: event.frame });
|
|
2696
|
+
if (
|
|
2697
|
+
delivered
|
|
2698
|
+
&& refreshAdmission
|
|
2699
|
+
&& refreshAdmission.refreshStarted
|
|
2700
|
+
&& !refreshAdmission.boundaryCandidate
|
|
2701
|
+
) {
|
|
2702
|
+
if (
|
|
2703
|
+
acknowledgement.tabId === refreshAdmission.initialTabId
|
|
2704
|
+
&& acknowledgement.sequence > refreshAdmission.startingHighestSequence
|
|
2705
|
+
&& acknowledgement.generation > refreshAdmission.initialGeneration
|
|
2706
|
+
&& acknowledgement.viewportRevision === refreshAdmission.initialViewportRevision
|
|
2707
|
+
) {
|
|
2708
|
+
refreshAdmission.boundaryCandidate = { ...acknowledgement };
|
|
2709
|
+
} else {
|
|
2710
|
+
this.failFrameRefresh(refreshAdmission, 'FRAME_STREAM_FAILED');
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2284
2713
|
return;
|
|
2285
2714
|
}
|
|
2286
2715
|
if (event.type === 'error' && event.error.fatal && slot.lease) {
|
|
2287
|
-
|
|
2716
|
+
if (refreshForSession) {
|
|
2717
|
+
this.failFrameRefresh(refreshForSession, 'FRAME_STREAM_FAILED');
|
|
2718
|
+
} else {
|
|
2719
|
+
this.trackCleanup(this.revokeCapabilityRecord(slot.lease.capability));
|
|
2720
|
+
}
|
|
2288
2721
|
}
|
|
2289
2722
|
if (!subscription || subscription.closed) return;
|
|
2290
2723
|
if (event.type === 'state') {
|
|
@@ -2304,22 +2737,30 @@ export class BrowserRuntime {
|
|
|
2304
2737
|
private pushToSubscription(
|
|
2305
2738
|
subscription: IFrameSubscriptionRecord,
|
|
2306
2739
|
event: TBrowserRuntimeEvent,
|
|
2307
|
-
):
|
|
2740
|
+
): boolean {
|
|
2308
2741
|
try {
|
|
2309
2742
|
this.requireValidLease(subscription.lease);
|
|
2310
2743
|
subscription.listener(event);
|
|
2744
|
+
return true;
|
|
2311
2745
|
} catch {
|
|
2312
2746
|
const slot = subscription.lease.slot;
|
|
2313
2747
|
const session = slot.session;
|
|
2314
2748
|
const incarnationGeneration = slot.incarnationGeneration;
|
|
2749
|
+
const refresh = session
|
|
2750
|
+
? this.matchingFrameRefresh(slot, subscription, session, incarnationGeneration)
|
|
2751
|
+
: undefined;
|
|
2315
2752
|
if (event.type === 'frame') {
|
|
2316
2753
|
const key = this.frameAcknowledgementKey(this.validateFrameAcknowledgement(event.frame));
|
|
2317
2754
|
this.trackCleanup(this.retireOutstandingFrame(subscription, key, true).then(() => undefined));
|
|
2318
|
-
return;
|
|
2755
|
+
return false;
|
|
2756
|
+
}
|
|
2757
|
+
if (refresh) {
|
|
2758
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2759
|
+
return false;
|
|
2319
2760
|
}
|
|
2320
2761
|
if (!session) {
|
|
2321
2762
|
this.trackCleanup(this.revokeCapabilityRecord(subscription.lease.capability));
|
|
2322
|
-
return;
|
|
2763
|
+
return false;
|
|
2323
2764
|
}
|
|
2324
2765
|
this.trackCleanup((async () => {
|
|
2325
2766
|
await this.options.beforeFrameFailureTermination?.();
|
|
@@ -2330,6 +2771,7 @@ export class BrowserRuntime {
|
|
|
2330
2771
|
subscription.lease,
|
|
2331
2772
|
);
|
|
2332
2773
|
})());
|
|
2774
|
+
return false;
|
|
2333
2775
|
}
|
|
2334
2776
|
}
|
|
2335
2777
|
|
|
@@ -2337,6 +2779,10 @@ export class BrowserRuntime {
|
|
|
2337
2779
|
if (subscription.closed) return;
|
|
2338
2780
|
subscription.closed = true;
|
|
2339
2781
|
const slot = subscription.lease.slot;
|
|
2782
|
+
const refresh = slot.frameRefresh;
|
|
2783
|
+
if (refresh?.subscription === subscription && !refresh.abandoned) {
|
|
2784
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2785
|
+
}
|
|
2340
2786
|
if (slot.frameSubscription === subscription) slot.frameSubscription = undefined;
|
|
2341
2787
|
for (const key of [...subscription.outstanding.keys()]) {
|
|
2342
2788
|
await this.retireOutstandingFrame(subscription, key, false);
|
|
@@ -2351,15 +2797,37 @@ export class BrowserRuntime {
|
|
|
2351
2797
|
const outstanding = subscription.outstanding.get(key);
|
|
2352
2798
|
if (!outstanding) return false;
|
|
2353
2799
|
subscription.outstanding.delete(key);
|
|
2354
|
-
clearTimeout(outstanding.timer);
|
|
2355
|
-
|
|
2800
|
+
if (outstanding.timer) clearTimeout(outstanding.timer);
|
|
2801
|
+
outstanding.timer = undefined;
|
|
2802
|
+
const slot = subscription.lease.slot;
|
|
2803
|
+
const refresh = this.matchingFrameRefresh(
|
|
2804
|
+
slot,
|
|
2805
|
+
subscription,
|
|
2806
|
+
outstanding.session,
|
|
2807
|
+
outstanding.incarnationGeneration,
|
|
2808
|
+
);
|
|
2809
|
+
if (refresh?.admissionClosed) {
|
|
2810
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2811
|
+
return false;
|
|
2812
|
+
}
|
|
2813
|
+
if (refresh && failLease) this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2814
|
+
const refreshOwned = Boolean(refresh && !refresh.abandoned);
|
|
2815
|
+
const outcome = await this.beginFrameAcknowledgement(
|
|
2356
2816
|
outstanding.session,
|
|
2357
2817
|
outstanding.acknowledgement,
|
|
2818
|
+
refreshOwned ? refresh : undefined,
|
|
2358
2819
|
);
|
|
2359
2820
|
if (outcome.status === 'fulfilled' && !failLease) return outcome.accepted;
|
|
2821
|
+
if (refreshOwned) return outcome.status === 'fulfilled' ? outcome.accepted : false;
|
|
2822
|
+
if (refresh && slot.frameRefresh === refresh) {
|
|
2823
|
+
if (outcome.status !== 'fulfilled') {
|
|
2824
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2825
|
+
}
|
|
2826
|
+
return outcome.status === 'fulfilled' ? outcome.accepted : false;
|
|
2827
|
+
}
|
|
2360
2828
|
await this.options.beforeFrameFailureTermination?.();
|
|
2361
2829
|
await this.handleFrameFailure(
|
|
2362
|
-
|
|
2830
|
+
slot,
|
|
2363
2831
|
outstanding.session,
|
|
2364
2832
|
outstanding.incarnationGeneration,
|
|
2365
2833
|
subscription.lease,
|
|
@@ -2434,8 +2902,25 @@ export class BrowserRuntime {
|
|
|
2434
2902
|
): Promise<void> {
|
|
2435
2903
|
const failedLease = slot.session === session ? (slot.lease ?? null) : null;
|
|
2436
2904
|
const incarnationGeneration = slot.incarnationGeneration;
|
|
2437
|
-
const
|
|
2905
|
+
const subscription = slot.frameSubscription;
|
|
2906
|
+
const refresh = subscription
|
|
2907
|
+
? this.matchingFrameRefresh(slot, subscription, session, incarnationGeneration)
|
|
2908
|
+
: undefined;
|
|
2909
|
+
if (refresh && failLease) this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2910
|
+
const refreshOwned = Boolean(refresh && !refresh.admissionClosed && !refresh.abandoned);
|
|
2911
|
+
const outcome = await this.beginFrameAcknowledgement(
|
|
2912
|
+
session,
|
|
2913
|
+
acknowledgement,
|
|
2914
|
+
refreshOwned ? refresh : undefined,
|
|
2915
|
+
);
|
|
2438
2916
|
if (outcome.status === 'fulfilled' && !failLease) return;
|
|
2917
|
+
if (refreshOwned) return;
|
|
2918
|
+
if (refresh && slot.frameRefresh === refresh) {
|
|
2919
|
+
if (outcome.status !== 'fulfilled') {
|
|
2920
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2921
|
+
}
|
|
2922
|
+
return;
|
|
2923
|
+
}
|
|
2439
2924
|
await this.options.beforeFrameFailureTermination?.();
|
|
2440
2925
|
await this.handleFrameFailure(slot, session, incarnationGeneration, failedLease);
|
|
2441
2926
|
}
|
|
@@ -2445,7 +2930,8 @@ export class BrowserRuntime {
|
|
|
2445
2930
|
session: ILiveBrowserSessionLike,
|
|
2446
2931
|
incarnationGeneration: number,
|
|
2447
2932
|
expectedLease: ILeaseRecord | null,
|
|
2448
|
-
|
|
2933
|
+
expectedRefresh?: IFrameRefreshRecord,
|
|
2934
|
+
): Promise<boolean> {
|
|
2449
2935
|
let transitionGeneration = 0;
|
|
2450
2936
|
let ownsTransitionFence = false;
|
|
2451
2937
|
const lease = expectedLease ?? undefined;
|
|
@@ -2478,8 +2964,14 @@ export class BrowserRuntime {
|
|
|
2478
2964
|
|| slot.attachmentRetryBinding
|
|
2479
2965
|
|| !exactLeaseIsCurrent
|
|
2480
2966
|
|| !leaseAttachmentIsCurrent
|
|
2967
|
+
|| (expectedRefresh !== undefined && slot.frameRefresh !== expectedRefresh)
|
|
2481
2968
|
|| (slot.fencingGeneration !== undefined && !borrowsLeaseReleaseFence)
|
|
2482
|
-
) return;
|
|
2969
|
+
) return false;
|
|
2970
|
+
if (expectedRefresh) {
|
|
2971
|
+
expectedRefresh.admissionClosed = true;
|
|
2972
|
+
expectedRefresh.abandoned = true;
|
|
2973
|
+
slot.frameRefresh = undefined;
|
|
2974
|
+
}
|
|
2483
2975
|
if (lease) this.invalidateCapabilityRecord(lease.capability);
|
|
2484
2976
|
if (borrowsLeaseReleaseFence) {
|
|
2485
2977
|
transitionGeneration = slot.fencingGeneration!;
|
|
@@ -2526,6 +3018,7 @@ export class BrowserRuntime {
|
|
|
2526
3018
|
if (errors.length > 1) {
|
|
2527
3019
|
throw new AggregateError(errors, 'Frame acknowledgement cleanup is incomplete');
|
|
2528
3020
|
}
|
|
3021
|
+
return true;
|
|
2529
3022
|
}
|
|
2530
3023
|
|
|
2531
3024
|
private trackFrameCapabilityRevocation(lease: ILeaseRecord): void {
|
|
@@ -2696,6 +3189,9 @@ export class BrowserRuntime {
|
|
|
2696
3189
|
}
|
|
2697
3190
|
await this.closeFrameSubscriptionIfLease(lease);
|
|
2698
3191
|
await this.quiesceSlot(slot);
|
|
3192
|
+
if (slot.frameRefresh?.lease === lease) {
|
|
3193
|
+
this.abandonFrameRefresh(slot, slot.frameRefresh);
|
|
3194
|
+
}
|
|
2699
3195
|
if (
|
|
2700
3196
|
slot.permanentlyFenced
|
|
2701
3197
|
&& (slot.session || slot.proxy || slot.profileDirectory)
|
|
@@ -2780,6 +3276,9 @@ export class BrowserRuntime {
|
|
|
2780
3276
|
}
|
|
2781
3277
|
const session = slot.session;
|
|
2782
3278
|
if (expectedSession && session !== expectedSession) return;
|
|
3279
|
+
if (slot.frameRefresh && (!session || slot.frameRefresh.session === session)) {
|
|
3280
|
+
this.abandonFrameRefresh(slot, slot.frameRefresh);
|
|
3281
|
+
}
|
|
2783
3282
|
if (session) {
|
|
2784
3283
|
let termination: plugins.smartpuppeteer.ILiveBrowserTerminationResult;
|
|
2785
3284
|
try {
|
|
@@ -3578,6 +4077,12 @@ export class BrowserRuntimeLease {
|
|
|
3578
4077
|
return this.runtime.acknowledgeLeaseFrame(this.record, acknowledgement);
|
|
3579
4078
|
}
|
|
3580
4079
|
|
|
4080
|
+
public refreshFrameStream(
|
|
4081
|
+
operationOptions?: IBrowserRuntimeOperationOptions,
|
|
4082
|
+
): Promise<void> {
|
|
4083
|
+
return this.runtime.leaseRefreshFrameStream(this.record, operationOptions);
|
|
4084
|
+
}
|
|
4085
|
+
|
|
3581
4086
|
public readArtifact(artifactId: string): Promise<Uint8Array> {
|
|
3582
4087
|
return this.runtime.readLeaseArtifact(this.record, artifactId);
|
|
3583
4088
|
}
|