(async () => { const existing = document.getElementById('live-overlay-root') if (existing) { existing.remove() if (window.__liveOverlayCleanup) { try { window.__liveOverlayCleanup() } catch (err) { console.warn('LiveOverlay cleanup error', err) } delete window.__liveOverlayCleanup } return } const DEFAULTS = { overlayUrl: '', opacity: 0.6, invertEnabled: false, locked: false, clickThrough: false, gridEnabled: false, position: { x: 48, y: 48 }, size: { width: 1200, height: 800 }, } const iconNames = [ 'eye', 'eye-slash', 'lock-closed', 'lock-open', 'hand', 'adjustments-horizontal', 'moon', 'arrows-pointing-out', 'arrows-pointing-in', 'arrows-up-down-left-right', 'squares-2x2', 'arrow-path', 'question-mark-circle', ] const iconCache = {} async function loadIcon(name) { if (iconCache[name]) return iconCache[name] const res = await fetch(chrome.runtime.getURL(`icons/ui/${name}.svg`)) const text = await res.text() iconCache[name] = text return text } await Promise.all(iconNames.map(loadIcon)) const stored = await new Promise((resolve) => { chrome.storage.local.get(DEFAULTS, (value) => { if (chrome.runtime.lastError) { console.warn('LiveOverlay storage get error', chrome.runtime.lastError) resolve({ ...DEFAULTS }) return } resolve(value) }) }) const state = { ...DEFAULTS, ...stored, position: { ...DEFAULTS.position, ...(stored.position || {}) }, size: { ...DEFAULTS.size, ...(stored.size || {}) }, } if (!state.overlayUrl) { state.overlayUrl = window.location.href } let overlayVisible = true let fullscreen = false let savedState = { size: { ...state.size }, position: { ...state.position } } let currentFrameUrl = state.overlayUrl const pendingSave = {} let saveTimer = null function scheduleSave(partial) { Object.assign(pendingSave, partial) if (saveTimer) { clearTimeout(saveTimer) } saveTimer = setTimeout(() => { chrome.storage.local.set(pendingSave, () => { if (chrome.runtime.lastError) { console.warn('LiveOverlay storage set error', chrome.runtime.lastError) } }) for (const key of Object.keys(pendingSave)) { delete pendingSave[key] } saveTimer = null }, 200) } function setButtonIcon(button, iconName) { button.innerHTML = iconCache[iconName] || '' const svg = button.querySelector('svg') if (svg) { svg.setAttribute('aria-hidden', 'true') svg.setAttribute('focusable', 'false') svg.setAttribute('width', '20') svg.setAttribute('height', '20') svg.setAttribute('stroke', 'currentColor') if (!svg.getAttribute('fill')) { svg.setAttribute('fill', 'none') } } } function createButton(label, options = {}) { const { toggle = true } = options const button = document.createElement('button') button.type = 'button' button.className = 'lo-btn lo-control' button.setAttribute('aria-label', label) button.title = label if (toggle) { button.setAttribute('aria-pressed', 'false') } return button } function applyFrameOptions() { iframe.style.opacity = String(state.opacity) iframe.style.filter = state.invertEnabled ? 'invert(1)' : 'none' iframe.style.pointerEvents = state.clickThrough ? 'none' : 'auto' iframe.setAttribute('aria-hidden', overlayVisible ? 'false' : 'true') if (state.overlayUrl !== currentFrameUrl) { currentFrameUrl = state.overlayUrl iframe.src = state.overlayUrl } root.classList.toggle('lo-grid-on', !!state.gridEnabled) root.classList.toggle('lo-click-through', !!state.clickThrough) root.classList.toggle('lo-locked', !!state.locked) opacityValue.textContent = Math.round(state.opacity * 100) + '%' opacitySlider.value = String(Math.round(state.opacity * 100)) opacitySlider.setAttribute('aria-valuenow', String(Math.round(state.opacity * 100))) urlInput.value = state.overlayUrl updateToggleIcons() } function applySizeAndPosition() { const maxWidth = Math.max(document.documentElement.clientWidth, window.innerWidth) const maxHeight = Math.max(document.documentElement.clientHeight, window.innerHeight) if (!fullscreen) { state.size.width = Math.min(Math.max(state.size.width, 320), maxWidth) state.size.height = Math.min(Math.max(state.size.height, 240), maxHeight) const clampedWidth = Math.min(state.size.width, maxWidth) const clampedHeight = Math.min(state.size.height, maxHeight) state.position.x = Math.min(Math.max(state.position.x, 0), Math.max(0, maxWidth - clampedWidth)) state.position.y = Math.min(Math.max(state.position.y, 0), Math.max(0, maxHeight - clampedHeight)) } root.style.width = fullscreen ? '100vw' : `${state.size.width}px` root.style.height = fullscreen ? '100vh' : `${state.size.height}px` root.style.left = fullscreen ? '0' : `${state.position.x}px` root.style.top = fullscreen ? '0' : `${state.position.y}px` root.classList.toggle('lo-fullscreen', fullscreen) } function adjustPosition(deltaX, deltaY) { if (state.locked || fullscreen) return state.position.x += deltaX state.position.y += deltaY applySizeAndPosition() scheduleSave({ position: { ...state.position } }) if (!fullscreen) { savedState.position = { ...state.position } } } function updateToggleIcons() { lockButton.classList.toggle('is-active', state.locked) lockButton.setAttribute('aria-pressed', String(state.locked)) setButtonIcon(lockButton, state.locked ? 'lock-closed' : 'lock-open') clickThroughButton.classList.toggle('is-active', state.clickThrough) clickThroughButton.setAttribute('aria-pressed', String(state.clickThrough)) invertButton.classList.toggle('is-active', state.invertEnabled) invertButton.setAttribute('aria-pressed', String(state.invertEnabled)) gridButton.classList.toggle('is-active', state.gridEnabled) gridButton.setAttribute('aria-pressed', String(state.gridEnabled)) toggleVisibilityButton.classList.toggle('is-active', overlayVisible) toggleVisibilityButton.setAttribute('aria-pressed', String(overlayVisible)) setButtonIcon(toggleVisibilityButton, overlayVisible ? 'eye' : 'eye-slash') resizeButton.classList.toggle('is-active', fullscreen) resizeButton.setAttribute('aria-pressed', String(fullscreen)) setButtonIcon(resizeButton, fullscreen ? 'arrows-pointing-in' : 'arrows-pointing-out') } const root = document.createElement('div') root.id = 'live-overlay-root' root.setAttribute('role', 'region') root.setAttribute('aria-label', 'Live overlay container') const style = document.createElement('style') style.textContent = ` #live-overlay-root { position: fixed; top: 48px; left: 48px; display: flex; flex-direction: column; max-width: 100vw; max-height: 100vh; background: rgba(15, 23, 42, 0.85); color: #f8fafc; border-radius: 12px; box-shadow: 0 20px 45px rgba(15, 23, 42, 0.35); z-index: 2147483647; border: 1px solid rgba(148, 163, 184, 0.45); overflow: hidden; backdrop-filter: blur(4px); } #live-overlay-root.lo-click-through iframe { pointer-events: none; } #live-overlay-root.lo-locked .lo-toolbar { cursor: default; } #live-overlay-root.lo-locked .lo-title { cursor: default; opacity: 0.7; } #live-overlay-root .lo-toolbar { display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: rgba(15, 23, 42, 0.92); border-bottom: 1px solid rgba(148, 163, 184, 0.35); user-select: none; touch-action: none; position: relative; flex-wrap: wrap; row-gap: 6px; } #live-overlay-root .lo-title { font-size: 12px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; margin-right: 6px; cursor: move; display: inline-flex; align-items: center; gap: 6px; } #live-overlay-root .lo-toolbar button.lo-btn { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 8px; background: transparent; color: inherit; border: 1px solid transparent; transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease; cursor: pointer; padding: 0; } #live-overlay-root .lo-toolbar button.lo-btn:hover, #live-overlay-root .lo-toolbar button.lo-btn.is-active { background: rgba(96, 165, 250, 0.18); border-color: rgba(148, 197, 255, 0.4); } #live-overlay-root .lo-toolbar button.lo-btn:focus-visible { outline: 2px solid #22d3ee; outline-offset: 2px; } #live-overlay-root .lo-toolbar button.lo-btn svg { pointer-events: none; } #live-overlay-root .lo-slider { display: inline-flex; align-items: center; gap: 6px; margin-left: 6px; } #live-overlay-root .lo-slider input[type="range"] { width: 120px; accent-color: #6366f1; } #live-overlay-root .lo-slider span { font-size: 12px; min-width: 36px; text-align: right; } #live-overlay-root .lo-url { display: inline-flex; align-items: center; gap: 6px; flex: 1; min-width: 160px; } #live-overlay-root .lo-url input { flex: 1; min-width: 160px; padding: 6px 8px; border-radius: 8px; border: 1px solid rgba(148, 163, 184, 0.35); background: rgba(15, 23, 42, 0.65); color: inherit; font-size: 12px; } #live-overlay-root .lo-url input:focus-visible { outline: 2px solid #22d3ee; outline-offset: 2px; } #live-overlay-root .lo-frame { position: relative; flex: 1 1 auto; background: rgba(15, 23, 42, 0.85); min-height: 160px; } #live-overlay-root.lo-fullscreen { border-radius: 0; border: none; } #live-overlay-root.lo-fullscreen .lo-toolbar { border-radius: 0; } #live-overlay-root iframe { width: 100%; height: 100%; border: none; background: #0f172a; } #live-overlay-root .lo-grid { position: absolute; inset: 0; pointer-events: none; background-image: linear-gradient(to right, rgba(148, 163, 184, 0.3) 1px, transparent 1px), linear-gradient(to bottom, rgba(148, 163, 184, 0.3) 1px, transparent 1px); background-size: 48px 48px; opacity: 0; transition: opacity 0.2s ease; } #live-overlay-root.lo-grid-on .lo-grid { opacity: 1; } #live-overlay-root .lo-resize { position: absolute; width: 18px; height: 18px; right: 6px; bottom: 6px; border-right: 2px solid rgba(148, 197, 255, 0.8); border-bottom: 2px solid rgba(148, 197, 255, 0.8); border-radius: 2px; cursor: se-resize; pointer-events: auto; } #live-overlay-root.lo-locked .lo-resize { display: none; } #live-overlay-root .lo-nudge-panel { position: absolute; top: calc(100% + 6px); right: 10px; display: none; flex-direction: column; gap: 4px; background: rgba(15, 23, 42, 0.95); border: 1px solid rgba(148, 163, 184, 0.45); border-radius: 10px; padding: 8px; box-shadow: 0 12px 30px rgba(15, 23, 42, 0.35); z-index: 2; } #live-overlay-root .lo-nudge-panel.is-open { display: flex; } #live-overlay-root .lo-nudge-grid { display: grid; grid-template-columns: repeat(3, 28px); gap: 4px; justify-items: center; margin-top: 4px; } #live-overlay-root .lo-nudge-grid button { width: 28px; height: 28px; border-radius: 6px; background: rgba(96, 165, 250, 0.15); border: 1px solid transparent; color: inherit; cursor: pointer; font-size: 14px; display: inline-flex; align-items: center; justify-content: center; } #live-overlay-root .lo-nudge-grid span { width: 28px; height: 28px; display: block; } #live-overlay-root .lo-nudge-grid button:hover { background: rgba(96, 165, 250, 0.25); } #live-overlay-root .lo-nudge-grid button:focus-visible { outline: 2px solid #22d3ee; outline-offset: 1px; } #live-overlay-root .lo-nudge-wrap { position: relative; display: inline-flex; align-items: center; } ` document.head.appendChild(style) const toolbar = document.createElement('div') toolbar.className = 'lo-toolbar' toolbar.setAttribute('role', 'toolbar') toolbar.setAttribute('aria-label', 'Live overlay controls') const title = document.createElement('div') title.className = 'lo-title' title.textContent = 'Live Overlay' toolbar.appendChild(title) const toggleVisibilityButton = createButton('Toggle overlay visibility') setButtonIcon(toggleVisibilityButton, 'eye') const lockButton = createButton('Toggle overlay lock') const clickThroughButton = createButton('Toggle click-through mode') setButtonIcon(clickThroughButton, 'hand') const invertButton = createButton('Invert overlay colors') setButtonIcon(invertButton, 'moon') const resizeButton = createButton('Toggle overlay size') setButtonIcon(resizeButton, 'arrows-pointing-out') const nudgeWrap = document.createElement('div') nudgeWrap.className = 'lo-nudge-wrap' const nudgeButton = createButton('Open nudge controls') setButtonIcon(nudgeButton, 'arrows-up-down-left-right') const gridButton = createButton('Toggle alignment grid') setButtonIcon(gridButton, 'squares-2x2') const resetButton = createButton('Reset overlay settings', { toggle: false }) setButtonIcon(resetButton, 'arrow-path') const helpButton = createButton('Help and usage tips', { toggle: false }) setButtonIcon(helpButton, 'question-mark-circle') const sliderWrap = document.createElement('label') sliderWrap.className = 'lo-slider' sliderWrap.setAttribute('aria-label', 'Overlay opacity control') sliderWrap.title = 'Adjust overlay opacity' const sliderIcon = document.createElement('span') sliderIcon.className = 'lo-slider-icon' sliderIcon.innerHTML = iconCache['adjustments-horizontal'] const sliderIconSvg = sliderIcon.querySelector('svg') if (sliderIconSvg) { sliderIconSvg.setAttribute('aria-hidden', 'true') sliderIconSvg.setAttribute('focusable', 'false') sliderIconSvg.setAttribute('width', '20') sliderIconSvg.setAttribute('height', '20') sliderIconSvg.setAttribute('stroke', 'currentColor') if (!sliderIconSvg.getAttribute('fill')) { sliderIconSvg.setAttribute('fill', 'none') } } const opacitySlider = document.createElement('input') opacitySlider.className = 'lo-control' opacitySlider.type = 'range' opacitySlider.min = '0' opacitySlider.max = '100' opacitySlider.step = '1' opacitySlider.value = String(Math.round(state.opacity * 100)) opacitySlider.setAttribute('aria-valuemin', '0') opacitySlider.setAttribute('aria-valuemax', '100') opacitySlider.setAttribute('aria-valuenow', String(Math.round(state.opacity * 100))) opacitySlider.setAttribute('aria-label', 'Overlay opacity') const opacityValue = document.createElement('span') opacityValue.textContent = Math.round(state.opacity * 100) + '%' sliderWrap.appendChild(sliderIcon) sliderWrap.appendChild(opacitySlider) sliderWrap.appendChild(opacityValue) const urlWrap = document.createElement('div') urlWrap.className = 'lo-url' const urlInput = document.createElement('input') urlInput.type = 'url' urlInput.placeholder = 'https://overlay.example.com' urlInput.value = state.overlayUrl urlInput.setAttribute('aria-label', 'Overlay URL') urlWrap.appendChild(urlInput) toolbar.appendChild(toggleVisibilityButton) toolbar.appendChild(lockButton) toolbar.appendChild(clickThroughButton) toolbar.appendChild(invertButton) toolbar.appendChild(resizeButton) toolbar.appendChild(nudgeWrap) toolbar.appendChild(gridButton) toolbar.appendChild(resetButton) toolbar.appendChild(helpButton) toolbar.appendChild(sliderWrap) toolbar.appendChild(urlWrap) const frameWrap = document.createElement('div') frameWrap.className = 'lo-frame' const iframe = document.createElement('iframe') iframe.src = currentFrameUrl iframe.title = 'Live overlay frame' iframe.allow = 'clipboard-read; clipboard-write' iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-pointer-lock') frameWrap.appendChild(iframe) const gridOverlay = document.createElement('div') gridOverlay.className = 'lo-grid' frameWrap.appendChild(gridOverlay) const resizeHandle = document.createElement('div') resizeHandle.className = 'lo-resize' frameWrap.appendChild(resizeHandle) const nudgePanel = document.createElement('div') nudgePanel.className = 'lo-nudge-panel' const nudgeTitle = document.createElement('span') nudgeTitle.textContent = 'Nudge overlay' nudgeTitle.style.fontSize = '11px' nudgeTitle.style.opacity = '0.8' nudgePanel.appendChild(nudgeTitle) const nudgeGrid = document.createElement('div') nudgeGrid.className = 'lo-nudge-grid' const nudgeButtons = [ { label: '', empty: true }, { label: '↑', dx: 0, dy: -1, aria: 'Nudge up 1px' }, { label: '', empty: true }, { label: '←', dx: -1, dy: 0, aria: 'Nudge left 1px' }, { label: 'Reset', dx: 0, dy: 0, reset: true, aria: 'Reset overlay position' }, { label: '→', dx: 1, dy: 0, aria: 'Nudge right 1px' }, { label: '', empty: true }, { label: '↓', dx: 0, dy: 1, aria: 'Nudge down 1px' }, { label: '', empty: true }, ] nudgeButtons.forEach(({ label, dx, dy, reset, aria, empty }) => { if (empty) { const spacer = document.createElement('span') nudgeGrid.appendChild(spacer) return } const btn = document.createElement('button') btn.type = 'button' btn.textContent = label btn.setAttribute('aria-label', aria || label) btn.addEventListener('click', () => { if (reset) { state.position = { ...DEFAULTS.position } applySizeAndPosition() scheduleSave({ position: { ...state.position } }) return } adjustPosition(dx, dy) }) nudgeGrid.appendChild(btn) }) nudgePanel.appendChild(nudgeGrid) const nudgeHint = document.createElement('span') nudgeHint.textContent = 'Hold Shift for 10px steps' nudgeHint.style.fontSize = '10px' nudgeHint.style.opacity = '0.65' nudgeHint.style.textAlign = 'center' nudgePanel.appendChild(nudgeHint) nudgeWrap.appendChild(nudgeButton) nudgeWrap.appendChild(nudgePanel) root.appendChild(toolbar) root.appendChild(frameWrap) document.body.appendChild(root) applySizeAndPosition() applyFrameOptions() updateToggleIcons() let dragActive = false let dragStart = { x: 0, y: 0 } let origin = { x: 0, y: 0 } function handlePointerDown(event) { if (state.locked || fullscreen) return if (event.button !== 0) return if (event.target.closest('.lo-control')) return dragActive = true dragStart = { x: event.clientX, y: event.clientY } origin = { ...state.position } title.setPointerCapture(event.pointerId) event.preventDefault() } function handlePointerMove(event) { if (!dragActive) return const deltaX = event.clientX - dragStart.x const deltaY = event.clientY - dragStart.y state.position.x = origin.x + deltaX state.position.y = origin.y + deltaY applySizeAndPosition() } function handlePointerUp(event) { if (!dragActive) return dragActive = false title.releasePointerCapture(event.pointerId) scheduleSave({ position: { ...state.position } }) } title.addEventListener('pointerdown', handlePointerDown) title.addEventListener('pointermove', handlePointerMove) title.addEventListener('pointerup', handlePointerUp) title.addEventListener('pointercancel', handlePointerUp) let resizing = false let resizeStart = { x: 0, y: 0 } let sizeOrigin = { width: 0, height: 0 } resizeHandle.addEventListener('pointerdown', (event) => { if (state.locked || fullscreen) return resizing = true resizeStart = { x: event.clientX, y: event.clientY } sizeOrigin = { ...state.size } resizeHandle.setPointerCapture(event.pointerId) event.preventDefault() }) resizeHandle.addEventListener('pointermove', (event) => { if (!resizing) return const deltaX = event.clientX - resizeStart.x const deltaY = event.clientY - resizeStart.y state.size.width = sizeOrigin.width + deltaX state.size.height = sizeOrigin.height + deltaY applySizeAndPosition() }) function finishResize(event) { if (!resizing) return resizing = false resizeHandle.releasePointerCapture(event.pointerId) scheduleSave({ size: { ...state.size } }) if (!fullscreen) { savedState.size = { ...state.size } } } resizeHandle.addEventListener('pointerup', finishResize) resizeHandle.addEventListener('pointercancel', finishResize) toggleVisibilityButton.addEventListener('click', () => { overlayVisible = !overlayVisible frameWrap.style.display = overlayVisible ? 'block' : 'none' iframe.setAttribute('aria-hidden', overlayVisible ? 'false' : 'true') if (!overlayVisible) { nudgePanel.classList.remove('is-open') nudgeButton.classList.remove('is-active') nudgeButton.setAttribute('aria-pressed', 'false') } updateToggleIcons() }) lockButton.addEventListener('click', () => { state.locked = !state.locked applyFrameOptions() if (state.locked) { nudgePanel.classList.remove('is-open') nudgeButton.classList.remove('is-active') nudgeButton.setAttribute('aria-pressed', 'false') } scheduleSave({ locked: state.locked }) }) clickThroughButton.addEventListener('click', () => { state.clickThrough = !state.clickThrough applyFrameOptions() scheduleSave({ clickThrough: state.clickThrough }) }) invertButton.addEventListener('click', () => { state.invertEnabled = !state.invertEnabled applyFrameOptions() scheduleSave({ invertEnabled: state.invertEnabled }) }) resizeButton.addEventListener('click', () => { fullscreen = !fullscreen if (fullscreen) { savedSize = { size: { ...state.size }, position: { ...state.position } } state.position = { x: 0, y: 0 } } else { state.size = { ...savedSize.size } state.position = { ...savedSize.position } } applySizeAndPosition() updateToggleIcons() scheduleSave({ size: { ...state.size }, position: { ...state.position } }) if (!fullscreen) { savedState = { size: { ...state.size }, position: { ...state.position } } } }) nudgeButton.addEventListener('click', () => { if (state.locked) return const isOpen = !nudgePanel.classList.contains('is-open') nudgePanel.classList.toggle('is-open', isOpen) nudgeButton.classList.toggle('is-active', isOpen) nudgeButton.setAttribute('aria-pressed', String(isOpen)) }) gridButton.addEventListener('click', () => { state.gridEnabled = !state.gridEnabled applyFrameOptions() scheduleSave({ gridEnabled: state.gridEnabled }) }) resetButton.addEventListener('click', () => { Object.assign(state, JSON.parse(JSON.stringify(DEFAULTS))) state.overlayUrl = window.location.href currentFrameUrl = state.overlayUrl savedState = { size: { ...state.size }, position: { ...state.position } } overlayVisible = true fullscreen = false frameWrap.style.display = 'block' nudgePanel.classList.remove('is-open') nudgeButton.classList.remove('is-active') nudgeButton.setAttribute('aria-pressed', 'false') applySizeAndPosition() applyFrameOptions() updateToggleIcons() const resetPayload = JSON.parse(JSON.stringify({ ...DEFAULTS, overlayUrl: state.overlayUrl, position: { ...state.position }, size: { ...state.size }, })) scheduleSave(resetPayload) }) helpButton.addEventListener('click', () => { const message = [ 'Live Overlay tips:', '• Drag the title bar to reposition.', '• Use the resize corner or the expand button for full screen.', '• Toggle click-through mode to interact with the page below.', '• Settings persist automatically via chrome.storage.local.' ].join('\n') alert(message) }) opacitySlider.addEventListener('input', () => { const value = Number(opacitySlider.value) / 100 state.opacity = Math.min(Math.max(value, 0), 1) opacitySlider.setAttribute('aria-valuenow', String(Math.round(state.opacity * 100))) applyFrameOptions() }) opacitySlider.addEventListener('change', () => { scheduleSave({ opacity: state.opacity }) }) urlInput.addEventListener('change', () => { let url = urlInput.value.trim() if (url && !/^https?:\/\//i.test(url)) { url = `https://${url}` urlInput.value = url } state.overlayUrl = url || window.location.href applyFrameOptions() scheduleSave({ overlayUrl: state.overlayUrl }) }) urlInput.addEventListener('keydown', (event) => { if (event.key === 'Enter') { event.preventDefault() urlInput.dispatchEvent(new Event('change')) urlInput.blur() } }) function handleKeydown(event) { if (state.locked || fullscreen) return const step = event.shiftKey ? 10 : 1 switch (event.key) { case 'ArrowUp': adjustPosition(0, -step) event.preventDefault() break case 'ArrowDown': adjustPosition(0, step) event.preventDefault() break case 'ArrowLeft': adjustPosition(-step, 0) event.preventDefault() break case 'ArrowRight': adjustPosition(step, 0) event.preventDefault() break default: break } } window.addEventListener('keydown', handleKeydown, true) window.__liveOverlayCleanup = () => { window.removeEventListener('keydown', handleKeydown, true) if (saveTimer) { clearTimeout(saveTimer) saveTimer = null if (Object.keys(pendingSave).length > 0) { chrome.storage.local.set(pendingSave, () => { if (chrome.runtime.lastError) { console.warn('LiveOverlay storage set error', chrome.runtime.lastError) } }) for (const key of Object.keys(pendingSave)) { delete pendingSave[key] } } } if (style.isConnected) { style.remove() } } })()