To make a floating chat window on fullscreen
// ==UserScript== // @name YouTube: Floating Chat Window on Fullscreen // @namespace UserScript // @version 0.5.5 // @license MIT License // @author CY Fung // @match https://www.youtube.com/* // @exclude /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/ // @require https://cdn.jsdelivr.net/gh/cyfung1031/userscript-supports@5d83d154956057bdde19e24f95b332cb9a78fcda/library/default-trusted-type-policy.js // @require https://cdn.jsdelivr.net/gh/cyfung1031/userscript-supports@8fac46500c5a916e6ed21149f6c25f8d1c56a6a3/library/ytZara.js // @run-at document-start // @grant none // @unwrap // @allFrames true // @inject-into page // @description To make a floating chat window on fullscreen // @description:ja フルスクリーンで浮動チャットウィンドウを表示する // @description:zh-TW 在全螢幕上顯示浮動聊天視窗 // @description:zh-CN 在全屏上显示浮动聊天窗口 // ==/UserScript== ((__CONTEXT__) => { let activeStyle = false; let _lastStyleText = null; let tvc = 0; const insp = o => o ? (o.polymerController || o.inst || o || 0) : (o || 0); let c27 = 0; let mouseDownActiveElement = null; /** @type {globalThis.PromiseConstructor} */ const Promise = (async () => { })().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve. const HTMLElement_ = HTMLElement; /** * @param {Element} elm * @param {string} selector * @returns {Element | null} * */ const qsOne = (elm, selector) => { return HTMLElement_.prototype.querySelector.call(elm, selector); } /** * @param {Element} elm * @param {string} selector * @returns {NodeListOf<Element>} * */ const qsAll = (elm, selector) => { return HTMLElement_.prototype.querySelectorAll.call(elm, selector); } const win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : (this instanceof Window ? this : window); const hkey_script = 'vdnvorrwsksy'; if (win[hkey_script]) throw new Error('Duplicated Userscript Calling'); // avoid duplicated scripting win[hkey_script] = true; document.addEventListener('click', function (evt) { if (!document.fullscreenElement) return; let byPass = false; if (Date.now() - c27 < 40) { byPass = true; } else { return; } if (evt.target && evt.target.id === 'chat' && evt.target.nodeName.toLowerCase() === 'ytd-live-chat-frame') byPass = false; else if (evt.target && evt.target.nodeName.toLowerCase() === 'iframe') byPass = false; if (byPass) { evt.stopPropagation(); evt.stopImmediatePropagation(); c27 = Date.now(); } c27 = 0; }, { capture: true, passive: false }); document.addEventListener('mousedown', function (evt) { if (!document.fullscreenElement) return; let byPass = false; const activeElement = document.activeElement || 0; mouseDownActiveElement = null; if (activeElement.nodeName === 'IFRAME') { if (activeElement.matches('[floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) iframe')) { byPass = true; mouseDownActiveElement = activeElement; } } if (evt.target && evt.target.id === 'chat' && evt.target.nodeName.toLowerCase() === 'ytd-live-chat-frame') byPass = false; else if (evt.target && evt.target.nodeName.toLowerCase() === 'iframe') byPass = false; if (byPass) { evt.stopPropagation(); evt.stopImmediatePropagation(); c27 = Date.now(); } else { mouseDownActiveElement = null; } c27 = 0; }, { capture: true, passive: false }); document.addEventListener('mouseup', function (evt) { if (!document.fullscreenElement) return; let mde = mouseDownActiveElement; mouseDownActiveElement = null; if (!mde) return; let byPass = false; const activeElement = mde || 0; if (activeElement.nodeName === 'IFRAME') { if (activeElement.matches('[floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) iframe')) { byPass = true; } } // if(Date.now()-c27 < 40 ) byPass = true; c27 = 0; if (evt.target && evt.target.id === 'chat' && evt.target.nodeName.toLowerCase() === 'ytd-live-chat-frame') byPass = false; else if (evt.target && evt.target.nodeName.toLowerCase() === 'iframe') byPass = false; if (byPass) { evt.stopPropagation(); evt.stopImmediatePropagation(); c27 = Date.now(); } }, { capture: true, passive: false }); const svgDefs = () => ` <svg version="1.1" xmlns="//www.w3.org/2000/svg" xmlns:xlink="//www.w3.org/1999/xlink" style="display:none;"> <defs> <filter id="stroke-text-svg-filter-03"> <feColorMatrix type="matrix" in="SourceGraphic" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" r###lt="white-text"/> <feMorphology in="white-text" r###lt="DILATED" operator="dilate" radius="2"></feMorphology> <feFlood flood-color="transparent" flood-opacity="1" r###lt="PINK" id="floodColor-03"></feFlood> <feComposite in="PINK" in2="DILATED" operator="in" r###lt="OUTLINE"></feComposite> <feMerge> <feMergeNode in="OUTLINE" /> <feMergeNode in="SourceGraphic" /> </feMerge> </filter> <filter id="stroke-text-svg-filter-04"> <feMorphology operator="dilate" radius="2"></feMorphology> <feComposite operator="xor" in="SourceGraphic"/> </filter> </defs> </svg> `.trim(); const createStyleTextForTopWin = () => ` [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) { position:fixed !important; top: var(--f3-top, 5px) !important; left: var(--f3-left, calc(60vw + 100px)) !important; height: var(--f3-h, 60vh) !important; width: var(--f3-w, 320px) !important; display:flex !important; flex-direction: column !important; padding: 4px; cursor: all-scroll; z-index:9999; box-sizing: border-box !important; margin:0 !important; opacity: var(--floating-window-opacity, 1.0) !important; background: transparent; background-color: rgba(0, 0, 0, 0.5); transition: background-color 300ms; } [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]):hover { background-color: rgba(0, 0, 0, 0.85); } .no-floating[floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) { top: -300vh !important; left: -300vh !important; } [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) #show-hide-button[class]{ flex-grow: 0; flex-shrink:0; position:static; cursor: all-scroll; } [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) #show-hide-button[class] *[class]{ cursor: inherit; } [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) iframe[class]{ flex-grow: 100; flex-shrink:0; height: 0; position:static; } html{ --fc7-handle-color: #0cb8da; } html[dark]{ --fc7-handle-color: #0c74e4; } :fullscreen .resize-handle { position: absolute !important; top: 0; left: 0; bottom: 0; background: transparent; right: 0; z-index: 999 !important; border-radius: inherit !important; box-sizing: border-box !important; pointer-events:none !important; visibility: collapse; border: 4px solid transparent; border-color: transparent; transition: border-color 300ms; } [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]):hover .resize-handle { visibility: visible; border-color: var(--fc7-handle-color); } [moving] { cursor: all-scroll; --pointer-events:initial; } [moving] body { --pointer-events:none; } [moving] ytd-live-chat-frame#chat{ --pointer-events:initial; } [moving] ytd-live-chat-frame#chat iframe { --pointer-events:none; } [moving="move"] ytd-live-chat-frame#chat { background-color: var(--yt-spec-general-background-a); } [moving="move"] ytd-live-chat-frame#chat iframe { visibility: collapse; } [moving] * { pointer-events:var(--pointer-events) !important; } [moving] *, [moving] [class] { user-select: none !important; } :fullscreen tyt-iframe-popup-btn { display: none !important; } [moving] tyt-iframe-popup-btn { display: none !important; } [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) #show-hide-button.ytd-live-chat-frame>ytd-toggle-button-renderer.ytd-live-chat-frame { background: transparent; } :fullscreen ytd-live-chat-frame#chat:not([collapsed]) { --chat-show-button-display: block; --chat-show-text-display: none; --chat-show-btn-text: 'ϞϞϞϞϞϞϞϞϞϞϞ'; } :fullscreen ytd-live-chat-frame#chat:not([collapsed]) [is-show-button] [role="text"] { display: var(--chat-show-text-display); } :fullscreen ytd-live-chat-frame#chat:not([collapsed]) [is-show-button] button::before { content: var(--chat-show-btn-text); } :fullscreen ytd-live-chat-frame#chat:not([collapsed]) [is-show-button][hidden] { display: var(--chat-show-button-display) !important; } `; const createStyleTextForIframe = () => ` .youtube-floating-chat-iframe yt-live-chat-docked-message#docked-messages.style-scope.yt-live-chat-item-list-renderer { margin-top:var(--fc7-top-banner-mt); transition: margin-top 180ms; } .youtube-floating-chat-iframe yt-live-chat-banner-manager#live-chat-banner.style-scope.yt-live-chat-item-list-renderer { margin-top:var(--fc7-top-banner-mt); transition: margin-top 180ms; } .youtube-floating-chat-iframe #action-panel.style-scope.yt-live-chat-renderer { position: fixed; top: 50%; transform: translateY(-50%); } .youtube-floating-chat-iframe yt-live-chat-header-renderer.style-scope.yt-live-chat-renderer { position: relative; z-index: 8; background: rgb(0,0,0); visibility: var(--fc7-panel-visibility); } .youtube-floating-chat-iframe #chat-messages.style-scope.yt-live-chat-renderer.iron-selected > #contents.style-scope.yt-live-chat-renderer { position: fixed; z-index: 4; top: 0; bottom: 0; left: 0; right: 0; } .youtube-floating-chat-iframe #right-arrow-container.yt-live-chat-ticker-renderer, .youtube-floating-chat-iframe #left-arrow-container.yt-live-chat-ticker-renderer { background: transparent; } .youtube-floating-chat-iframe yt-live-chat-renderer.yt-live-chat-app { --yt-live-chat-background-color: transparent; --yt-live-chat-action-panel-background-color: rgba(0, 0, 0, 0.08); --yt-live-chat-header-background-color: rgba(0, 0, 0, 0.18); --yt-spec-static-overlay-background-medium: rgba(0, 0, 0, 0.08); --yt-live-chat-banner-gradient-scrim: transparent; } .youtube-floating-chat-iframe{ --fc7-top-banner-mt: 0px; --fc7-banner-opacity: 0.86; --fc7-system-message-opacity: 0.66; --fc7-system-message-opacity2: 0.66; --fc7-panel-display: none; --fc7-panel-visibility: collapse; --fc7-panel-position: absolute; } .youtube-floating-chat-iframe:focus-within, html:focus-within, body:focus-within, yt-live-chat-app:focus-within, yt-live-chat-renderer.yt-live-chat-app:focus-within { --fc7-top-banner-mt: 56px; --fc7-banner-opacity: 1.0; --fc7-system-message-opacity: 1.0; --fc7-system-message-opacity2: 1.00; --fc7-panel-display: invalid; --fc7-panel-visibility: invalid; --fc7-panel-position: absolute; } .youtube-floating-chat-iframe yt-live-chat-app:hover { --fc7-top-banner-mt: 56px; --fc7-banner-opacity: 1.0; --fc7-system-message-opacity: 1.0; --fc7-system-message-opacity2: 1.00; --fc7-panel-display: invalid; --fc7-panel-visibility: invalid; --fc7-panel-position: absolute; } .youtube-floating-chat-iframe yt-live-chat-renderer.yt-live-chat-app #visible-banners > yt-live-chat-banner-renderer { opacity: var(--fc7-banner-opacity) !important; } .youtube-floating-chat-iframe yt-live-chat-renderer.yt-live-chat-app yt-live-chat-viewer-engagement-message-renderer { opacity: var(--fc7-system-message-opacity) !important; } .youtube-floating-chat-iframe yt-live-chat-app yt-live-chat-renderer.yt-live-chat-app yt-live-chat-message-input-renderer { visibility: var(--fc7-panel-visibility); position: var(--fc7-panel-position); transform: translateY(-100%); left: 0; right: 0; opacity: 1; background: rgba(0,0,0,0.86); } /* hide message with input panel hidden */ .youtube-floating-chat-iframe yt-live-chat-app > tp-yt-iron-dropdown.yt-live-chat-app yt-tooltip-renderer[slot="dropdown-content"][position-type="OPEN_POPUP_POSITION_TOP"].yt-live-chat-app { visibility: var(--fc7-panel-visibility); } [dark].youtube-floating-chat-iframe yt-live-chat-app ::-webkit-scrollbar-track, [dark].youtube-floating-chat-iframe yt-live-chat-kevlar-container ::-webkit-scrollbar-track { background-color: var(--ytd-searchbox-legacy-button-color); } .youtube-floating-chat-iframe yt-live-chat-app ::-webkit-scrollbar-track, .youtube-floating-chat-iframe yt-live-chat-kevlar-container ::-webkit-scrollbar-track { background-color: #fcfcfc; } [dark].youtube-floating-chat-iframe yt-live-chat-app ::-webkit-scrollbar-thumb, [dark].youtube-floating-chat-iframe yt-live-chat-kevlar-container ::-webkit-scrollbar-thumb{ background-color: var(--ytd-searchbox-legacy-button-color); border: 2px solid var(--ytd-searchbox-legacy-button-color); } .youtube-floating-chat-iframe yt-live-chat-renderer[has-action-panel-renderer] #action-panel.yt-live-chat-renderer { --yt-live-chat-action-panel-gradient-scrim: transparent; } .youtube-floating-chat-iframe yt-live-chat-renderer[has-action-panel-renderer] #action-panel.yt-live-chat-renderer yt-live-chat-action-panel-renderer { opacity: var(--fc7-system-message-opacity2) !important; } `; const { isIframe, isTopFrame } = (() => { let isIframe = false, isTopFrame = false; try { isIframe = window.document !== top.document } catch (e) { } try { isTopFrame = window.document === top.document } catch (e) { } return { isIframe, isTopFrame }; })(); if (isIframe ^ isTopFrame) { } else return; const addCSS = (createStyleText) => { let text = createStyleText(); let style = document.createElement('style'); style.id = 'rvZ0t'; style.textContent = text; document.head.appendChild(style); } const cleanContext = async (win) => { const waitFn = requestAnimationFrame; // shall have been binded to window try { let mx = 16; // MAX TRIAL const frameId = 'vanillajs-iframe-v1' let frame = document.getElementById(frameId); let removeIframeFn = null; if (!frame) { frame = document.createElement('iframe'); frame.id = 'vanillajs-iframe-v1'; frame.sandbox = 'allow-same-origin'; // script cannot be run inside iframe but API can be obtained from iframe let n = document.createElement('noscript'); // wrap into NOSCRPIT to avoid reflow (layouting) n.appendChild(frame); while (!document.documentElement && mx-- > 0) await new Promise(waitFn); // requestAnimationFrame here could get modified by YouTube engine const root = document.documentElement; root.appendChild(n); // throw error if root is null due to exceeding MAX TRIAL removeIframeFn = (setTimeout) => { const removeIframeOnDocumentReady = (e) => { e && win.removeEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false); win = null; setTimeout(() => { n.remove(); n = null; }, 200); } if (document.readyState !== 'loading') { removeIframeOnDocumentReady(); } else { win.addEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false); } } } while (!frame.contentWindow && mx-- > 0) await new Promise(waitFn); const fc = frame.contentWindow; if (!fc) throw "window is not found."; // throw error if root is null due to exceeding MAX TRIAL const { requestAnimationFrame, setTimeout } = fc; const res = { requestAnimationFrame, setTimeout }; for (let k in res) res[k] = res[k].bind(win); // necessary if (removeIframeFn) Promise.resolve(res.setTimeout).then(removeIframeFn); return res; } catch (e) { console.warn(e); return null; } }; isTopFrame && cleanContext(win).then(__CONTEXT__ => { if (!__CONTEXT__) return null; const { requestAnimationFrame } = __CONTEXT__; let chatWindowWR = null; let showHideButtonWR = null; let showButtonWR = null; /* globals WeakRef:false */ /** @type {(o: Object | null) => WeakRef | null} */ const mWeakRef = typeof WeakRef === 'function' ? (o => o ? new WeakRef(o) : null) : (o => o || null); // typeof InvalidVar == 'undefined' /** @type {(wr: Object | null) => Object | null} */ const kRef = (wr => (wr && wr.deref) ? wr.deref() : wr); let rafPromise = null; const getRafPromise = () => rafPromise || (rafPromise = new Promise(resolve => { requestAnimationFrame(hRes => { rafPromise = null; resolve(hRes); }); })); let startX; let startY; let startWidth; let startHeight; let edge = 0; let initialLeft; let initialTop; let stopResize; let stopMove; function filteroutHidden(el) { if (!el) return el; if (el.closest('[hidden]')) return null; return el; } const getXY = (e) => { let rect = e.target.getBoundingClientRect(); let x = e.clientX - rect.left; //x position within the element. let y = e.clientY - rect.top; //y position within the element. return { x, y }; } let beforeEvent = null; function resizeWindow(e) { const chatWindow = kRef(chatWindowWR); if (chatWindow) { const mEdge = edge; if (mEdge == 4 || mEdge == 1) { } else if (mEdge == 8 || mEdge == 16) { } else { return; } Promise.resolve(chatWindow).then(chatWindow => { let rect; if (mEdge == 4 || mEdge == 1 || mEdge == 16) { let newWidth = startWidth + (startX - e.pageX); let newLeft = initialLeft + startWidth - newWidth; chatWindow.style.setProperty('--f3-w', newWidth + "px"); chatWindow.style.setProperty('--f3-left', newLeft + "px"); let newHeight = startHeight + (startY - e.pageY); let newTop = initialTop + startHeight - newHeight; chatWindow.style.setProperty('--f3-h', newHeight + "px"); chatWindow.style.setProperty('--f3-top', newTop + "px"); rect = { x: newLeft, y: newTop, w: newWidth, h: newHeight, }; } else if (mEdge == 8) { let newWidth = startWidth + e.pageX - startX; let newHeight = startHeight + e.pageY - startY; chatWindow.style.setProperty('--f3-w', newWidth + "px"); chatWindow.style.setProperty('--f3-h', newHeight + "px"); rect = { x: initialLeft, y: initialTop, w: newWidth, h: newHeight, }; } updateOpacity(chatWindow, rect, screen); }) e.stopImmediatePropagation(); e.stopPropagation(); e.preventDefault(); } } let isMoved = false; function moveWindow(e) { const chatWindow = kRef(chatWindowWR); if (!chatWindow) return; Promise.resolve(chatWindow).then(chatWindow => { let newX = initialLeft + e.pageX - startX; let newY = initialTop + e.pageY - startY; if (Math.abs(e.pageX - startX) > 10 || Math.abs(e.pageY - startY) > 10) isMoved = true; chatWindow.style.setProperty('--f3-left', newX + "px"); chatWindow.style.setProperty('--f3-top', newY + "px"); updateOpacity(chatWindow, { x: newX, y: newY, w: startWidth, h: startHeight, }, screen); }); e.stopImmediatePropagation(); e.stopPropagation(); e.preventDefault(); } function initializeResize(e) { if (!document.fullscreenElement) return; if (!document.querySelector('[floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed])')) return; if (e.target.id !== 'chat') return; const { x, y } = getXY(e); edge = 0; if (x < 16 && y < 16) { edge = 16; } else if (x < 16) edge = 4; else if (y < 16) edge = 1; else edge = 8; if (edge <= 0) return; startX = e.pageX; startY = e.pageY; const chatWindow = kRef(chatWindowWR); if (chatWindow) { Promise.resolve(chatWindow).then(chatWindow => { const rect = chatWindow.getBoundingClientRect(); initialLeft = rect.x; initialTop = rect.y; startWidth = rect.width; startHeight = rect.height; chatWindow.style.setProperty('--f3-left', initialLeft + "px"); chatWindow.style.setProperty('--f3-top', initialTop + "px"); chatWindow.style.setProperty('--f3-w', startWidth + "px"); chatWindow.style.setProperty('--f3-h', startHeight + "px"); }); } document.documentElement.setAttribute('moving', 'resize'); document.documentElement.removeEventListener("mousemove", resizeWindow, false); document.documentElement.removeEventListener("mousemove", moveWindow, false); document.documentElement.removeEventListener("mouseup", stopResize, false); document.documentElement.removeEventListener("mouseup", stopMove, false); isMoved = false; document.documentElement.addEventListener("mousemove", resizeWindow); document.documentElement.addEventListener("mouseup", stopResize); } let updateOpacityRid = 0; function updateOpacity(chatWindow, rect, screen) { const tid = ++updateOpacityRid; getRafPromise().then(() => { if (tid !== updateOpacityRid) return; const { x, y, w, h } = rect; const [left, top, right, bottom] = [x, y, x + w, y + h]; const opacityW = (Math.min(right, screen.width) - Math.max(0, left)) / w; const opacityH = (Math.min(bottom, screen.height) - Math.max(0, top)) / h; const opacity = Math.min(opacityW, opacityH); chatWindow.style.setProperty('--floating-window-opacity', Math.round(opacity * 100 * 5, 0) / 5 / 100); }); } function initializeMove(e) { if (!document.fullscreenElement) return; if (!document.querySelector('[floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed])')) return; const chatWindow = kRef(chatWindowWR); startX = e.pageX; startY = e.pageY; if (chatWindow) { Promise.resolve(chatWindow).then(chatWindow => { let rect = chatWindow.getBoundingClientRect(); initialLeft = rect.x; initialTop = rect.y; startWidth = rect.width; startHeight = rect.height; chatWindow.style.setProperty('--f3-left', initialLeft + "px"); chatWindow.style.setProperty('--f3-top', initialTop + "px"); chatWindow.style.setProperty('--f3-w', startWidth + "px"); chatWindow.style.setProperty('--f3-h', startHeight + "px"); }); } document.documentElement.setAttribute('moving', 'move'); document.documentElement.removeEventListener("mousemove", resizeWindow, false); document.documentElement.removeEventListener("mousemove", moveWindow, false); document.documentElement.removeEventListener("mouseup", stopResize, false); document.documentElement.removeEventListener("mouseup", stopMove, false); isMoved = false; document.documentElement.addEventListener("mousemove", moveWindow, false); document.documentElement.addEventListener("mouseup", stopMove, false); e.stopImmediatePropagation(); e.stopPropagation(); e.preventDefault(); beforeEvent = e; } function checkClick(beforeEvent, currentEvent) { const d = currentEvent.timeStamp - beforeEvent.timeStamp; if (d < 300 && d > 30 && !isMoved) { document.documentElement.classList.add('no-floating'); } } stopResize = (e) => { document.documentElement.removeEventListener("mousemove", resizeWindow, false); document.documentElement.removeEventListener("mousemove", moveWindow, false); document.documentElement.removeEventListener("mouseup", stopResize, false); document.documentElement.removeEventListener("mouseup", stopMove, false); document.documentElement.removeAttribute('moving'); e.stopImmediatePropagation(); e.stopPropagation(); } stopMove = (e) => { document.documentElement.removeEventListener("mousemove", resizeWindow, false); document.documentElement.removeEventListener("mousemove", moveWindow, false); document.documentElement.removeEventListener("mouseup", stopResize, false); document.documentElement.removeEventListener("mouseup", stopMove, false); document.documentElement.removeAttribute('moving'); beforeEvent && checkClick(beforeEvent, e); beforeEvent = null; e.stopImmediatePropagation(); e.stopPropagation(); } function reset() { document.documentElement.removeAttribute('moving'); document.documentElement.removeEventListener("mousemove", resizeWindow, false); document.documentElement.removeEventListener("mousemove", moveWindow, false); document.documentElement.removeEventListener("mouseup", stopResize, false); document.documentElement.removeEventListener("mouseup", stopMove, false); startX = 0; startY = 0; startWidth = 0; startHeight = 0; edge = 0; initialLeft = 0; initialTop = 0; beforeEvent = null; } function iframeFullscreenChanged() { const iframeDoc = this; _lastStyleText = null; if (!document.fullscreenElement) { activeStyle = false; iframeDoc.documentElement.classList.remove('youtube-floating-chat-iframe'); } else { activeStyle = true; iframeDoc.documentElement.classList.add('youtube-floating-chat-iframe'); } } let iframeFullscreenChangedBinded = null; function onMessage(evt) { if (evt.data !== hkey_script) return; const iframeWin = evt.source; if (!iframeWin) return; const iframeDoc = iframeWin.document; const intervalCheckFn = () => { if (!activeStyle) return; let xpathExpression = "//style[text()[contains(., 'userscript-control[floating-chat-iframe]')]]"; // Evaluating the XPath expression and getting string value directly let r###lt = iframeDoc.evaluate(xpathExpression, iframeDoc, null, XPathR###lt.STRING_TYPE, null); let newText = r###lt && r###lt.stringValue ? r###lt.stringValue : null; if (newText !== _lastStyleText) { _lastStyleText = newText; // console.log(123) let tid = ++tvc; getRafPromise().then(() => { if (tid !== tvc) return; let style = iframeWin.getComputedStyle(iframeDoc.documentElement); let fc = style.getPropertyValue('--floodcolor'); if (fc) { let floodColor03 = iframeDoc.querySelector('#floodColor-03'); floodColor03 && floodColor03.setAttribute('flood-color', fc); let floodColor04 = iframeDoc.querySelector('#floodColor-04'); floodColor04 && floodColor04.setAttribute('flood-color', fc); iframeDoc.documentElement.setAttribute('hpkns', '') } else { iframeDoc.documentElement.removeAttribute('hpkns') } }); } }; function onReady() { iframeDoc.head.appendChild(document.createElement('style')).textContent = createStyleTextForIframe(); const tm = document.createElement('template'); tm.innerHTML = svgDefs(); iframeDoc.body.appendChild(tm.content) if (iframeFullscreenChangedBinded) document.removeEventListener('fullscreenchange', iframeFullscreenChangedBinded, false); iframeFullscreenChangedBinded = iframeFullscreenChanged.bind(iframeDoc); document.addEventListener('fullscreenchange', iframeFullscreenChangedBinded, false); iframeFullscreenChangedBinded(); setInterval(intervalCheckFn, 100); } Promise.resolve().then(() => { if (iframeDoc.readyState !== 'loading') { onReady(); } else { iframeWin.addEventListener("DOMContentLoaded", onReady, false); } }); } let moInt = 0; const mutationObserverFn = () => { if (moInt > 1e9) moInt = 9; const t = ++moInt; getRafPromise().then(() => { if (t !== moInt) return; const chatWindow = chatWindowWR ? kRef(chatWindowWR) : null; if (!(chatWindow instanceof Element)) return; if (chatWindow.hasAttribute('collapsed')) return; const chatWindowCnt = insp(chatWindow); if (!chatWindowCnt) return; const btn = qsOne(chatWindow, '#show-hide-button[hidden]') if (!btn) return; if (btn && filteroutHidden(chatWindow)) { const liveChatRenderer = ((chatWindowCnt || 0).data || 0).liveChatRenderer || 0; if (liveChatRenderer && liveChatRenderer.showButton && !liveChatRenderer.showHideButton) { btn.setAttribute('is-show-button', '') } else { btn.removeAttribute('is-show-button') } } }); }; const mutationObserver = new MutationObserver(mutationObserverFn); function setChat(chat) { if (!(chat instanceof Element)) return; let resizeHandle = qsOne(chat, '.resize-handle') if (resizeHandle) return; const chatDollar = insp(chat).$ || chat.$ || 0; let cw = (() => { try { const { head, body } = chatDollar.chatframe.contentWindow.document; return { head, body } } catch (e) { return null; } })(); if (!cw) return; window.removeEventListener('message', onMessage, false); window.addEventListener('message', onMessage, false); let script = document.getElementById('rvZ0t') || (document.evaluate("//div[contains(text(), 'userscript-control[enable-customized-floating-window]')]", document, null, XPathR###lt.FIRST_ORDERED_NODE_TYPE, null) || 0).singleNodeValue; if (!script) addCSS(createStyleTextForTopWin); /* const tm = document.createElement('template'); tm.innerHTML=svgDefs(); document.body.appendChild(tm.content) */ if (!document.documentElement.hasAttribute('floating-chat-window')) document.documentElement.setAttribute('floating-chat-window', ''); chat.setAttribute('allowtransparency', 'true'); resizeHandle = document.createElement("div"); resizeHandle.className = "resize-handle"; chat.appendChild(resizeHandle); resizeHandle = null; let chatWindow; let showHideButton; let showButton; chatWindow = kRef(chatWindowWR); showHideButton = kRef(showHideButtonWR); showButton = kRef(showButtonWR); if (chatWindow) chatWindow.removeEventListener("mousedown", initializeResize, false); if (showHideButton) showHideButton.removeEventListener("mousedown", initializeMove, false); else if (showButton) showButton.removeEventListener("mousedown", initializeMove, false); if (chat) { mutationObserver.observe(chat, { attributes: true, attributeFilter: ['collapsed', 'hidden'] }); mutationObserverFn(); } chatWindow = chat; showHideButton = (qsOne(chat, '#show-hide-button')); showButton = (qsOne(chat, '#show-button')); chatWindowWR = mWeakRef(chat) showHideButtonWR = mWeakRef(showHideButton); showButtonWR = mWeakRef(showButton); if (chatWindow) chatWindow.addEventListener("mousedown", initializeResize, false); if (showHideButton) showHideButton.addEventListener("mousedown", initializeMove, false); else if (showButton) showButton.addEventListener("mousedown", initializeMove, false); reset(); } const fullscreenchangePageFn = () => { if (!document.fullscreenElement) { document.documentElement.classList.remove('no-floating') } }; function noChat(chat) { if (!(chat instanceof Element)) return; let chatWindow; let showHideButton; let showButton; chatWindow = kRef(chatWindowWR); showHideButton = kRef(showHideButtonWR); showButton = kRef(showButtonWR); if (chatWindow) chatWindow.removeEventListener("mousedown", initializeResize, false); if (showHideButton) showHideButton.removeEventListener("mousedown", initializeMove, false); else if (showHideButton) showHideButton.removeEventListener("mousedown", initializeMove, false); let resizeHandle = qsOne(chat, '.resize-handle') if (resizeHandle) { resizeHandle.remove(); } chat.removeEventListener("mousedown", initializeResize, false); showHideButton = (qsOne(chat, '#show-hide-button')); showButton = (qsOne(chat, '#show-button')); if (showHideButton) showHideButton.removeEventListener("mousedown", initializeMove, false); else if (showButton) showButton.removeEventListener("mousedown", initializeMove, false); reset(); } document.removeEventListener('fullscreenchange', fullscreenchangePageFn, false); document.addEventListener('fullscreenchange', fullscreenchangePageFn, false); fullscreenchangePageFn(); ytZara.ytProtoAsync("ytd-live-chat-frame").then((proto) => { proto.attached = ((attached) => (function () { Promise.resolve(this.hostElement || this).then(setChat).catch(console.warn); return attached.apply(this, arguments) }))(proto.attached); proto.detached = ((detached) => (function () { Promise.resolve(this.hostElement || this).then(noChat).catch(console.warn); return detached.apply(this, arguments) }))(proto.detached); let chat = document.querySelector('ytd-live-chat-frame'); if (chat) Promise.resolve(chat).then(setChat).catch(console.warn); }); }); (!isTopFrame && isIframe && top === parent) && top.postMessage(hkey_script, `${location.protocol}//${location.hostname}`); })({ requestAnimationFrame });