diff --git a/frontend/app/player/web/managers/DOM/DOMManager.ts b/frontend/app/player/web/managers/DOM/DOMManager.ts index 76e194916..27a6454ca 100644 --- a/frontend/app/player/web/managers/DOM/DOMManager.ts +++ b/frontend/app/player/web/managers/DOM/DOMManager.ts @@ -449,9 +449,8 @@ export default class DOMManager extends ListWalker { logger.error('CreateIFrameDocument: Node not found', msg); return; } - - // shadow DOM for a custom element - const isCustomElement = vElem.tagName.includes('-'); + // shadow DOM for a custom element + SALESFORCE () + const isCustomElement = vElem.tagName.includes('-') || vElem.tagName === 'SLOT'; const isNotActualIframe = !["IFRAME", "FRAME"].includes(vElem.tagName.toUpperCase()); const isLikelyShadowRoot = isCustomElement && isNotActualIframe; diff --git a/tracker/tracker/src/main/app/observer/cssInliner.ts b/tracker/tracker/src/main/app/observer/cssInliner.ts index df0133dba..b0aeb034f 100644 --- a/tracker/tracker/src/main/app/observer/cssInliner.ts +++ b/tracker/tracker/src/main/app/observer/cssInliner.ts @@ -1,3 +1,5 @@ +let fakeIdHolder = 1000000 * 99; + export function inlineRemoteCss( node: HTMLLinkElement, id: number, @@ -5,83 +7,289 @@ export function inlineRemoteCss( getNextID: () => number, insertRule: (id: number, cssText: string, index: number, baseHref: string) => any[], addOwner: (sheetId: number, ownerId: number) => any[], + forceFetch?: boolean, + sendPlain?: boolean, + onPlain?: (cssText: string, id: number) => void, ) { + const sheetId = sendPlain ? null : getNextID(); + if (!sendPlain) { + addOwner(sheetId!, id); + } + const sheet = node.sheet; - const sheetId = getNextID() - addOwner(sheetId, id); - const processRules = (rules: CSSRuleList) => { - if (rules.length) { - setTimeout(() => { - for (let i = 0; i < rules.length; i++) { - const rule = rules[i]; - insertRule(sheetId, rule.cssText, i, baseHref); - } - }, 0) + if (sheet && !forceFetch) { + try { + const cssText = stringifyStylesheet(sheet); + + if (cssText) { + processCssText(cssText); + return; + } + } catch (e) { + // console.warn("Could not stringify sheet, falling back to fetch:", e); } - }; + } - const processCssText = (cssText: string) => { + // Fall back to fetching if we couldn't get or stringify the sheet + if (node.href) { + fetch(node.href) + .then(response => { + if (!response.ok) { + throw new Error(`response status ${response.status}`); + } + return response.text(); + }) + .then(cssText => { + if (sendPlain && onPlain) { + onPlain(cssText, fakeIdHolder++); + } else { + processCssText(cssText); + } + }) + .catch(error => { + console.error(`OpenReplay: Failed to fetch CSS from ${node.href}:`, error); + }); + } + + function processCssText(cssText: string) { + // Remove comments cssText = cssText.replace(/\/\*[\s\S]*?\*\//g, ''); - const ruleTexts: string[] = []; - let depth = 0; + // Parse and process the CSS text to extract rules + const ruleTexts = parseCSS(cssText); + + for (let i = 0; i < ruleTexts.length; i++) { + insertRule(sheetId!, ruleTexts[i], i, baseHref); + } + } + + + function parseCSS(cssText: string): string[] { + const rules: string[] = []; + let inComment = false; + let inString = false; + let stringChar = ''; + let braceLevel = 0; let currentRule = ''; for (let i = 0; i < cssText.length; i++) { const char = cssText[i]; + const nextChar = cssText[i + 1] || ''; - if (char === '{') { - depth++; - } else if (char === '}') { - depth--; - if (depth === 0) { - currentRule += char; - ruleTexts.push(currentRule.trim()); - currentRule = ''; - continue; - } + // comments + if (!inString && char === '/' && nextChar === '*') { + inComment = true; + i++; // Skip the next character + continue; } + if (inComment) { + if (char === '*' && nextChar === '/') { + inComment = false; + i++; // Skip the next character + } + continue; + } + + + if (!inString && (char === '"' || char === "'")) { + inString = true; + stringChar = char; + currentRule += char; + continue; + } + + if (inString) { + currentRule += char; + if (char === stringChar && cssText[i - 1] !== '\\') { + inString = false; + } + continue; + } + + currentRule += char; - } - for (let i = 0; i < ruleTexts.length; i++) { - const ruleText = ruleTexts[i]; - insertRule(sheetId, ruleText, i, baseHref); - } - }; + if (char === '{') { + braceLevel++; + } else if (char === '}') { + braceLevel--; - if (sheet) { - try { - const rules = sheet.cssRules; - processRules(rules); - } catch (e) { - const href = node.href; - if (href) { - fetch(href) - .then(response => { - if (!response.ok) { - throw new Error(`Failed to fetch CSS: ${response.status}`); - } - return response.text(); - }) - .then(cssText => { - processCssText(cssText); - }) - .catch(error => { - console.error(`Failed to fetch or process CSS from ${href}:`, error); - }); + if (braceLevel === 0) { + // End of a top-level rule + rules.push(currentRule.trim()); + currentRule = ''; + } } } - } else if (node.href) { - fetch(node.href) - .then(response => response.text()) - .then(cssText => { - processCssText(cssText); - }) - .catch(error => { - console.error(`Failed to fetch CSS from ${node.href}:`, error); - }); + + // Handle any remaining text (should be rare) + if (currentRule.trim()) { + rules.push(currentRule.trim()); + } + + return rules; } -} \ No newline at end of file + + + function stringifyStylesheet(s: CSSStyleSheet): string | null { + try { + const rules = s.rules || s.cssRules; + if (!rules) { + return null; + } + + let sheetHref = s.href; + if (!sheetHref && s.ownerNode && (s.ownerNode as HTMLElement).ownerDocument) { + // an inline