Skip to content

Commit

Permalink
fix(timetravel): multi-tabs conflicting documents
Browse files Browse the repository at this point in the history
  • Loading branch information
blakebyrnes committed Nov 22, 2021
1 parent af7b1c4 commit 8943a6e
Show file tree
Hide file tree
Showing 9 changed files with 74 additions and 14 deletions.
3 changes: 0 additions & 3 deletions core/injected-scripts/interactReplayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,7 @@ window.replayInteractions = function replayInteractions(
scrollEvent: IFrontendScrollEvent,
) {
highlightNodes(resultNodeIds);
const startingTracking = shouldTrackMouse;
shouldTrackMouse = true;
updateMouse(mouseEvent);
shouldTrackMouse = startingTracking;
updateScroll(scrollEvent);
};

Expand Down
5 changes: 4 additions & 1 deletion core/models/ScreenshotsTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export default class ScreenshotsTable extends SqliteTable<IScreenshot> {
public includeWhiteScreens = false;

private screenshotTimesByTabId = new Map<number, number[]>();
private hasLoadedCounts = false;
private lastImageByTab: { [tabId: number]: Buffer } = {};

constructor(readonly db: SqliteDatabase) {
Expand Down Expand Up @@ -36,7 +37,8 @@ export default class ScreenshotsTable extends SqliteTable<IScreenshot> {
}

public getScreenshotTimesByTabId(): ScreenshotsTable['screenshotTimesByTabId'] {
if (this.screenshotTimesByTabId.size) return this.screenshotTimesByTabId;
if (this.hasLoadedCounts) return this.screenshotTimesByTabId;
this.hasLoadedCounts = true;
const timestamps = this.db.prepare(`select timestamp, tabId from ${this.tableName}`).all();
for (const { timestamp, tabId } of timestamps) {
this.trackScreenshotTime(tabId, timestamp);
Expand All @@ -60,6 +62,7 @@ export default class ScreenshotsTable extends SqliteTable<IScreenshot> {
}

private trackScreenshotTime(tabId: number, timestamp: number): void {
this.hasLoadedCounts = true;
if (!this.screenshotTimesByTabId.has(tabId)) {
this.screenshotTimesByTabId.set(tabId, []);
}
Expand Down
27 changes: 27 additions & 0 deletions core/models/StorageChangesTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import TypeSerializer from '@ulixee/commons/lib/TypeSerializer';
import { IPuppetStorageEvents } from '@ulixee/hero-interfaces/IPuppetDomStorageTracker';

export default class StorageChangesTable extends SqliteTable<IStorageChangesEntry> {
private changesByTabIdAndTime: {
[tabId_timestamp: string]: { tabId: number; timestamp: number; count: number };
} = {};

private hasLoadedCounts = false;

constructor(readonly db: SqliteDatabase) {
super(db, 'StorageChanges', [
['tabId', 'INTEGER'],
Expand Down Expand Up @@ -33,6 +39,7 @@ export default class StorageChangesTable extends SqliteTable<IStorageChangesEntr
TypeSerializer.stringify(entry.meta),
entry.timestamp,
]);
this.trackChangeTime(tabId, entry.timestamp);
}

public findChange(
Expand Down Expand Up @@ -60,6 +67,26 @@ export default class StorageChangesTable extends SqliteTable<IStorageChangesEntr
)
.all(tabId, startTime, endTime);
}

public getChangesByTabIdAndTime(): { tabId: number; timestamp: number; count: number }[] {
if (!this.hasLoadedCounts) {
this.hasLoadedCounts = true;
const timestamps = this.db.prepare(`select timestamp, tabId from ${this.tableName}`).all();
for (const { timestamp, tabId } of timestamps) {
this.trackChangeTime(tabId, timestamp);
}
}
const times = Object.values(this.changesByTabIdAndTime);
times.sort((a, b) => a.timestamp - b.timestamp);
return times;
}

private trackChangeTime(tabId: number, timestamp: number): void {
this.hasLoadedCounts = true;
const key = `${tabId}_${timestamp}`;
this.changesByTabIdAndTime[key] ??= { tabId, timestamp, count: 0 };
this.changesByTabIdAndTime[key].count += 1;
}
}

export interface IStorageChangesEntry {
Expand Down
5 changes: 5 additions & 0 deletions interfaces/ITimelineMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ export default interface ITimelineMetadata {
offsetPercent: number;
tabId: number;
}[];
storageEvents: {
offsetPercent: number;
tabId: number;
count: number;
}[];
}
10 changes: 6 additions & 4 deletions timetravel/lib/MirrorNetwork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@ import decodeBuffer from '@ulixee/commons/lib/decodeBuffer';
import { bindFunctions } from '@ulixee/commons/lib/utils';
import { ISessionResourceDetails } from '@ulixee/hero-core/apis/Session.resource';
import ResourcesTable, { IResourcesRecord } from '@ulixee/hero-core/models/ResourcesTable';
import { IDocument } from '@ulixee/hero-core/models/DomChangesTable';
import SessionDb from '@ulixee/hero-core/dbs/SessionDb';
import Fetch = Protocol.Fetch;

export default class MirrorNetwork {
public resourceLookup: { [method_url: string]: IResourceSummary[] } = {};
public documents: IDocument[] = [];
public headersFilter: (string | RegExp)[];
public ignoreJavascriptRequests: boolean;
public useResourcesOnce: boolean;

private readonly doctypesByUrl: { [url: string]: string } = {};
private loadResourceDetails: (
id: number,
) => Promise<ISessionResourceDetails> | ISessionResourceDetails;
Expand All @@ -30,17 +29,20 @@ export default class MirrorNetwork {
bindFunctions(this);
}

public registerDoctype(url: string, doctype: string): void {
this.doctypesByUrl[url] = doctype;
}

public close(): void {
this.resourceLookup = {};
this.documents.length = 0;
}

public async mirrorNetworkRequests(
request: Fetch.RequestPausedEvent,
): Promise<Fetch.FulfillRequestRequest> {
const { url, method } = request.request;
if (request.resourceType === 'Document' || url === 'about:blank') {
const doctype = this.documents.find(x => x.url === url)?.doctype ?? '';
const doctype = this.doctypesByUrl[url] ?? '';
return {
requestId: request.requestId,
responseCode: 200,
Expand Down
10 changes: 8 additions & 2 deletions timetravel/lib/MirrorPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ export default class MirrorPage extends TypedEventEmitter<{
private debugLogging: boolean = false,
) {
super();
network.documents = domRecording.documents;
for (const document of domRecording.documents ?? []) {
if (document.doctype) this.network.registerDoctype(document.url, document.doctype);
}
}

public async open(
Expand Down Expand Up @@ -142,6 +144,7 @@ export default class MirrorPage extends TypedEventEmitter<{
}

public async navigate(url: string): Promise<void> {
await this.isReady;
if (this.debugLogging) {
log.info('MirrorPage.goto', {
url,
Expand All @@ -156,17 +159,19 @@ export default class MirrorPage extends TypedEventEmitter<{
}

public async load(paintIndexRange?: [number, number]): Promise<void> {
await this.isReady;
paintIndexRange ??= [0, this.domRecording.paintEvents.length - 1];
let [startIndex] = paintIndexRange;
const endIndex = paintIndexRange[1];

let loadingDocument: IDocument;
for (const document of this.network.documents) {
for (const document of this.domRecording.documents) {
if (!document.isMainframe) continue;
if (document.paintEventIndex >= startIndex && document.paintEventIndex <= endIndex) {
loadingDocument = document;
if (document.paintEventIndex > startIndex) startIndex = document.paintEventIndex;
}
this.network.registerDoctype(document.url, document.doctype);
}

const page = this.page;
Expand Down Expand Up @@ -241,6 +246,7 @@ export default class MirrorPage extends TypedEventEmitter<{
}

private async evaluate<T>(expression: string): Promise<T> {
await this.isReady;
return await this.page.mainFrame.evaluate(expression, true, { retriesWaitingForLoad: 2 });
}

Expand Down
7 changes: 6 additions & 1 deletion timetravel/lib/PageStateGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,12 @@ export default class PageStateGenerator {
};
}
for (const session of savedState.sessions) {
const db = SessionDb.getCached(session.sessionId, false);
let db: SessionDb;
try {
db = SessionDb.getCached(session.sessionId, false);
} catch (err) {
// couldn't load
}
this.sessionsById.set(session.sessionId, {
...session,
db,
Expand Down
15 changes: 15 additions & 0 deletions timetravel/lib/TimelineBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,25 @@ export default class TimelineBuilder {
});
}

const storageEvents: ITimelineMetadata['storageEvents'] = [];
const changesByTabId: { [tabId: number]: number } = {};
for (const { tabId, timestamp, count } of db.storageChanges.getChangesByTabIdAndTime()) {
changesByTabId[tabId] ??= 0;
changesByTabId[tabId] += count;
const offsetPercent = commandTimeline.getTimelineOffsetForTimestamp(timestamp);
if (offsetPercent === -1) continue;
storageEvents.push({
offsetPercent,
tabId,
count: changesByTabId[tabId],
});
}

return {
urls,
screenshots,
paintEvents,
storageEvents,
};
}
}
6 changes: 3 additions & 3 deletions timetravel/player/TimetravelPlayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export default class TimetravelPlayer extends TypedEventEmitter<{
const tab = this.activeTab;

const startTick = tab.currentTick;
const startedOpen = this.isOpen;
const startedOpen = tab.isOpen;
await this.openTab(tab);
if (sessionOffsetPercent !== undefined) {
await tab.setTimelineOffset(sessionOffsetPercent);
Expand Down Expand Up @@ -205,12 +205,12 @@ export default class TimetravelPlayer extends TypedEventEmitter<{
});

if (this.debugLogging) {
log.info('Replay Tab State', {
log.info('Timetravel Tab State', {
sessionId: this.sessionId,
tabDetails: ticksResult.tabDetails,
});
}

for (const tabDetails of ticksResult.tabDetails) {
const tabPlaybackController = this.tabsById.get(tabDetails.tab.id);
if (tabPlaybackController) {
Expand Down

0 comments on commit 8943a6e

Please sign in to comment.