@modelprofile.com/browser-runtime 3.0.0 → 3.1.0
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 +433 -32
- 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 +536 -33
- 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;
|
|
@@ -1153,6 +1172,39 @@ export class BrowserRuntime {
|
|
|
1153
1172
|
);
|
|
1154
1173
|
}
|
|
1155
1174
|
|
|
1175
|
+
/** @internal */
|
|
1176
|
+
public async leaseRefreshFrameStream(
|
|
1177
|
+
lease: ILeaseRecord,
|
|
1178
|
+
operationOptions: IBrowserRuntimeOperationOptions = {},
|
|
1179
|
+
): Promise<void> {
|
|
1180
|
+
this.requireHuman(lease);
|
|
1181
|
+
this.requireValidLease(lease);
|
|
1182
|
+
const slot = lease.slot;
|
|
1183
|
+
const subscription = slot.frameSubscription;
|
|
1184
|
+
if (!subscription || subscription.closed || subscription.lease !== lease) {
|
|
1185
|
+
throw new BrowserRuntimeError('BUSY');
|
|
1186
|
+
}
|
|
1187
|
+
let refresh: IFrameRefreshRecord | undefined;
|
|
1188
|
+
return this.runOperation(
|
|
1189
|
+
lease,
|
|
1190
|
+
'refreshFrameStream',
|
|
1191
|
+
'frame-stream',
|
|
1192
|
+
operationOptions,
|
|
1193
|
+
async (signal, session) => {
|
|
1194
|
+
refresh = this.beginFrameRefresh(
|
|
1195
|
+
slot,
|
|
1196
|
+
lease,
|
|
1197
|
+
subscription,
|
|
1198
|
+
session,
|
|
1199
|
+
);
|
|
1200
|
+
await this.executeFrameRefresh(refresh, signal);
|
|
1201
|
+
},
|
|
1202
|
+
async () => {
|
|
1203
|
+
if (refresh) await this.finalizeFrameRefresh(slot, refresh);
|
|
1204
|
+
},
|
|
1205
|
+
);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1156
1208
|
/** @internal */
|
|
1157
1209
|
public async leaseDispatchRawInput(
|
|
1158
1210
|
lease: ILeaseRecord,
|
|
@@ -1186,11 +1238,12 @@ export class BrowserRuntime {
|
|
|
1186
1238
|
this.requireHuman(lease);
|
|
1187
1239
|
if (typeof listener !== 'function') throw new BrowserRuntimeError('INVALID_INPUT');
|
|
1188
1240
|
const slot = lease.slot;
|
|
1189
|
-
const release =
|
|
1241
|
+
const release = slot.mutex.tryAcquire();
|
|
1242
|
+
if (!release) throw new BrowserRuntimeError('BUSY');
|
|
1190
1243
|
let subscription: IFrameSubscriptionRecord;
|
|
1191
1244
|
try {
|
|
1192
1245
|
this.requireValidLease(lease);
|
|
1193
|
-
if (slot.frameSubscription) throw new BrowserRuntimeError('BUSY');
|
|
1246
|
+
if (slot.frameSubscription || slot.frameRefresh) throw new BrowserRuntimeError('BUSY');
|
|
1194
1247
|
subscription = {
|
|
1195
1248
|
lease,
|
|
1196
1249
|
listener,
|
|
@@ -1225,14 +1278,34 @@ export class BrowserRuntime {
|
|
|
1225
1278
|
if (!subscription || subscription.lease !== lease) return false;
|
|
1226
1279
|
const outstanding = subscription.outstanding.get(key);
|
|
1227
1280
|
if (!outstanding) return false;
|
|
1228
|
-
subscription.outstanding.delete(key);
|
|
1229
|
-
clearTimeout(outstanding.timer);
|
|
1230
1281
|
const { session, incarnationGeneration } = outstanding;
|
|
1231
|
-
const
|
|
1282
|
+
const refresh = this.matchingFrameRefresh(
|
|
1283
|
+
slot,
|
|
1284
|
+
subscription,
|
|
1285
|
+
session,
|
|
1286
|
+
incarnationGeneration,
|
|
1287
|
+
);
|
|
1288
|
+
if (refresh?.admissionClosed) {
|
|
1289
|
+
subscription.outstanding.delete(key);
|
|
1290
|
+
if (outstanding.timer) clearTimeout(outstanding.timer);
|
|
1291
|
+
outstanding.timer = undefined;
|
|
1292
|
+
return false;
|
|
1293
|
+
}
|
|
1294
|
+
subscription.outstanding.delete(key);
|
|
1295
|
+
if (outstanding.timer) clearTimeout(outstanding.timer);
|
|
1296
|
+
outstanding.timer = undefined;
|
|
1297
|
+
const refreshOwned = Boolean(refresh && !refresh.abandoned);
|
|
1298
|
+
const outcome = await this.beginFrameAcknowledgement(
|
|
1232
1299
|
session,
|
|
1233
1300
|
outstanding.acknowledgement,
|
|
1301
|
+
refreshOwned ? refresh : undefined,
|
|
1234
1302
|
);
|
|
1235
1303
|
if (outcome.status === 'fulfilled') return outcome.accepted;
|
|
1304
|
+
if (refreshOwned) return false;
|
|
1305
|
+
if (refresh && slot.frameRefresh === refresh) {
|
|
1306
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
1307
|
+
return false;
|
|
1308
|
+
}
|
|
1236
1309
|
await this.options.beforeFrameFailureTermination?.();
|
|
1237
1310
|
await this.handleFrameFailure(slot, session, incarnationGeneration, subscription.lease);
|
|
1238
1311
|
return false;
|
|
@@ -1640,6 +1713,7 @@ export class BrowserRuntime {
|
|
|
1640
1713
|
classification: TBrowserRuntimeOperationClassification,
|
|
1641
1714
|
operationOptions: IBrowserRuntimeOperationOptions,
|
|
1642
1715
|
execute: (signal: AbortSignal, session: ILiveBrowserSessionLike) => Promise<T>,
|
|
1716
|
+
finalize?: () => Promise<void>,
|
|
1643
1717
|
): Promise<T> {
|
|
1644
1718
|
if (!operationOptions || typeof operationOptions !== 'object') {
|
|
1645
1719
|
throw new BrowserRuntimeError('INVALID_INPUT');
|
|
@@ -1678,6 +1752,7 @@ export class BrowserRuntime {
|
|
|
1678
1752
|
onOperationStarted: operationOptions.onOperationStarted,
|
|
1679
1753
|
signal,
|
|
1680
1754
|
execute: execute as IQueuedOperationRecord['execute'],
|
|
1755
|
+
finalize,
|
|
1681
1756
|
resolve: (value) => resolve(value as T),
|
|
1682
1757
|
reject,
|
|
1683
1758
|
state: 'queued',
|
|
@@ -1813,6 +1888,7 @@ export class BrowserRuntime {
|
|
|
1813
1888
|
let failed = false;
|
|
1814
1889
|
let failure: unknown;
|
|
1815
1890
|
let executionStarted = false;
|
|
1891
|
+
let executionSettled = false;
|
|
1816
1892
|
try {
|
|
1817
1893
|
await this.runBeforeOperation(operationIdentity, combinedSignal);
|
|
1818
1894
|
const executionRelease = await slot.mutex.acquire();
|
|
@@ -1829,7 +1905,9 @@ export class BrowserRuntime {
|
|
|
1829
1905
|
) throw new BrowserRuntimeError('ABORTED');
|
|
1830
1906
|
executionStarted = true;
|
|
1831
1907
|
executionPromise = queued.execute(combinedSignal, session);
|
|
1832
|
-
operation.promise = executionPromise.then(() => undefined, () => undefined)
|
|
1908
|
+
operation.promise = executionPromise.then(() => undefined, () => undefined).finally(() => {
|
|
1909
|
+
executionSettled = true;
|
|
1910
|
+
});
|
|
1833
1911
|
} finally {
|
|
1834
1912
|
executionRelease();
|
|
1835
1913
|
}
|
|
@@ -1875,6 +1953,18 @@ export class BrowserRuntime {
|
|
|
1875
1953
|
} else {
|
|
1876
1954
|
await cleanupSlot();
|
|
1877
1955
|
}
|
|
1956
|
+
if (
|
|
1957
|
+
queued.finalize
|
|
1958
|
+
&& (!executionStarted || executionSettled)
|
|
1959
|
+
&& slot.operation !== operation
|
|
1960
|
+
) {
|
|
1961
|
+
try {
|
|
1962
|
+
await queued.finalize();
|
|
1963
|
+
} catch (error) {
|
|
1964
|
+
failed = true;
|
|
1965
|
+
failure = error;
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1878
1968
|
const errorCode = failed ? this.operationErrorCode(failure) : undefined;
|
|
1879
1969
|
await this.emitAudit(Object.freeze({
|
|
1880
1970
|
...operationIdentity,
|
|
@@ -2193,6 +2283,271 @@ export class BrowserRuntime {
|
|
|
2193
2283
|
};
|
|
2194
2284
|
}
|
|
2195
2285
|
|
|
2286
|
+
private beginFrameRefresh(
|
|
2287
|
+
slot: IResourceSlot,
|
|
2288
|
+
lease: ILeaseRecord,
|
|
2289
|
+
subscription: IFrameSubscriptionRecord,
|
|
2290
|
+
session: ILiveBrowserSessionLike,
|
|
2291
|
+
): IFrameRefreshRecord {
|
|
2292
|
+
if (
|
|
2293
|
+
slot.frameRefresh
|
|
2294
|
+
|| slot.frameSubscription !== subscription
|
|
2295
|
+
|| subscription.closed
|
|
2296
|
+
|| subscription.lease !== lease
|
|
2297
|
+
|| slot.lease !== lease
|
|
2298
|
+
|| slot.session !== session
|
|
2299
|
+
) throw new BrowserRuntimeError('BUSY');
|
|
2300
|
+
const state = session.getState();
|
|
2301
|
+
const activeTab = state.tabs.find((tab) => tab.id === state.activeTabId);
|
|
2302
|
+
if (
|
|
2303
|
+
!activeTab
|
|
2304
|
+
|| !activeTab.active
|
|
2305
|
+
|| activeTab.status !== 'open'
|
|
2306
|
+
|| !activeTab.streaming
|
|
2307
|
+
) throw new BrowserRuntimeError('BUSY');
|
|
2308
|
+
const refresh: IFrameRefreshRecord = {
|
|
2309
|
+
lease,
|
|
2310
|
+
subscription,
|
|
2311
|
+
session,
|
|
2312
|
+
incarnationGeneration: slot.incarnationGeneration,
|
|
2313
|
+
initialTabId: activeTab.id,
|
|
2314
|
+
initialGeneration: activeTab.generation,
|
|
2315
|
+
initialViewportRevision: state.viewportRevision,
|
|
2316
|
+
startingHighestSequence: subscription.highestSequence,
|
|
2317
|
+
pendingAcknowledgements: new Set(),
|
|
2318
|
+
refreshStarted: false,
|
|
2319
|
+
admissionClosed: false,
|
|
2320
|
+
abandoned: false,
|
|
2321
|
+
};
|
|
2322
|
+
slot.frameRefresh = refresh;
|
|
2323
|
+
return refresh;
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
private async executeFrameRefresh(
|
|
2327
|
+
refresh: IFrameRefreshRecord,
|
|
2328
|
+
signal: AbortSignal,
|
|
2329
|
+
): Promise<void> {
|
|
2330
|
+
let operationError: unknown;
|
|
2331
|
+
try {
|
|
2332
|
+
await this.drainFrameRefreshOutstanding(refresh);
|
|
2333
|
+
if (refresh.failure) throw refresh.failure;
|
|
2334
|
+
signal.throwIfAborted();
|
|
2335
|
+
refresh.refreshStarted = true;
|
|
2336
|
+
const identity = this.validateFrameAcknowledgement(
|
|
2337
|
+
await refresh.session.refreshScreencast({ signal }),
|
|
2338
|
+
true,
|
|
2339
|
+
);
|
|
2340
|
+
this.validateFrameRefreshBoundary(refresh, identity);
|
|
2341
|
+
await this.reconcileFrameRefreshOutstanding(refresh, identity);
|
|
2342
|
+
if (refresh.failure) throw refresh.failure;
|
|
2343
|
+
} catch (error) {
|
|
2344
|
+
operationError = error;
|
|
2345
|
+
if (signal.aborted && refresh.refreshStarted) {
|
|
2346
|
+
try {
|
|
2347
|
+
const candidate = refresh.boundaryCandidate;
|
|
2348
|
+
if (!candidate) throw new BrowserRuntimeError('FRAME_STREAM_FAILED');
|
|
2349
|
+
this.validateFrameRefreshBoundary(refresh, candidate);
|
|
2350
|
+
await this.reconcileFrameRefreshOutstanding(refresh, candidate);
|
|
2351
|
+
} catch {
|
|
2352
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2353
|
+
}
|
|
2354
|
+
} else if (!signal.aborted && !refresh.failure) {
|
|
2355
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2356
|
+
}
|
|
2357
|
+
} finally {
|
|
2358
|
+
refresh.admissionClosed = true;
|
|
2359
|
+
await Promise.allSettled([...refresh.pendingAcknowledgements]);
|
|
2360
|
+
}
|
|
2361
|
+
if (refresh.failure) throw refresh.failure;
|
|
2362
|
+
if (operationError !== undefined) throw operationError;
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
private async drainFrameRefreshOutstanding(refresh: IFrameRefreshRecord): Promise<void> {
|
|
2366
|
+
const outstanding = [...refresh.subscription.outstanding.values()];
|
|
2367
|
+
refresh.subscription.outstanding.clear();
|
|
2368
|
+
for (const frame of outstanding) {
|
|
2369
|
+
if (frame.timer) clearTimeout(frame.timer);
|
|
2370
|
+
frame.timer = undefined;
|
|
2371
|
+
}
|
|
2372
|
+
await Promise.all(outstanding.map((frame) => this.beginFrameAcknowledgement(
|
|
2373
|
+
frame.session,
|
|
2374
|
+
frame.acknowledgement,
|
|
2375
|
+
refresh,
|
|
2376
|
+
)));
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
private validateFrameRefreshBoundary(
|
|
2380
|
+
refresh: IFrameRefreshRecord,
|
|
2381
|
+
identity: plugins.smartpuppeteer.ILiveBrowserFrameIdentity,
|
|
2382
|
+
): void {
|
|
2383
|
+
const candidate = refresh.boundaryCandidate;
|
|
2384
|
+
const state = refresh.session.getState();
|
|
2385
|
+
const activeTab = state.tabs.find((tab) => tab.id === state.activeTabId);
|
|
2386
|
+
if (
|
|
2387
|
+
!candidate
|
|
2388
|
+
|| this.frameAcknowledgementKey(candidate) !== this.frameAcknowledgementKey(identity)
|
|
2389
|
+
|| identity.tabId !== refresh.initialTabId
|
|
2390
|
+
|| identity.sequence <= refresh.startingHighestSequence
|
|
2391
|
+
|| identity.generation <= refresh.initialGeneration
|
|
2392
|
+
|| identity.viewportRevision !== refresh.initialViewportRevision
|
|
2393
|
+
|| state.activeTabId !== refresh.initialTabId
|
|
2394
|
+
|| state.viewportRevision !== refresh.initialViewportRevision
|
|
2395
|
+
|| !activeTab
|
|
2396
|
+
|| !activeTab.active
|
|
2397
|
+
|| activeTab.status !== 'open'
|
|
2398
|
+
|| !activeTab.streaming
|
|
2399
|
+
|| activeTab.generation !== identity.generation
|
|
2400
|
+
|| activeTab.appliedViewportRevision !== identity.viewportRevision
|
|
2401
|
+
) throw new BrowserRuntimeError('FRAME_STREAM_FAILED');
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
private async reconcileFrameRefreshOutstanding(
|
|
2405
|
+
refresh: IFrameRefreshRecord,
|
|
2406
|
+
identity: plugins.smartpuppeteer.ILiveBrowserFrameIdentity,
|
|
2407
|
+
): Promise<void> {
|
|
2408
|
+
const retirements: Array<Promise<TFrameAcknowledgementOutcome>> = [];
|
|
2409
|
+
for (const [key, frame] of refresh.subscription.outstanding) {
|
|
2410
|
+
const acknowledgement = frame.acknowledgement;
|
|
2411
|
+
if (
|
|
2412
|
+
acknowledgement.tabId === identity.tabId
|
|
2413
|
+
&& acknowledgement.generation === identity.generation
|
|
2414
|
+
&& acknowledgement.viewportRevision === identity.viewportRevision
|
|
2415
|
+
&& acknowledgement.sequence >= identity.sequence
|
|
2416
|
+
) continue;
|
|
2417
|
+
refresh.subscription.outstanding.delete(key);
|
|
2418
|
+
if (frame.timer) clearTimeout(frame.timer);
|
|
2419
|
+
frame.timer = undefined;
|
|
2420
|
+
retirements.push(this.beginFrameAcknowledgement(
|
|
2421
|
+
frame.session,
|
|
2422
|
+
acknowledgement,
|
|
2423
|
+
refresh,
|
|
2424
|
+
));
|
|
2425
|
+
}
|
|
2426
|
+
await Promise.all(retirements);
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
private beginFrameAcknowledgement(
|
|
2430
|
+
session: ILiveBrowserSessionLike,
|
|
2431
|
+
acknowledgement: plugins.smartpuppeteer.ILiveBrowserFrameAcknowledgementRequest,
|
|
2432
|
+
refresh?: IFrameRefreshRecord,
|
|
2433
|
+
): Promise<TFrameAcknowledgementOutcome> {
|
|
2434
|
+
const operation = this.waitForFrameAcknowledgement(session, acknowledgement);
|
|
2435
|
+
if (!refresh || refresh.admissionClosed || refresh.abandoned) return operation;
|
|
2436
|
+
const tracked = operation.then((outcome) => {
|
|
2437
|
+
if (outcome.status !== 'fulfilled') this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2438
|
+
return outcome;
|
|
2439
|
+
});
|
|
2440
|
+
refresh.pendingAcknowledgements.add(tracked);
|
|
2441
|
+
void tracked.then(
|
|
2442
|
+
() => refresh.pendingAcknowledgements.delete(tracked),
|
|
2443
|
+
() => refresh.pendingAcknowledgements.delete(tracked),
|
|
2444
|
+
);
|
|
2445
|
+
return tracked;
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
private failFrameRefresh(
|
|
2449
|
+
refresh: IFrameRefreshRecord,
|
|
2450
|
+
code: 'FRAME_STREAM_FAILED' | 'FRAME_TOO_LARGE',
|
|
2451
|
+
): void {
|
|
2452
|
+
refresh.failure ??= new BrowserRuntimeError(code);
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
private async finalizeFrameRefresh(
|
|
2456
|
+
slot: IResourceSlot,
|
|
2457
|
+
refresh: IFrameRefreshRecord,
|
|
2458
|
+
): Promise<void> {
|
|
2459
|
+
if (refresh.failure) {
|
|
2460
|
+
await this.options.beforeFrameFailureTermination?.();
|
|
2461
|
+
const claimed = await this.handleFrameFailure(
|
|
2462
|
+
slot,
|
|
2463
|
+
refresh.session,
|
|
2464
|
+
refresh.incarnationGeneration,
|
|
2465
|
+
refresh.lease,
|
|
2466
|
+
refresh,
|
|
2467
|
+
);
|
|
2468
|
+
if (!claimed) {
|
|
2469
|
+
this.abandonFrameRefresh(slot, refresh);
|
|
2470
|
+
throw new BrowserRuntimeError('ABORTED');
|
|
2471
|
+
}
|
|
2472
|
+
throw refresh.failure;
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
let current = false;
|
|
2476
|
+
const release = await slot.mutex.acquire();
|
|
2477
|
+
try {
|
|
2478
|
+
try {
|
|
2479
|
+
this.requireValidLease(refresh.lease);
|
|
2480
|
+
current = slot.frameRefresh === refresh
|
|
2481
|
+
&& !refresh.abandoned
|
|
2482
|
+
&& slot.frameSubscription === refresh.subscription
|
|
2483
|
+
&& !refresh.subscription.closed
|
|
2484
|
+
&& slot.session === refresh.session
|
|
2485
|
+
&& slot.incarnationGeneration === refresh.incarnationGeneration;
|
|
2486
|
+
} catch {
|
|
2487
|
+
current = false;
|
|
2488
|
+
}
|
|
2489
|
+
if (current) {
|
|
2490
|
+
slot.frameRefresh = undefined;
|
|
2491
|
+
for (const [key, frame] of refresh.subscription.outstanding) {
|
|
2492
|
+
if (!frame.timer) this.armFrameAcknowledgementTimer(refresh.subscription, key, frame);
|
|
2493
|
+
}
|
|
2494
|
+
} else {
|
|
2495
|
+
this.abandonFrameRefresh(slot, refresh);
|
|
2496
|
+
}
|
|
2497
|
+
} finally {
|
|
2498
|
+
release();
|
|
2499
|
+
}
|
|
2500
|
+
if (!current) throw new BrowserRuntimeError('ABORTED');
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
private abandonFrameRefresh(slot: IResourceSlot, expected?: IFrameRefreshRecord): void {
|
|
2504
|
+
const refresh = slot.frameRefresh;
|
|
2505
|
+
if (!refresh || (expected && refresh !== expected)) return;
|
|
2506
|
+
refresh.admissionClosed = true;
|
|
2507
|
+
refresh.abandoned = true;
|
|
2508
|
+
slot.frameRefresh = undefined;
|
|
2509
|
+
if (
|
|
2510
|
+
slot.frameSubscription === refresh.subscription
|
|
2511
|
+
&& !refresh.subscription.closed
|
|
2512
|
+
&& slot.session === refresh.session
|
|
2513
|
+
&& slot.incarnationGeneration === refresh.incarnationGeneration
|
|
2514
|
+
) {
|
|
2515
|
+
for (const [key, frame] of refresh.subscription.outstanding) {
|
|
2516
|
+
if (!frame.timer) this.armFrameAcknowledgementTimer(refresh.subscription, key, frame);
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
private armFrameAcknowledgementTimer(
|
|
2522
|
+
subscription: IFrameSubscriptionRecord,
|
|
2523
|
+
key: string,
|
|
2524
|
+
frame: IOutstandingFrameRecord,
|
|
2525
|
+
): void {
|
|
2526
|
+
if (frame.timer || subscription.closed || !subscription.outstanding.has(key)) return;
|
|
2527
|
+
const timer = setTimeout(() => {
|
|
2528
|
+
this.trackCleanup(this.retireOutstandingFrame(subscription, key, true).then(() => undefined));
|
|
2529
|
+
}, this.options.frameAcknowledgementTimeoutMs);
|
|
2530
|
+
timer.unref();
|
|
2531
|
+
frame.timer = timer;
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
private matchingFrameRefresh(
|
|
2535
|
+
slot: IResourceSlot,
|
|
2536
|
+
subscription: IFrameSubscriptionRecord,
|
|
2537
|
+
session: ILiveBrowserSessionLike,
|
|
2538
|
+
incarnationGeneration: number,
|
|
2539
|
+
): IFrameRefreshRecord | undefined {
|
|
2540
|
+
const refresh = slot.frameRefresh;
|
|
2541
|
+
return refresh
|
|
2542
|
+
&& !refresh.abandoned
|
|
2543
|
+
&& refresh.subscription === subscription
|
|
2544
|
+
&& refresh.lease === subscription.lease
|
|
2545
|
+
&& refresh.session === session
|
|
2546
|
+
&& refresh.incarnationGeneration === incarnationGeneration
|
|
2547
|
+
? refresh
|
|
2548
|
+
: undefined;
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2196
2551
|
private handleSessionEvent(
|
|
2197
2552
|
slot: IResourceSlot,
|
|
2198
2553
|
session: ILiveBrowserSessionLike,
|
|
@@ -2210,6 +2565,12 @@ export class BrowserRuntime {
|
|
|
2210
2565
|
return;
|
|
2211
2566
|
}
|
|
2212
2567
|
const subscription = slot.frameSubscription;
|
|
2568
|
+
const refreshForSession = slot.frameRefresh
|
|
2569
|
+
&& !slot.frameRefresh.abandoned
|
|
2570
|
+
&& slot.frameRefresh.session === session
|
|
2571
|
+
&& slot.frameRefresh.incarnationGeneration === slot.incarnationGeneration
|
|
2572
|
+
? slot.frameRefresh
|
|
2573
|
+
: undefined;
|
|
2213
2574
|
if (event.type === 'frame') {
|
|
2214
2575
|
let acknowledgement: plugins.smartpuppeteer.ILiveBrowserFrameAcknowledgementRequest;
|
|
2215
2576
|
try {
|
|
@@ -2218,38 +2579,72 @@ export class BrowserRuntime {
|
|
|
2218
2579
|
}
|
|
2219
2580
|
acknowledgement = this.validateFrameAcknowledgement(event.frame);
|
|
2220
2581
|
} catch {
|
|
2582
|
+
if (refreshForSession) {
|
|
2583
|
+
this.failFrameRefresh(refreshForSession, 'FRAME_STREAM_FAILED');
|
|
2584
|
+
return;
|
|
2585
|
+
}
|
|
2221
2586
|
const failedLease = slot.session === session ? (slot.lease ?? null) : null;
|
|
2222
2587
|
this.trackCleanup(this.handleFrameFailure(
|
|
2223
2588
|
slot,
|
|
2224
2589
|
session,
|
|
2225
2590
|
slot.incarnationGeneration,
|
|
2226
2591
|
failedLease,
|
|
2227
|
-
));
|
|
2592
|
+
).then(() => undefined));
|
|
2228
2593
|
return;
|
|
2229
2594
|
}
|
|
2230
2595
|
if (!subscription || subscription.closed) {
|
|
2596
|
+
if (refreshForSession) {
|
|
2597
|
+
this.failFrameRefresh(refreshForSession, 'FRAME_STREAM_FAILED');
|
|
2598
|
+
return;
|
|
2599
|
+
}
|
|
2231
2600
|
this.trackCleanup(this.acknowledgeFrameInBackground(slot, session, acknowledgement));
|
|
2232
2601
|
return;
|
|
2233
2602
|
}
|
|
2603
|
+
const refresh = this.matchingFrameRefresh(
|
|
2604
|
+
slot,
|
|
2605
|
+
subscription,
|
|
2606
|
+
session,
|
|
2607
|
+
slot.incarnationGeneration,
|
|
2608
|
+
);
|
|
2609
|
+
if (refreshForSession && refreshForSession !== refresh) {
|
|
2610
|
+
this.failFrameRefresh(refreshForSession, 'FRAME_STREAM_FAILED');
|
|
2611
|
+
return;
|
|
2612
|
+
}
|
|
2234
2613
|
try {
|
|
2235
2614
|
this.requireValidLease(subscription.lease);
|
|
2236
2615
|
} catch {
|
|
2616
|
+
if (refresh) {
|
|
2617
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2618
|
+
return;
|
|
2619
|
+
}
|
|
2237
2620
|
this.trackCleanup(this.acknowledgeFrameInBackground(slot, session, acknowledgement));
|
|
2238
2621
|
this.trackCleanup(this.revokeCapabilityRecord(subscription.lease.capability));
|
|
2239
2622
|
return;
|
|
2240
2623
|
}
|
|
2241
2624
|
const key = this.frameAcknowledgementKey(acknowledgement);
|
|
2242
2625
|
if (acknowledgement.sequence <= subscription.highestSequence) {
|
|
2626
|
+
if (refresh) {
|
|
2627
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2628
|
+
return;
|
|
2629
|
+
}
|
|
2243
2630
|
this.trackCleanup(this.handleFrameFailure(
|
|
2244
2631
|
slot,
|
|
2245
2632
|
session,
|
|
2246
2633
|
slot.incarnationGeneration,
|
|
2247
2634
|
subscription.lease,
|
|
2248
|
-
));
|
|
2635
|
+
).then(() => undefined));
|
|
2249
2636
|
return;
|
|
2250
2637
|
}
|
|
2251
2638
|
subscription.highestSequence = acknowledgement.sequence;
|
|
2252
2639
|
if (event.frame.data.byteLength > this.options.maxFrameBytes) {
|
|
2640
|
+
if (refresh) {
|
|
2641
|
+
this.failFrameRefresh(refresh, 'FRAME_TOO_LARGE');
|
|
2642
|
+
this.pushToSubscription(subscription, {
|
|
2643
|
+
type: 'error',
|
|
2644
|
+
error: { code: 'FRAME_TOO_LARGE', fatal: true, tabId: event.frame.tabId },
|
|
2645
|
+
});
|
|
2646
|
+
return;
|
|
2647
|
+
}
|
|
2253
2648
|
this.trackCleanup(this.acknowledgeFrameInBackground(
|
|
2254
2649
|
slot,
|
|
2255
2650
|
session,
|
|
@@ -2262,28 +2657,64 @@ export class BrowserRuntime {
|
|
|
2262
2657
|
});
|
|
2263
2658
|
return;
|
|
2264
2659
|
}
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
);
|
|
2660
|
+
if (refresh?.admissionClosed) return;
|
|
2661
|
+
const refreshAdmission = refresh && !refresh.admissionClosed && !refresh.abandoned
|
|
2662
|
+
? refresh
|
|
2663
|
+
: undefined;
|
|
2664
|
+
if (refreshAdmission) {
|
|
2665
|
+
if (refreshAdmission.failure) return;
|
|
2666
|
+
if (
|
|
2667
|
+
subscription.outstanding.size + refreshAdmission.pendingAcknowledgements.size
|
|
2668
|
+
>= this.options.maxOutstandingFrames
|
|
2669
|
+
) {
|
|
2670
|
+
this.failFrameRefresh(refreshAdmission, 'FRAME_STREAM_FAILED');
|
|
2671
|
+
return;
|
|
2672
|
+
}
|
|
2673
|
+
} else {
|
|
2674
|
+
if (refresh && subscription.outstanding.size >= this.options.maxOutstandingFrames) {
|
|
2675
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2676
|
+
}
|
|
2677
|
+
while (subscription.outstanding.size >= this.options.maxOutstandingFrames) {
|
|
2678
|
+
const oldestKey = subscription.outstanding.keys().next().value as string | undefined;
|
|
2679
|
+
if (!oldestKey) break;
|
|
2680
|
+
this.trackCleanup(
|
|
2681
|
+
this.retireOutstandingFrame(subscription, oldestKey, false).then(() => undefined),
|
|
2682
|
+
);
|
|
2683
|
+
}
|
|
2271
2684
|
}
|
|
2272
|
-
const
|
|
2273
|
-
this.trackCleanup(this.retireOutstandingFrame(subscription, key, true).then(() => undefined));
|
|
2274
|
-
}, this.options.frameAcknowledgementTimeoutMs);
|
|
2275
|
-
timer.unref();
|
|
2276
|
-
subscription.outstanding.set(key, {
|
|
2685
|
+
const outstanding: IOutstandingFrameRecord = {
|
|
2277
2686
|
acknowledgement,
|
|
2278
|
-
timer,
|
|
2279
2687
|
session,
|
|
2280
2688
|
incarnationGeneration: slot.incarnationGeneration,
|
|
2281
|
-
}
|
|
2282
|
-
|
|
2689
|
+
};
|
|
2690
|
+
subscription.outstanding.set(key, outstanding);
|
|
2691
|
+
if (!refreshAdmission) this.armFrameAcknowledgementTimer(subscription, key, outstanding);
|
|
2692
|
+
const delivered = this.pushToSubscription(subscription, { type: 'frame', frame: event.frame });
|
|
2693
|
+
if (
|
|
2694
|
+
delivered
|
|
2695
|
+
&& refreshAdmission
|
|
2696
|
+
&& refreshAdmission.refreshStarted
|
|
2697
|
+
&& !refreshAdmission.boundaryCandidate
|
|
2698
|
+
) {
|
|
2699
|
+
if (
|
|
2700
|
+
acknowledgement.tabId === refreshAdmission.initialTabId
|
|
2701
|
+
&& acknowledgement.sequence > refreshAdmission.startingHighestSequence
|
|
2702
|
+
&& acknowledgement.generation > refreshAdmission.initialGeneration
|
|
2703
|
+
&& acknowledgement.viewportRevision === refreshAdmission.initialViewportRevision
|
|
2704
|
+
) {
|
|
2705
|
+
refreshAdmission.boundaryCandidate = { ...acknowledgement };
|
|
2706
|
+
} else {
|
|
2707
|
+
this.failFrameRefresh(refreshAdmission, 'FRAME_STREAM_FAILED');
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2283
2710
|
return;
|
|
2284
2711
|
}
|
|
2285
2712
|
if (event.type === 'error' && event.error.fatal && slot.lease) {
|
|
2286
|
-
|
|
2713
|
+
if (refreshForSession) {
|
|
2714
|
+
this.failFrameRefresh(refreshForSession, 'FRAME_STREAM_FAILED');
|
|
2715
|
+
} else {
|
|
2716
|
+
this.trackCleanup(this.revokeCapabilityRecord(slot.lease.capability));
|
|
2717
|
+
}
|
|
2287
2718
|
}
|
|
2288
2719
|
if (!subscription || subscription.closed) return;
|
|
2289
2720
|
if (event.type === 'state') {
|
|
@@ -2303,22 +2734,30 @@ export class BrowserRuntime {
|
|
|
2303
2734
|
private pushToSubscription(
|
|
2304
2735
|
subscription: IFrameSubscriptionRecord,
|
|
2305
2736
|
event: TBrowserRuntimeEvent,
|
|
2306
|
-
):
|
|
2737
|
+
): boolean {
|
|
2307
2738
|
try {
|
|
2308
2739
|
this.requireValidLease(subscription.lease);
|
|
2309
2740
|
subscription.listener(event);
|
|
2741
|
+
return true;
|
|
2310
2742
|
} catch {
|
|
2311
2743
|
const slot = subscription.lease.slot;
|
|
2312
2744
|
const session = slot.session;
|
|
2313
2745
|
const incarnationGeneration = slot.incarnationGeneration;
|
|
2746
|
+
const refresh = session
|
|
2747
|
+
? this.matchingFrameRefresh(slot, subscription, session, incarnationGeneration)
|
|
2748
|
+
: undefined;
|
|
2314
2749
|
if (event.type === 'frame') {
|
|
2315
2750
|
const key = this.frameAcknowledgementKey(this.validateFrameAcknowledgement(event.frame));
|
|
2316
2751
|
this.trackCleanup(this.retireOutstandingFrame(subscription, key, true).then(() => undefined));
|
|
2317
|
-
return;
|
|
2752
|
+
return false;
|
|
2753
|
+
}
|
|
2754
|
+
if (refresh) {
|
|
2755
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2756
|
+
return false;
|
|
2318
2757
|
}
|
|
2319
2758
|
if (!session) {
|
|
2320
2759
|
this.trackCleanup(this.revokeCapabilityRecord(subscription.lease.capability));
|
|
2321
|
-
return;
|
|
2760
|
+
return false;
|
|
2322
2761
|
}
|
|
2323
2762
|
this.trackCleanup((async () => {
|
|
2324
2763
|
await this.options.beforeFrameFailureTermination?.();
|
|
@@ -2329,6 +2768,7 @@ export class BrowserRuntime {
|
|
|
2329
2768
|
subscription.lease,
|
|
2330
2769
|
);
|
|
2331
2770
|
})());
|
|
2771
|
+
return false;
|
|
2332
2772
|
}
|
|
2333
2773
|
}
|
|
2334
2774
|
|
|
@@ -2336,6 +2776,10 @@ export class BrowserRuntime {
|
|
|
2336
2776
|
if (subscription.closed) return;
|
|
2337
2777
|
subscription.closed = true;
|
|
2338
2778
|
const slot = subscription.lease.slot;
|
|
2779
|
+
const refresh = slot.frameRefresh;
|
|
2780
|
+
if (refresh?.subscription === subscription && !refresh.abandoned) {
|
|
2781
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2782
|
+
}
|
|
2339
2783
|
if (slot.frameSubscription === subscription) slot.frameSubscription = undefined;
|
|
2340
2784
|
for (const key of [...subscription.outstanding.keys()]) {
|
|
2341
2785
|
await this.retireOutstandingFrame(subscription, key, false);
|
|
@@ -2350,15 +2794,37 @@ export class BrowserRuntime {
|
|
|
2350
2794
|
const outstanding = subscription.outstanding.get(key);
|
|
2351
2795
|
if (!outstanding) return false;
|
|
2352
2796
|
subscription.outstanding.delete(key);
|
|
2353
|
-
clearTimeout(outstanding.timer);
|
|
2354
|
-
|
|
2797
|
+
if (outstanding.timer) clearTimeout(outstanding.timer);
|
|
2798
|
+
outstanding.timer = undefined;
|
|
2799
|
+
const slot = subscription.lease.slot;
|
|
2800
|
+
const refresh = this.matchingFrameRefresh(
|
|
2801
|
+
slot,
|
|
2802
|
+
subscription,
|
|
2803
|
+
outstanding.session,
|
|
2804
|
+
outstanding.incarnationGeneration,
|
|
2805
|
+
);
|
|
2806
|
+
if (refresh?.admissionClosed) {
|
|
2807
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2808
|
+
return false;
|
|
2809
|
+
}
|
|
2810
|
+
if (refresh && failLease) this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2811
|
+
const refreshOwned = Boolean(refresh && !refresh.abandoned);
|
|
2812
|
+
const outcome = await this.beginFrameAcknowledgement(
|
|
2355
2813
|
outstanding.session,
|
|
2356
2814
|
outstanding.acknowledgement,
|
|
2815
|
+
refreshOwned ? refresh : undefined,
|
|
2357
2816
|
);
|
|
2358
2817
|
if (outcome.status === 'fulfilled' && !failLease) return outcome.accepted;
|
|
2818
|
+
if (refreshOwned) return outcome.status === 'fulfilled' ? outcome.accepted : false;
|
|
2819
|
+
if (refresh && slot.frameRefresh === refresh) {
|
|
2820
|
+
if (outcome.status !== 'fulfilled') {
|
|
2821
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2822
|
+
}
|
|
2823
|
+
return outcome.status === 'fulfilled' ? outcome.accepted : false;
|
|
2824
|
+
}
|
|
2359
2825
|
await this.options.beforeFrameFailureTermination?.();
|
|
2360
2826
|
await this.handleFrameFailure(
|
|
2361
|
-
|
|
2827
|
+
slot,
|
|
2362
2828
|
outstanding.session,
|
|
2363
2829
|
outstanding.incarnationGeneration,
|
|
2364
2830
|
subscription.lease,
|
|
@@ -2433,8 +2899,25 @@ export class BrowserRuntime {
|
|
|
2433
2899
|
): Promise<void> {
|
|
2434
2900
|
const failedLease = slot.session === session ? (slot.lease ?? null) : null;
|
|
2435
2901
|
const incarnationGeneration = slot.incarnationGeneration;
|
|
2436
|
-
const
|
|
2902
|
+
const subscription = slot.frameSubscription;
|
|
2903
|
+
const refresh = subscription
|
|
2904
|
+
? this.matchingFrameRefresh(slot, subscription, session, incarnationGeneration)
|
|
2905
|
+
: undefined;
|
|
2906
|
+
if (refresh && failLease) this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2907
|
+
const refreshOwned = Boolean(refresh && !refresh.admissionClosed && !refresh.abandoned);
|
|
2908
|
+
const outcome = await this.beginFrameAcknowledgement(
|
|
2909
|
+
session,
|
|
2910
|
+
acknowledgement,
|
|
2911
|
+
refreshOwned ? refresh : undefined,
|
|
2912
|
+
);
|
|
2437
2913
|
if (outcome.status === 'fulfilled' && !failLease) return;
|
|
2914
|
+
if (refreshOwned) return;
|
|
2915
|
+
if (refresh && slot.frameRefresh === refresh) {
|
|
2916
|
+
if (outcome.status !== 'fulfilled') {
|
|
2917
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2918
|
+
}
|
|
2919
|
+
return;
|
|
2920
|
+
}
|
|
2438
2921
|
await this.options.beforeFrameFailureTermination?.();
|
|
2439
2922
|
await this.handleFrameFailure(slot, session, incarnationGeneration, failedLease);
|
|
2440
2923
|
}
|
|
@@ -2444,7 +2927,8 @@ export class BrowserRuntime {
|
|
|
2444
2927
|
session: ILiveBrowserSessionLike,
|
|
2445
2928
|
incarnationGeneration: number,
|
|
2446
2929
|
expectedLease: ILeaseRecord | null,
|
|
2447
|
-
|
|
2930
|
+
expectedRefresh?: IFrameRefreshRecord,
|
|
2931
|
+
): Promise<boolean> {
|
|
2448
2932
|
let transitionGeneration = 0;
|
|
2449
2933
|
let ownsTransitionFence = false;
|
|
2450
2934
|
const lease = expectedLease ?? undefined;
|
|
@@ -2477,8 +2961,14 @@ export class BrowserRuntime {
|
|
|
2477
2961
|
|| slot.attachmentRetryBinding
|
|
2478
2962
|
|| !exactLeaseIsCurrent
|
|
2479
2963
|
|| !leaseAttachmentIsCurrent
|
|
2964
|
+
|| (expectedRefresh !== undefined && slot.frameRefresh !== expectedRefresh)
|
|
2480
2965
|
|| (slot.fencingGeneration !== undefined && !borrowsLeaseReleaseFence)
|
|
2481
|
-
) return;
|
|
2966
|
+
) return false;
|
|
2967
|
+
if (expectedRefresh) {
|
|
2968
|
+
expectedRefresh.admissionClosed = true;
|
|
2969
|
+
expectedRefresh.abandoned = true;
|
|
2970
|
+
slot.frameRefresh = undefined;
|
|
2971
|
+
}
|
|
2482
2972
|
if (lease) this.invalidateCapabilityRecord(lease.capability);
|
|
2483
2973
|
if (borrowsLeaseReleaseFence) {
|
|
2484
2974
|
transitionGeneration = slot.fencingGeneration!;
|
|
@@ -2525,6 +3015,7 @@ export class BrowserRuntime {
|
|
|
2525
3015
|
if (errors.length > 1) {
|
|
2526
3016
|
throw new AggregateError(errors, 'Frame acknowledgement cleanup is incomplete');
|
|
2527
3017
|
}
|
|
3018
|
+
return true;
|
|
2528
3019
|
}
|
|
2529
3020
|
|
|
2530
3021
|
private trackFrameCapabilityRevocation(lease: ILeaseRecord): void {
|
|
@@ -2695,6 +3186,9 @@ export class BrowserRuntime {
|
|
|
2695
3186
|
}
|
|
2696
3187
|
await this.closeFrameSubscriptionIfLease(lease);
|
|
2697
3188
|
await this.quiesceSlot(slot);
|
|
3189
|
+
if (slot.frameRefresh?.lease === lease) {
|
|
3190
|
+
this.abandonFrameRefresh(slot, slot.frameRefresh);
|
|
3191
|
+
}
|
|
2698
3192
|
if (
|
|
2699
3193
|
slot.permanentlyFenced
|
|
2700
3194
|
&& (slot.session || slot.proxy || slot.profileDirectory)
|
|
@@ -2779,6 +3273,9 @@ export class BrowserRuntime {
|
|
|
2779
3273
|
}
|
|
2780
3274
|
const session = slot.session;
|
|
2781
3275
|
if (expectedSession && session !== expectedSession) return;
|
|
3276
|
+
if (slot.frameRefresh && (!session || slot.frameRefresh.session === session)) {
|
|
3277
|
+
this.abandonFrameRefresh(slot, slot.frameRefresh);
|
|
3278
|
+
}
|
|
2782
3279
|
if (session) {
|
|
2783
3280
|
let termination: plugins.smartpuppeteer.ILiveBrowserTerminationResult;
|
|
2784
3281
|
try {
|
|
@@ -3577,6 +4074,12 @@ export class BrowserRuntimeLease {
|
|
|
3577
4074
|
return this.runtime.acknowledgeLeaseFrame(this.record, acknowledgement);
|
|
3578
4075
|
}
|
|
3579
4076
|
|
|
4077
|
+
public refreshFrameStream(
|
|
4078
|
+
operationOptions?: IBrowserRuntimeOperationOptions,
|
|
4079
|
+
): Promise<void> {
|
|
4080
|
+
return this.runtime.leaseRefreshFrameStream(this.record, operationOptions);
|
|
4081
|
+
}
|
|
4082
|
+
|
|
3580
4083
|
public readArtifact(artifactId: string): Promise<Uint8Array> {
|
|
3581
4084
|
return this.runtime.readLeaseArtifact(this.record, artifactId);
|
|
3582
4085
|
}
|