@modelprofile.com/browser-runtime 3.0.1 → 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 +10 -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 +430 -31
- 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 +534 -32
- 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,
|
|
@@ -1191,7 +1243,7 @@ export class BrowserRuntime {
|
|
|
1191
1243
|
let subscription: IFrameSubscriptionRecord;
|
|
1192
1244
|
try {
|
|
1193
1245
|
this.requireValidLease(lease);
|
|
1194
|
-
if (slot.frameSubscription) throw new BrowserRuntimeError('BUSY');
|
|
1246
|
+
if (slot.frameSubscription || slot.frameRefresh) throw new BrowserRuntimeError('BUSY');
|
|
1195
1247
|
subscription = {
|
|
1196
1248
|
lease,
|
|
1197
1249
|
listener,
|
|
@@ -1226,14 +1278,34 @@ export class BrowserRuntime {
|
|
|
1226
1278
|
if (!subscription || subscription.lease !== lease) return false;
|
|
1227
1279
|
const outstanding = subscription.outstanding.get(key);
|
|
1228
1280
|
if (!outstanding) return false;
|
|
1229
|
-
subscription.outstanding.delete(key);
|
|
1230
|
-
clearTimeout(outstanding.timer);
|
|
1231
1281
|
const { session, incarnationGeneration } = outstanding;
|
|
1232
|
-
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(
|
|
1233
1299
|
session,
|
|
1234
1300
|
outstanding.acknowledgement,
|
|
1301
|
+
refreshOwned ? refresh : undefined,
|
|
1235
1302
|
);
|
|
1236
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
|
+
}
|
|
1237
1309
|
await this.options.beforeFrameFailureTermination?.();
|
|
1238
1310
|
await this.handleFrameFailure(slot, session, incarnationGeneration, subscription.lease);
|
|
1239
1311
|
return false;
|
|
@@ -1641,6 +1713,7 @@ export class BrowserRuntime {
|
|
|
1641
1713
|
classification: TBrowserRuntimeOperationClassification,
|
|
1642
1714
|
operationOptions: IBrowserRuntimeOperationOptions,
|
|
1643
1715
|
execute: (signal: AbortSignal, session: ILiveBrowserSessionLike) => Promise<T>,
|
|
1716
|
+
finalize?: () => Promise<void>,
|
|
1644
1717
|
): Promise<T> {
|
|
1645
1718
|
if (!operationOptions || typeof operationOptions !== 'object') {
|
|
1646
1719
|
throw new BrowserRuntimeError('INVALID_INPUT');
|
|
@@ -1679,6 +1752,7 @@ export class BrowserRuntime {
|
|
|
1679
1752
|
onOperationStarted: operationOptions.onOperationStarted,
|
|
1680
1753
|
signal,
|
|
1681
1754
|
execute: execute as IQueuedOperationRecord['execute'],
|
|
1755
|
+
finalize,
|
|
1682
1756
|
resolve: (value) => resolve(value as T),
|
|
1683
1757
|
reject,
|
|
1684
1758
|
state: 'queued',
|
|
@@ -1814,6 +1888,7 @@ export class BrowserRuntime {
|
|
|
1814
1888
|
let failed = false;
|
|
1815
1889
|
let failure: unknown;
|
|
1816
1890
|
let executionStarted = false;
|
|
1891
|
+
let executionSettled = false;
|
|
1817
1892
|
try {
|
|
1818
1893
|
await this.runBeforeOperation(operationIdentity, combinedSignal);
|
|
1819
1894
|
const executionRelease = await slot.mutex.acquire();
|
|
@@ -1830,7 +1905,9 @@ export class BrowserRuntime {
|
|
|
1830
1905
|
) throw new BrowserRuntimeError('ABORTED');
|
|
1831
1906
|
executionStarted = true;
|
|
1832
1907
|
executionPromise = queued.execute(combinedSignal, session);
|
|
1833
|
-
operation.promise = executionPromise.then(() => undefined, () => undefined)
|
|
1908
|
+
operation.promise = executionPromise.then(() => undefined, () => undefined).finally(() => {
|
|
1909
|
+
executionSettled = true;
|
|
1910
|
+
});
|
|
1834
1911
|
} finally {
|
|
1835
1912
|
executionRelease();
|
|
1836
1913
|
}
|
|
@@ -1876,6 +1953,18 @@ export class BrowserRuntime {
|
|
|
1876
1953
|
} else {
|
|
1877
1954
|
await cleanupSlot();
|
|
1878
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
|
+
}
|
|
1879
1968
|
const errorCode = failed ? this.operationErrorCode(failure) : undefined;
|
|
1880
1969
|
await this.emitAudit(Object.freeze({
|
|
1881
1970
|
...operationIdentity,
|
|
@@ -2194,6 +2283,271 @@ export class BrowserRuntime {
|
|
|
2194
2283
|
};
|
|
2195
2284
|
}
|
|
2196
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
|
+
|
|
2197
2551
|
private handleSessionEvent(
|
|
2198
2552
|
slot: IResourceSlot,
|
|
2199
2553
|
session: ILiveBrowserSessionLike,
|
|
@@ -2211,6 +2565,12 @@ export class BrowserRuntime {
|
|
|
2211
2565
|
return;
|
|
2212
2566
|
}
|
|
2213
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;
|
|
2214
2574
|
if (event.type === 'frame') {
|
|
2215
2575
|
let acknowledgement: plugins.smartpuppeteer.ILiveBrowserFrameAcknowledgementRequest;
|
|
2216
2576
|
try {
|
|
@@ -2219,38 +2579,72 @@ export class BrowserRuntime {
|
|
|
2219
2579
|
}
|
|
2220
2580
|
acknowledgement = this.validateFrameAcknowledgement(event.frame);
|
|
2221
2581
|
} catch {
|
|
2582
|
+
if (refreshForSession) {
|
|
2583
|
+
this.failFrameRefresh(refreshForSession, 'FRAME_STREAM_FAILED');
|
|
2584
|
+
return;
|
|
2585
|
+
}
|
|
2222
2586
|
const failedLease = slot.session === session ? (slot.lease ?? null) : null;
|
|
2223
2587
|
this.trackCleanup(this.handleFrameFailure(
|
|
2224
2588
|
slot,
|
|
2225
2589
|
session,
|
|
2226
2590
|
slot.incarnationGeneration,
|
|
2227
2591
|
failedLease,
|
|
2228
|
-
));
|
|
2592
|
+
).then(() => undefined));
|
|
2229
2593
|
return;
|
|
2230
2594
|
}
|
|
2231
2595
|
if (!subscription || subscription.closed) {
|
|
2596
|
+
if (refreshForSession) {
|
|
2597
|
+
this.failFrameRefresh(refreshForSession, 'FRAME_STREAM_FAILED');
|
|
2598
|
+
return;
|
|
2599
|
+
}
|
|
2232
2600
|
this.trackCleanup(this.acknowledgeFrameInBackground(slot, session, acknowledgement));
|
|
2233
2601
|
return;
|
|
2234
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
|
+
}
|
|
2235
2613
|
try {
|
|
2236
2614
|
this.requireValidLease(subscription.lease);
|
|
2237
2615
|
} catch {
|
|
2616
|
+
if (refresh) {
|
|
2617
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2618
|
+
return;
|
|
2619
|
+
}
|
|
2238
2620
|
this.trackCleanup(this.acknowledgeFrameInBackground(slot, session, acknowledgement));
|
|
2239
2621
|
this.trackCleanup(this.revokeCapabilityRecord(subscription.lease.capability));
|
|
2240
2622
|
return;
|
|
2241
2623
|
}
|
|
2242
2624
|
const key = this.frameAcknowledgementKey(acknowledgement);
|
|
2243
2625
|
if (acknowledgement.sequence <= subscription.highestSequence) {
|
|
2626
|
+
if (refresh) {
|
|
2627
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2628
|
+
return;
|
|
2629
|
+
}
|
|
2244
2630
|
this.trackCleanup(this.handleFrameFailure(
|
|
2245
2631
|
slot,
|
|
2246
2632
|
session,
|
|
2247
2633
|
slot.incarnationGeneration,
|
|
2248
2634
|
subscription.lease,
|
|
2249
|
-
));
|
|
2635
|
+
).then(() => undefined));
|
|
2250
2636
|
return;
|
|
2251
2637
|
}
|
|
2252
2638
|
subscription.highestSequence = acknowledgement.sequence;
|
|
2253
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
|
+
}
|
|
2254
2648
|
this.trackCleanup(this.acknowledgeFrameInBackground(
|
|
2255
2649
|
slot,
|
|
2256
2650
|
session,
|
|
@@ -2263,28 +2657,64 @@ export class BrowserRuntime {
|
|
|
2263
2657
|
});
|
|
2264
2658
|
return;
|
|
2265
2659
|
}
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
);
|
|
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
|
+
}
|
|
2272
2684
|
}
|
|
2273
|
-
const
|
|
2274
|
-
this.trackCleanup(this.retireOutstandingFrame(subscription, key, true).then(() => undefined));
|
|
2275
|
-
}, this.options.frameAcknowledgementTimeoutMs);
|
|
2276
|
-
timer.unref();
|
|
2277
|
-
subscription.outstanding.set(key, {
|
|
2685
|
+
const outstanding: IOutstandingFrameRecord = {
|
|
2278
2686
|
acknowledgement,
|
|
2279
|
-
timer,
|
|
2280
2687
|
session,
|
|
2281
2688
|
incarnationGeneration: slot.incarnationGeneration,
|
|
2282
|
-
}
|
|
2283
|
-
|
|
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
|
+
}
|
|
2284
2710
|
return;
|
|
2285
2711
|
}
|
|
2286
2712
|
if (event.type === 'error' && event.error.fatal && slot.lease) {
|
|
2287
|
-
|
|
2713
|
+
if (refreshForSession) {
|
|
2714
|
+
this.failFrameRefresh(refreshForSession, 'FRAME_STREAM_FAILED');
|
|
2715
|
+
} else {
|
|
2716
|
+
this.trackCleanup(this.revokeCapabilityRecord(slot.lease.capability));
|
|
2717
|
+
}
|
|
2288
2718
|
}
|
|
2289
2719
|
if (!subscription || subscription.closed) return;
|
|
2290
2720
|
if (event.type === 'state') {
|
|
@@ -2304,22 +2734,30 @@ export class BrowserRuntime {
|
|
|
2304
2734
|
private pushToSubscription(
|
|
2305
2735
|
subscription: IFrameSubscriptionRecord,
|
|
2306
2736
|
event: TBrowserRuntimeEvent,
|
|
2307
|
-
):
|
|
2737
|
+
): boolean {
|
|
2308
2738
|
try {
|
|
2309
2739
|
this.requireValidLease(subscription.lease);
|
|
2310
2740
|
subscription.listener(event);
|
|
2741
|
+
return true;
|
|
2311
2742
|
} catch {
|
|
2312
2743
|
const slot = subscription.lease.slot;
|
|
2313
2744
|
const session = slot.session;
|
|
2314
2745
|
const incarnationGeneration = slot.incarnationGeneration;
|
|
2746
|
+
const refresh = session
|
|
2747
|
+
? this.matchingFrameRefresh(slot, subscription, session, incarnationGeneration)
|
|
2748
|
+
: undefined;
|
|
2315
2749
|
if (event.type === 'frame') {
|
|
2316
2750
|
const key = this.frameAcknowledgementKey(this.validateFrameAcknowledgement(event.frame));
|
|
2317
2751
|
this.trackCleanup(this.retireOutstandingFrame(subscription, key, true).then(() => undefined));
|
|
2318
|
-
return;
|
|
2752
|
+
return false;
|
|
2753
|
+
}
|
|
2754
|
+
if (refresh) {
|
|
2755
|
+
this.failFrameRefresh(refresh, 'FRAME_STREAM_FAILED');
|
|
2756
|
+
return false;
|
|
2319
2757
|
}
|
|
2320
2758
|
if (!session) {
|
|
2321
2759
|
this.trackCleanup(this.revokeCapabilityRecord(subscription.lease.capability));
|
|
2322
|
-
return;
|
|
2760
|
+
return false;
|
|
2323
2761
|
}
|
|
2324
2762
|
this.trackCleanup((async () => {
|
|
2325
2763
|
await this.options.beforeFrameFailureTermination?.();
|
|
@@ -2330,6 +2768,7 @@ export class BrowserRuntime {
|
|
|
2330
2768
|
subscription.lease,
|
|
2331
2769
|
);
|
|
2332
2770
|
})());
|
|
2771
|
+
return false;
|
|
2333
2772
|
}
|
|
2334
2773
|
}
|
|
2335
2774
|
|
|
@@ -2337,6 +2776,10 @@ export class BrowserRuntime {
|
|
|
2337
2776
|
if (subscription.closed) return;
|
|
2338
2777
|
subscription.closed = true;
|
|
2339
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
|
+
}
|
|
2340
2783
|
if (slot.frameSubscription === subscription) slot.frameSubscription = undefined;
|
|
2341
2784
|
for (const key of [...subscription.outstanding.keys()]) {
|
|
2342
2785
|
await this.retireOutstandingFrame(subscription, key, false);
|
|
@@ -2351,15 +2794,37 @@ export class BrowserRuntime {
|
|
|
2351
2794
|
const outstanding = subscription.outstanding.get(key);
|
|
2352
2795
|
if (!outstanding) return false;
|
|
2353
2796
|
subscription.outstanding.delete(key);
|
|
2354
|
-
clearTimeout(outstanding.timer);
|
|
2355
|
-
|
|
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(
|
|
2356
2813
|
outstanding.session,
|
|
2357
2814
|
outstanding.acknowledgement,
|
|
2815
|
+
refreshOwned ? refresh : undefined,
|
|
2358
2816
|
);
|
|
2359
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
|
+
}
|
|
2360
2825
|
await this.options.beforeFrameFailureTermination?.();
|
|
2361
2826
|
await this.handleFrameFailure(
|
|
2362
|
-
|
|
2827
|
+
slot,
|
|
2363
2828
|
outstanding.session,
|
|
2364
2829
|
outstanding.incarnationGeneration,
|
|
2365
2830
|
subscription.lease,
|
|
@@ -2434,8 +2899,25 @@ export class BrowserRuntime {
|
|
|
2434
2899
|
): Promise<void> {
|
|
2435
2900
|
const failedLease = slot.session === session ? (slot.lease ?? null) : null;
|
|
2436
2901
|
const incarnationGeneration = slot.incarnationGeneration;
|
|
2437
|
-
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
|
+
);
|
|
2438
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
|
+
}
|
|
2439
2921
|
await this.options.beforeFrameFailureTermination?.();
|
|
2440
2922
|
await this.handleFrameFailure(slot, session, incarnationGeneration, failedLease);
|
|
2441
2923
|
}
|
|
@@ -2445,7 +2927,8 @@ export class BrowserRuntime {
|
|
|
2445
2927
|
session: ILiveBrowserSessionLike,
|
|
2446
2928
|
incarnationGeneration: number,
|
|
2447
2929
|
expectedLease: ILeaseRecord | null,
|
|
2448
|
-
|
|
2930
|
+
expectedRefresh?: IFrameRefreshRecord,
|
|
2931
|
+
): Promise<boolean> {
|
|
2449
2932
|
let transitionGeneration = 0;
|
|
2450
2933
|
let ownsTransitionFence = false;
|
|
2451
2934
|
const lease = expectedLease ?? undefined;
|
|
@@ -2478,8 +2961,14 @@ export class BrowserRuntime {
|
|
|
2478
2961
|
|| slot.attachmentRetryBinding
|
|
2479
2962
|
|| !exactLeaseIsCurrent
|
|
2480
2963
|
|| !leaseAttachmentIsCurrent
|
|
2964
|
+
|| (expectedRefresh !== undefined && slot.frameRefresh !== expectedRefresh)
|
|
2481
2965
|
|| (slot.fencingGeneration !== undefined && !borrowsLeaseReleaseFence)
|
|
2482
|
-
) return;
|
|
2966
|
+
) return false;
|
|
2967
|
+
if (expectedRefresh) {
|
|
2968
|
+
expectedRefresh.admissionClosed = true;
|
|
2969
|
+
expectedRefresh.abandoned = true;
|
|
2970
|
+
slot.frameRefresh = undefined;
|
|
2971
|
+
}
|
|
2483
2972
|
if (lease) this.invalidateCapabilityRecord(lease.capability);
|
|
2484
2973
|
if (borrowsLeaseReleaseFence) {
|
|
2485
2974
|
transitionGeneration = slot.fencingGeneration!;
|
|
@@ -2526,6 +3015,7 @@ export class BrowserRuntime {
|
|
|
2526
3015
|
if (errors.length > 1) {
|
|
2527
3016
|
throw new AggregateError(errors, 'Frame acknowledgement cleanup is incomplete');
|
|
2528
3017
|
}
|
|
3018
|
+
return true;
|
|
2529
3019
|
}
|
|
2530
3020
|
|
|
2531
3021
|
private trackFrameCapabilityRevocation(lease: ILeaseRecord): void {
|
|
@@ -2696,6 +3186,9 @@ export class BrowserRuntime {
|
|
|
2696
3186
|
}
|
|
2697
3187
|
await this.closeFrameSubscriptionIfLease(lease);
|
|
2698
3188
|
await this.quiesceSlot(slot);
|
|
3189
|
+
if (slot.frameRefresh?.lease === lease) {
|
|
3190
|
+
this.abandonFrameRefresh(slot, slot.frameRefresh);
|
|
3191
|
+
}
|
|
2699
3192
|
if (
|
|
2700
3193
|
slot.permanentlyFenced
|
|
2701
3194
|
&& (slot.session || slot.proxy || slot.profileDirectory)
|
|
@@ -2780,6 +3273,9 @@ export class BrowserRuntime {
|
|
|
2780
3273
|
}
|
|
2781
3274
|
const session = slot.session;
|
|
2782
3275
|
if (expectedSession && session !== expectedSession) return;
|
|
3276
|
+
if (slot.frameRefresh && (!session || slot.frameRefresh.session === session)) {
|
|
3277
|
+
this.abandonFrameRefresh(slot, slot.frameRefresh);
|
|
3278
|
+
}
|
|
2783
3279
|
if (session) {
|
|
2784
3280
|
let termination: plugins.smartpuppeteer.ILiveBrowserTerminationResult;
|
|
2785
3281
|
try {
|
|
@@ -3578,6 +4074,12 @@ export class BrowserRuntimeLease {
|
|
|
3578
4074
|
return this.runtime.acknowledgeLeaseFrame(this.record, acknowledgement);
|
|
3579
4075
|
}
|
|
3580
4076
|
|
|
4077
|
+
public refreshFrameStream(
|
|
4078
|
+
operationOptions?: IBrowserRuntimeOperationOptions,
|
|
4079
|
+
): Promise<void> {
|
|
4080
|
+
return this.runtime.leaseRefreshFrameStream(this.record, operationOptions);
|
|
4081
|
+
}
|
|
4082
|
+
|
|
3581
4083
|
public readArtifact(artifactId: string): Promise<Uint8Array> {
|
|
3582
4084
|
return this.runtime.readLeaseArtifact(this.record, artifactId);
|
|
3583
4085
|
}
|