fix litjs support, fix autocomplete modal options reset, fix dashboard chart density (#3382)

* Litjs fixes2 (#3381)

* ui: fixes for litjs capture

* ui: introduce vmode for lwc light dom

* ui: fixup the mode toggle and remover

* ui: fix filter options reset, fix dashboard chart density
This commit is contained in:
Delirium 2025-05-12 15:27:44 +02:00 committed by GitHub
parent 447fc26a2a
commit 58da1d3f64
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 178 additions and 67 deletions

View file

@ -6,6 +6,7 @@ import DefaultPlaying from 'Shared/SessionSettings/components/DefaultPlaying';
import DefaultTimezone from 'Shared/SessionSettings/components/DefaultTimezone'; import DefaultTimezone from 'Shared/SessionSettings/components/DefaultTimezone';
import ListingVisibility from 'Shared/SessionSettings/components/ListingVisibility'; import ListingVisibility from 'Shared/SessionSettings/components/ListingVisibility';
import MouseTrailSettings from 'Shared/SessionSettings/components/MouseTrailSettings'; import MouseTrailSettings from 'Shared/SessionSettings/components/MouseTrailSettings';
import VirtualModeSettings from '../shared/SessionSettings/components/VirtualMode';
import DebugLog from './DebugLog'; import DebugLog from './DebugLog';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -35,6 +36,7 @@ function SessionsListingSettings() {
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<MouseTrailSettings /> <MouseTrailSettings />
<DebugLog /> <DebugLog />
<VirtualModeSettings />
</div> </div>
</div> </div>
</div> </div>

View file

@ -181,9 +181,10 @@ function WidgetChart(props: Props) {
} }
prevMetricRef.current = _metric; prevMetricRef.current = _metric;
const timestmaps = drillDownPeriod.toTimestamps(); const timestmaps = drillDownPeriod.toTimestamps();
const density = props.isPreview ? metric.density : dashboardStore.selectedDensity
const payload = isSaved const payload = isSaved
? { ...metricParams } ? { ...metricParams, density }
: { ...params, ...timestmaps, ..._metric.toJson() }; : { ...params, ...timestmaps, ..._metric.toJson(), density };
debounceRequest( debounceRequest(
_metric, _metric,
payload, payload,

View file

@ -55,7 +55,7 @@ function RangeGranularity({
} }
const PAST_24_HR_MS = 24 * 60 * 60 * 1000; const PAST_24_HR_MS = 24 * 60 * 60 * 1000;
function calculateGranularities(periodDurationMs: number) { export function calculateGranularities(periodDurationMs: number) {
const granularities = [ const granularities = [
{ label: 'Hourly', durationMs: 60 * 60 * 1000 }, { label: 'Hourly', durationMs: 60 * 60 * 1000 },
{ label: 'Daily', durationMs: 24 * 60 * 60 * 1000 }, { label: 'Daily', durationMs: 24 * 60 * 60 * 1000 },

View file

@ -38,6 +38,7 @@ function SubHeader(props) {
projectsStore, projectsStore,
userStore, userStore,
issueReportingStore, issueReportingStore,
settingsStore
} = useStore(); } = useStore();
const { t } = useTranslation(); const { t } = useTranslation();
const { favorite } = sessionStore.current; const { favorite } = sessionStore.current;
@ -45,7 +46,7 @@ function SubHeader(props) {
const currentSession = sessionStore.current; const currentSession = sessionStore.current;
const projectId = projectsStore.siteId; const projectId = projectsStore.siteId;
const integrations = integrationsStore.issues.list; const integrations = integrationsStore.issues.list;
const { store } = React.useContext(PlayerContext); const { player, store } = React.useContext(PlayerContext);
const { location: currentLocation = 'loading...' } = store.get(); const { location: currentLocation = 'loading...' } = store.get();
const hasIframe = localStorage.getItem(IFRAME) === 'true'; const hasIframe = localStorage.getItem(IFRAME) === 'true';
const [hideTools, setHideTools] = React.useState(false); const [hideTools, setHideTools] = React.useState(false);
@ -127,6 +128,13 @@ function SubHeader(props) {
}); });
}; };
const showVModeBadge = store.get().vModeBadge;
const onVMode = () => {
settingsStore.sessionSettings.updateKey('virtualMode', true);
player.enableVMode?.();
location.reload();
}
return ( return (
<> <>
<div <div
@ -143,6 +151,8 @@ function SubHeader(props) {
siteId={projectId!} siteId={projectId!}
currentLocation={currentLocation} currentLocation={currentLocation}
version={currentSession?.trackerVersion ?? ''} version={currentSession?.trackerVersion ?? ''}
virtualElsFailed={showVModeBadge}
onVMode={onVMode}
/> />
<SessionTabs /> <SessionTabs />

View file

@ -34,38 +34,46 @@ const WarnBadge = React.memo(
currentLocation, currentLocation,
version, version,
siteId, siteId,
virtualElsFailed,
onVMode,
}: { }: {
currentLocation: string; currentLocation: string;
version: string; version: string;
siteId: string; siteId: string;
virtualElsFailed: boolean;
onVMode: () => void;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const localhostWarnSiteKey = localhostWarn(siteId); const localhostWarnSiteKey = localhostWarn(siteId);
const defaultLocalhostWarn = const defaultLocalhostWarn =
localStorage.getItem(localhostWarnSiteKey) !== '1'; localStorage.getItem(localhostWarnSiteKey) !== '1';
const localhostWarnActive = const localhostWarnActive = Boolean(
currentLocation && currentLocation &&
defaultLocalhostWarn && defaultLocalhostWarn &&
/(localhost)|(127.0.0.1)|(0.0.0.0)/.test(currentLocation); /(localhost)|(127.0.0.1)|(0.0.0.0)/.test(currentLocation)
)
const trackerVersion = window.env.TRACKER_VERSION ?? undefined; const trackerVersion = window.env.TRACKER_VERSION ?? undefined;
const trackerVerDiff = compareVersions(version, trackerVersion); const trackerVerDiff = compareVersions(version, trackerVersion);
const trackerWarnActive = trackerVerDiff !== VersionComparison.Same; const trackerWarnActive = trackerVerDiff !== VersionComparison.Same;
const [showLocalhostWarn, setLocalhostWarn] = const [warnings, setWarnings] = React.useState<[localhostWarn: boolean, trackerWarn: boolean, virtualElsFailWarn: boolean]>([localhostWarnActive, trackerWarnActive, virtualElsFailed])
React.useState(localhostWarnActive);
const [showTrackerWarn, setTrackerWarn] = React.useState(trackerWarnActive);
const closeWarning = (type: 1 | 2) => { React.useEffect(() => {
setWarnings([localhostWarnActive, trackerWarnActive, virtualElsFailed])
}, [localhostWarnActive, trackerWarnActive, virtualElsFailed])
const closeWarning = (type: 0 | 1 | 2) => {
if (type === 1) { if (type === 1) {
localStorage.setItem(localhostWarnSiteKey, '1'); localStorage.setItem(localhostWarnSiteKey, '1');
setLocalhostWarn(false);
}
if (type === 2) {
setTrackerWarn(false);
} }
setWarnings((prev) => {
const newWarnings = [...prev];
newWarnings[type] = false;
return newWarnings;
});
}; };
if (!showLocalhostWarn && !showTrackerWarn) return null; if (!warnings.some(el => el === true)) return null;
return ( return (
<div <div
@ -79,7 +87,7 @@ const WarnBadge = React.memo(
fontWeight: 500, fontWeight: 500,
}} }}
> >
{showLocalhostWarn ? ( {warnings[0] ? (
<div className="px-3 py-1 border border-gray-lighter drop-shadow-md rounded bg-active-blue flex items-center justify-between"> <div className="px-3 py-1 border border-gray-lighter drop-shadow-md rounded bg-active-blue flex items-center justify-between">
<div> <div>
<span>{t('Some assets may load incorrectly on localhost.')}</span> <span>{t('Some assets may load incorrectly on localhost.')}</span>
@ -101,7 +109,7 @@ const WarnBadge = React.memo(
</div> </div>
</div> </div>
) : null} ) : null}
{showTrackerWarn ? ( {warnings[1] ? (
<div className="px-3 py-1 border border-gray-lighter drop-shadow-md rounded bg-active-blue flex items-center justify-between"> <div className="px-3 py-1 border border-gray-lighter drop-shadow-md rounded bg-active-blue flex items-center justify-between">
<div> <div>
<div> <div>
@ -125,6 +133,21 @@ const WarnBadge = React.memo(
</div> </div>
</div> </div>
<div
className="py-1 ml-3 cursor-pointer"
onClick={() => closeWarning(2)}
>
<Icon name="close" size={16} color="black" />
</div>
</div>
) : null}
{warnings[2] ? (
<div className="px-3 py-1 border border-gray-lighter drop-shadow-md rounded bg-active-blue flex items-center justify-between">
<div className="flex flex-col">
<div>{t('If you have issues displaying custom HTML elements (i.e when using LWC), consider turning on Virtual Mode.')}</div>
<div className='link' onClick={onVMode}>{t('Enable')}</div>
</div>
<div <div
className="py-1 ml-3 cursor-pointer" className="py-1 ml-3 cursor-pointer"
onClick={() => closeWarning(2)} onClick={() => closeWarning(2)}

View file

@ -125,7 +125,7 @@ export function AutocompleteModal({
if (index === blocksAmount - 1 && blocksAmount > 1) { if (index === blocksAmount - 1 && blocksAmount > 1) {
str += ' and '; str += ' and ';
} }
str += `"${block.trim()}"`; str += block.trim();
if (index < blocksAmount - 2) { if (index < blocksAmount - 2) {
str += ', '; str += ', ';
} }
@ -188,10 +188,10 @@ export function AutocompleteModal({
{query.length ? ( {query.length ? (
<div className="border-y border-y-gray-light py-2"> <div className="border-y border-y-gray-light py-2">
<div <div
className="whitespace-normal rounded cursor-pointer text-teal hover:bg-active-blue px-2 py-1" className="whitespace-nowrap truncate w-full rounded cursor-pointer text-teal hover:bg-active-blue px-2 py-1"
onClick={applyQuery} onClick={applyQuery}
> >
{t('Apply')}&nbsp;{queryStr} {t('Apply')}&nbsp;<span className='font-semibold'>{queryStr}</span>
</div> </div>
</div> </div>
) : null} ) : null}

View file

@ -128,8 +128,10 @@ const FilterAutoComplete = observer(
}; };
const handleFocus = () => { const handleFocus = () => {
setInitialFocus(true); if (!initialFocus) {
setOptions(topValues.map((i) => ({ value: i.value, label: i.value }))); setOptions(topValues.map((i) => ({ value: i.value, label: i.value })));
}
setInitialFocus(true);
}; };
return ( return (

View file

@ -5,6 +5,7 @@ import ListingVisibility from './components/ListingVisibility';
import DefaultPlaying from './components/DefaultPlaying'; import DefaultPlaying from './components/DefaultPlaying';
import DefaultTimezone from './components/DefaultTimezone'; import DefaultTimezone from './components/DefaultTimezone';
import CaptureRate from './components/CaptureRate'; import CaptureRate from './components/CaptureRate';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
function SessionSettings() { function SessionSettings() {

View file

@ -0,0 +1,30 @@
import React from 'react';
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
import { Switch } from 'UI';
import { useTranslation } from 'react-i18next';
function VirtualModeSettings() {
const { settingsStore } = useStore();
const { sessionSettings } = settingsStore;
const { virtualMode } = sessionSettings;
const { t } = useTranslation();
const updateSettings = (checked: boolean) => {
settingsStore.sessionSettings.updateKey('virtualMode', !virtualMode);
};
return (
<div>
<h3 className="text-lg">{t('Virtual Mode')}</h3>
<div className="my-1">
{t('Change this setting if you have issues with recordings containing Lightning Web Components (or similar custom HTML Element libraries).')}
</div>
<div className="mt-2">
<Switch onChange={updateSettings} checked={virtualMode} />
</div>
</div>
);
}
export default observer(VirtualModeSettings);

View file

@ -9,6 +9,7 @@ export const GLOBAL_HAS_NO_RECORDINGS = '__$global-hasNoRecordings$__';
export const SITE_ID_STORAGE_KEY = '__$user-siteId$__'; export const SITE_ID_STORAGE_KEY = '__$user-siteId$__';
export const GETTING_STARTED = '__$user-gettingStarted$__'; export const GETTING_STARTED = '__$user-gettingStarted$__';
export const MOUSE_TRAIL = '__$session-mouseTrail$__'; export const MOUSE_TRAIL = '__$session-mouseTrail$__';
export const VIRTUAL_MODE_KEY = '__$session-virtualMode$__'
export const IFRAME = '__$session-iframe$__'; export const IFRAME = '__$session-iframe$__';
export const JWT_PARAM = '__$session-jwt-param$__'; export const JWT_PARAM = '__$session-jwt-param$__';
export const MENU_COLLAPSED = '__$global-menuCollapsed$__'; export const MENU_COLLAPSED = '__$global-menuCollapsed$__';

View file

@ -1,4 +1,4 @@
import { makeAutoObservable, runInAction } from 'mobx'; import { makeAutoObservable, runInAction, reaction } from 'mobx';
import { dashboardService, metricService } from 'App/services'; import { dashboardService, metricService } from 'App/services';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import Period, { LAST_24_HOURS, LAST_7_DAYS } from 'Types/app/period'; import Period, { LAST_24_HOURS, LAST_7_DAYS } from 'Types/app/period';
@ -6,6 +6,7 @@ import { getRE } from 'App/utils';
import Filter from './types/filter'; import Filter from './types/filter';
import Widget from './types/widget'; import Widget from './types/widget';
import Dashboard from './types/dashboard'; import Dashboard from './types/dashboard';
import { calculateGranularities } from '@/components/Dashboard/components/WidgetDateRange/RangeGranularity';
interface DashboardFilter { interface DashboardFilter {
query?: string; query?: string;
@ -36,7 +37,7 @@ export default class DashboardStore {
drillDownPeriod: Record<string, any> = Period({ rangeName: LAST_24_HOURS }); drillDownPeriod: Record<string, any> = Period({ rangeName: LAST_24_HOURS });
selectedDensity: number = 7; // depends on default drilldown, 7 points here!!!; selectedDensity: number = 7;
comparisonPeriods: Record<string, any> = {}; comparisonPeriods: Record<string, any> = {};
@ -83,10 +84,25 @@ export default class DashboardStore {
makeAutoObservable(this); makeAutoObservable(this);
this.resetDrillDownFilter(); this.resetDrillDownFilter();
this.createDensity(this.period.getDuration());
reaction(
() => this.period,
(period) => {
this.createDensity(period.getDuration());
}
)
} }
setDensity = (density: any) => { createDensity = (duration: number) => {
this.selectedDensity = parseInt(density, 10); const densityOpts = calculateGranularities(duration);
const defaultOption = densityOpts[densityOpts.length - 2];
this.setDensity(defaultOption.key)
}
setDensity = (density: number) => {
this.selectedDensity = density;
}; };
get sortedDashboards() { get sortedDashboards() {
@ -529,7 +545,7 @@ export default class DashboardStore {
const data = await metricService.getMetricChartData( const data = await metricService.getMetricChartData(
metric, metric,
params, params,
isSaved, isSaved
); );
resolve(metric.setData(data, period, isComparison, density)); resolve(metric.setData(data, period, isComparison, density));
} catch (error) { } catch (error) {

View file

@ -6,6 +6,7 @@ import {
SHOWN_TIMEZONE, SHOWN_TIMEZONE,
DURATION_FILTER, DURATION_FILTER,
MOUSE_TRAIL, MOUSE_TRAIL,
VIRTUAL_MODE_KEY,
} from 'App/constants/storageKeys'; } from 'App/constants/storageKeys';
import { DateTime, Settings } from 'luxon'; import { DateTime, Settings } from 'luxon';
@ -71,27 +72,19 @@ export const generateGMTZones = (): Timezone[] => {
export default class SessionSettings { export default class SessionSettings {
defaultTimezones = [...generateGMTZones()]; defaultTimezones = [...generateGMTZones()];
skipToIssue: boolean = localStorage.getItem(SKIP_TO_ISSUE) === 'true'; skipToIssue: boolean = localStorage.getItem(SKIP_TO_ISSUE) === 'true';
timezone: Timezone; timezone: Timezone;
durationFilter: any = JSON.parse( durationFilter: any = JSON.parse(
localStorage.getItem(DURATION_FILTER) || localStorage.getItem(DURATION_FILTER) ||
JSON.stringify(defaultDurationFilter), JSON.stringify(defaultDurationFilter),
); );
captureRate: string = '0'; captureRate: string = '0';
conditionalCapture: boolean = false; conditionalCapture: boolean = false;
captureConditions: { name: string; captureRate: number; filters: any[] }[] = captureConditions: { name: string; captureRate: number; filters: any[] }[] =
[]; [];
mouseTrail: boolean = localStorage.getItem(MOUSE_TRAIL) !== 'false'; mouseTrail: boolean = localStorage.getItem(MOUSE_TRAIL) !== 'false';
shownTimezone: 'user' | 'local'; shownTimezone: 'user' | 'local';
virtualMode: boolean = localStorage.getItem(VIRTUAL_MODE_KEY) === 'true';
usingLocal: boolean = false; usingLocal: boolean = false;
constructor() { constructor() {

View file

@ -163,6 +163,7 @@ export default class Widget {
fromJson(json: any, period?: any) { fromJson(json: any, period?: any) {
json.config = json.config || {}; json.config = json.config || {};
runInAction(() => { runInAction(() => {
this.dashboardId = json.dashboardId;
this.metricId = json.metricId; this.metricId = json.metricId;
this.widgetId = json.widgetId; this.widgetId = json.widgetId;
this.metricValue = this.metricValueFromArray( this.metricValue = this.metricValueFromArray(

View file

@ -80,7 +80,6 @@ export default class MessageLoader {
let artificialStartTime = Infinity; let artificialStartTime = Infinity;
let startTimeSet = false; let startTimeSet = false;
msgs.forEach((msg, i) => { msgs.forEach((msg, i) => {
if (msg.tp === MType.Redux || msg.tp === MType.ReduxDeprecated) { if (msg.tp === MType.Redux || msg.tp === MType.ReduxDeprecated) {
if ('actionTime' in msg && msg.actionTime) { if ('actionTime' in msg && msg.actionTime) {

View file

@ -1,7 +1,7 @@
// @ts-ignore // @ts-ignore
import { Decoder } from 'syncod'; import { Decoder } from 'syncod';
import logger from 'App/logger'; import logger from 'App/logger';
import { VIRTUAL_MODE_KEY } from '@/constants/storageKeys';
import type { Store, ILog, SessionFilesInfo } from 'Player'; import type { Store, ILog, SessionFilesInfo } from 'Player';
import TabSessionManager, { TabState } from 'Player/web/TabManager'; import TabSessionManager, { TabState } from 'Player/web/TabManager';
import ActiveTabManager from 'Player/web/managers/ActiveTabManager'; import ActiveTabManager from 'Player/web/managers/ActiveTabManager';
@ -69,6 +69,7 @@ export interface State extends ScreenState {
tabChangeEvents: TabChangeEvent[]; tabChangeEvents: TabChangeEvent[];
closedTabs: string[]; closedTabs: string[];
sessionStart: number; sessionStart: number;
vModeBadge: boolean;
} }
export const visualChanges = [ export const visualChanges = [
@ -99,6 +100,7 @@ export default class MessageManager {
closedTabs: [], closedTabs: [],
sessionStart: 0, sessionStart: 0,
tabNames: {}, tabNames: {},
vModeBadge: false,
}; };
private clickManager: ListWalker<MouseClick> = new ListWalker(); private clickManager: ListWalker<MouseClick> = new ListWalker();
@ -126,7 +128,6 @@ export default class MessageManager {
private tabsAmount = 0; private tabsAmount = 0;
private tabChangeEvents: TabChangeEvent[] = []; private tabChangeEvents: TabChangeEvent[] = [];
private activeTab = ''; private activeTab = '';
constructor( constructor(
@ -142,7 +143,18 @@ export default class MessageManager {
this.activityManager = new ActivityManager( this.activityManager = new ActivityManager(
this.session.duration.milliseconds, this.session.duration.milliseconds,
); // only if not-live ); // only if not-live
const vMode = localStorage.getItem(VIRTUAL_MODE_KEY);
if (vMode === 'true') {
this.setVirtualMode(true);
} }
}
private virtualMode = false;
public setVirtualMode = (virtualMode: boolean) => {
this.virtualMode = virtualMode;
Object.values(this.tabs).forEach((tab) => tab.setVirtualMode(virtualMode));
};
public getListsFullState = () => { public getListsFullState = () => {
const fullState: Record<string, any> = {}; const fullState: Record<string, any> = {};
@ -394,6 +406,9 @@ export default class MessageManager {
this.sessionStart, this.sessionStart,
this.initialLists, this.initialLists,
); );
if (this.virtualMode) {
this.tabs[msg.tabId].setVirtualMode(this.virtualMode);
}
} }
const lastMessageTime = Math.max(msg.time, this.lastMessageTime); const lastMessageTime = Math.max(msg.time, this.lastMessageTime);

View file

@ -99,6 +99,7 @@ export default class TabSessionManager {
tabStates: { [tabId: string]: TabState }; tabStates: { [tabId: string]: TabState };
tabNames: { [tabId: string]: string }; tabNames: { [tabId: string]: string };
location?: string; location?: string;
vModeBadge?: boolean;
}>, }>,
private readonly screen: Screen, private readonly screen: Screen,
private readonly id: string, private readonly id: string,
@ -116,6 +117,14 @@ export default class TabSessionManager {
screen, screen,
this.session.isMobile, this.session.isMobile,
this.setCSSLoading, this.setCSSLoading,
() => {
setTimeout(() => {
this.state.update({
vModeBadge: true,
})
// easier to spot the warning appearing plus more time to go over all messages
}, 1000)
}
); );
this.lists = new Lists(initialLists); this.lists = new Lists(initialLists);
initialLists?.event?.forEach((e: Record<string, string>) => { initialLists?.event?.forEach((e: Record<string, string>) => {
@ -126,6 +135,10 @@ export default class TabSessionManager {
}); });
} }
public setVirtualMode = (virtualMode: boolean) => {
this.pagesManager.setVirtualMode(virtualMode);
};
setSession = (session: any) => { setSession = (session: any) => {
this.session = session; this.session = session;
}; };

View file

@ -21,15 +21,10 @@ export default class WebPlayer extends Player {
inspectorMode: false, inspectorMode: false,
mobsFetched: false, mobsFetched: false,
}; };
private inspectorController: InspectorController; private inspectorController: InspectorController;
protected screen: Screen; protected screen: Screen;
protected readonly messageManager: MessageManager; protected readonly messageManager: MessageManager;
protected readonly messageLoader: MessageLoader; protected readonly messageLoader: MessageLoader;
private targetMarker: TargetMarker; private targetMarker: TargetMarker;
constructor( constructor(
@ -104,6 +99,10 @@ export default class WebPlayer extends Player {
window.__OPENREPLAY_DEV_TOOLS__.player = this; window.__OPENREPLAY_DEV_TOOLS__.player = this;
} }
enableVMode = () => {
this.messageManager.setVirtualMode(true);
}
preloadFirstFile(data: Uint8Array, fileKey?: string) { preloadFirstFile(data: Uint8Array, fileKey?: string) {
void this.messageLoader.preloadFirstFile(data, fileKey); void this.messageLoader.preloadFirstFile(data, fileKey);
} }

View file

@ -44,47 +44,33 @@ const ATTR_NAME_REGEXP = /([^\t\n\f \/>"'=]+)/;
export default class DOMManager extends ListWalker<Message> { export default class DOMManager extends ListWalker<Message> {
private readonly vTexts: Map<number, VText> = new Map(); // map vs object here? private readonly vTexts: Map<number, VText> = new Map(); // map vs object here?
private readonly vElements: Map<number, VElement> = new Map(); private readonly vElements: Map<number, VElement> = new Map();
private readonly olVRoots: Map<number, OnloadVRoot> = new Map(); private readonly olVRoots: Map<number, OnloadVRoot> = new Map();
/** required to keep track of iframes, frameId : vnodeId */ /** required to keep track of iframes, frameId : vnodeId */
private readonly iframeRoots: Record<number, number> = {}; private readonly iframeRoots: Record<number, number> = {};
private shadowRootParentMap: Map<number, number> = new Map(); private shadowRootParentMap: Map<number, number> = new Map();
/** Constructed StyleSheets https://developer.mozilla.org/en-US/docs/Web/API/Document/adoptedStyleSheets /** Constructed StyleSheets https://developer.mozilla.org/en-US/docs/Web/API/Document/adoptedStyleSheets
* as well as <style> tag owned StyleSheets * as well as <style> tag owned StyleSheets
*/ */
private olStyleSheets: Map<number, OnloadStyleSheet> = new Map(); private olStyleSheets: Map<number, OnloadStyleSheet> = new Map();
/** @depreacted since tracker 4.0.2 Mapping by nodeID */ /** @depreacted since tracker 4.0.2 Mapping by nodeID */
private olStyleSheetsDeprecated: Map<number, OnloadStyleSheet> = new Map(); private olStyleSheetsDeprecated: Map<number, OnloadStyleSheet> = new Map();
private upperBodyId: number = -1; private upperBodyId: number = -1;
private nodeScrollManagers: Map<number, ListWalker<SetNodeScroll>> = private nodeScrollManagers: Map<number, ListWalker<SetNodeScroll>> =
new Map(); new Map();
private stylesManager: StylesManager; private stylesManager: StylesManager;
private focusManager: FocusManager = new FocusManager(this.vElements); private focusManager: FocusManager = new FocusManager(this.vElements);
private selectionManager: SelectionManager; private selectionManager: SelectionManager;
private readonly screen: Screen; private readonly screen: Screen;
private readonly isMobile: boolean; private readonly isMobile: boolean;
private readonly stringDict: Record<number, string>; private readonly stringDict: Record<number, string>;
private readonly globalDict: { private readonly globalDict: {
get: (key: string) => string | undefined; get: (key: string) => string | undefined;
all: () => Record<string, string>; all: () => Record<string, string>;
}; };
public readonly time: number; public readonly time: number;
private virtualMode: boolean = false;
private showVModeBadge?: () => void;
constructor(params: { constructor(params: {
screen: Screen; screen: Screen;
@ -96,6 +82,8 @@ export default class DOMManager extends ListWalker<Message> {
get: (key: string) => string | undefined; get: (key: string) => string | undefined;
all: () => Record<string, string>; all: () => Record<string, string>;
}; };
virtualMode?: boolean;
showVModeBadge?: () => void;
}) { }) {
super(); super();
this.screen = params.screen; this.screen = params.screen;
@ -105,6 +93,8 @@ export default class DOMManager extends ListWalker<Message> {
this.globalDict = params.globalDict; this.globalDict = params.globalDict;
this.selectionManager = new SelectionManager(this.vElements, params.screen); this.selectionManager = new SelectionManager(this.vElements, params.screen);
this.stylesManager = new StylesManager(params.screen, params.setCssLoading); this.stylesManager = new StylesManager(params.screen, params.setCssLoading);
this.virtualMode = params.virtualMode || false;
this.showVModeBadge = params.showVModeBadge;
setupWindowLogging(this.vTexts, this.vElements, this.olVRoots); setupWindowLogging(this.vTexts, this.vElements, this.olVRoots);
} }
@ -450,14 +440,18 @@ export default class DOMManager extends ListWalker<Message> {
return; return;
} }
// shadow DOM for a custom element + SALESFORCE (<slot>) // shadow DOM for a custom element + SALESFORCE (<slot>)
const isCustomElement = vElem.tagName.includes('-') || vElem.tagName === 'SLOT'; const isCustomElement =
const isNotActualIframe = !["IFRAME", "FRAME"].includes(vElem.tagName.toUpperCase()); vElem.tagName.includes('-') || vElem.tagName === 'SLOT';
const isLikelyShadowRoot = isCustomElement && isNotActualIframe; const hasSlots = vElem.tagName === 'SLOT';
if (isLikelyShadowRoot) { if (isCustomElement) {
if (this.virtualMode) {
// Store the mapping but don't create the actual shadow root // Store the mapping but don't create the actual shadow root
this.shadowRootParentMap.set(msg.id, msg.frameID); this.shadowRootParentMap.set(msg.id, msg.frameID);
return; return;
} else if (hasSlots) {
this.showVModeBadge?.();
}
} }
// Real iframes // Real iframes
@ -473,7 +467,11 @@ export default class DOMManager extends ListWalker<Message> {
case MType.AdoptedSsInsertRule: { case MType.AdoptedSsInsertRule: {
const styleSheet = this.olStyleSheets.get(msg.sheetID); const styleSheet = this.olStyleSheets.get(msg.sheetID);
if (!styleSheet) { if (!styleSheet) {
logger.warn('No stylesheet was created for ', msg, this.olStyleSheets); logger.warn(
'No stylesheet was created for ',
msg,
this.olStyleSheets,
);
return; return;
} }
insertRule(styleSheet, msg); insertRule(styleSheet, msg);

View file

@ -22,6 +22,7 @@ export default class PagesManager extends ListWalker<DOMManager> {
private screen: Screen, private screen: Screen,
private isMobile: boolean, private isMobile: boolean,
private setCssLoading: (flag: boolean) => void, private setCssLoading: (flag: boolean) => void,
private showVModeBadge: () => void,
) { ) {
super(); super();
} }
@ -30,6 +31,10 @@ export default class PagesManager extends ListWalker<DOMManager> {
Assumed that messages added in a correct time sequence. Assumed that messages added in a correct time sequence.
*/ */
falseOrder = false; falseOrder = false;
virtualMode = false;
setVirtualMode = (virtualMode: boolean) => {
this.virtualMode = virtualMode;
};
appendMessage(m: Message): void { appendMessage(m: Message): void {
if ([MType.StringDict, MType.StringDictGlobal].includes(m.tp)) { if ([MType.StringDict, MType.StringDictGlobal].includes(m.tp)) {
@ -62,6 +67,8 @@ export default class PagesManager extends ListWalker<DOMManager> {
get: (key: string) => this.globalDictionary.get(key), get: (key: string) => this.globalDictionary.get(key),
all: () => Object.fromEntries(this.globalDictionary), all: () => Object.fromEntries(this.globalDictionary),
}, },
virtualMode: this.virtualMode,
showVModeBadge: this.showVModeBadge,
}), }),
); );
this.falseOrder = false; this.falseOrder = false;