151 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			151 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| //
 | |
| // The first script to be run on every page as to capture media elements
 | |
| // when they're created.
 | |
| //
 | |
| // Has to be run before anything else is executed in the document.
 | |
| // Declaring the script in the manifest, as opposed to using
 | |
| // chrome.tabs.executeScript, seems to be the only way to achieve this from
 | |
| // my own testing.
 | |
| // https://bugs.chromium.org/p/chromium/issues/detail?id=471801
 | |
| //
 | |
| 
 | |
| ;(function() {
 | |
|   // Only run in iframes, the same as Metastream webviews
 | |
|   if (window.self === window.top) return
 | |
| 
 | |
|   const mainWorldScript = function() {
 | |
|     document.getElementById('metastreaminitscript').remove()
 | |
| 
 | |
|     const INIT_TIMEOUT = 5e3
 | |
|     const isFirefox = navigator.userAgent.toLowerCase().includes('firefox')
 | |
| 
 | |
|     //=========================================================================
 | |
|     // document.createElement proxy
 | |
|     //=========================================================================
 | |
| 
 | |
|     window.__metastreamMediaElements = new Set()
 | |
| 
 | |
|     // Proxy document.createElement to trap media elements created in-memory
 | |
|     const origCreateElement = document.createElement
 | |
|     const proxyCreateElement = function() {
 | |
|       const element = origCreateElement.apply(document, arguments)
 | |
|       if (window.__metastreamMediaElements && element instanceof HTMLMediaElement) {
 | |
|         window.__metastreamMediaElements.add(element)
 | |
|       }
 | |
|       return element
 | |
|     }
 | |
|     proxyCreateElement.toString = origCreateElement.toString.bind(origCreateElement)
 | |
|     document.createElement = proxyCreateElement
 | |
| 
 | |
|     setTimeout(() => {
 | |
|       if (window.__metastreamMediaElements) {
 | |
|         window.__metastreamMediaElements.clear()
 | |
|         window.__metastreamMediaElements = undefined
 | |
|       }
 | |
|     }, INIT_TIMEOUT)
 | |
| 
 | |
|     //=========================================================================
 | |
|     // navigator.mediaSession proxy (Firefox)
 | |
|     //=========================================================================
 | |
| 
 | |
|     if (isFirefox) {
 | |
|       // stub out MediaSession API until Firefox supports this natively
 | |
|       if (!navigator.mediaSession) {
 | |
|         const noop = () => {}
 | |
|         const mediaSessionStub = {
 | |
|           __installedByMetastreamRemote__: true,
 | |
|           setActionHandler: noop
 | |
|         }
 | |
|         Object.defineProperty(window.navigator, 'mediaSession', {
 | |
|           value: mediaSessionStub,
 | |
|           enumerable: false,
 | |
|           writable: true
 | |
|         })
 | |
| 
 | |
|         function MediaMetadata(metadata) {
 | |
|           Object.assign(this, metadata)
 | |
|         }
 | |
|         window.MediaMetadata = MediaMetadata
 | |
|       }
 | |
| 
 | |
|       const { mediaSession } = navigator
 | |
| 
 | |
|       // Capture action handlers for player.js proxy
 | |
|       mediaSession._handlers = {}
 | |
| 
 | |
|       const _setActionHandler = mediaSession.setActionHandler
 | |
|       mediaSession.setActionHandler = function(name, handler) {
 | |
|         mediaSession._handlers[name] = handler
 | |
|         _setActionHandler.apply(mediaSession, arguments)
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     //=========================================================================
 | |
|     // document.domain fix (Firefox)
 | |
|     //=========================================================================
 | |
| 
 | |
|     if (isFirefox) {
 | |
|       const domains = ['twitch.tv', 'crunchyroll.com']
 | |
| 
 | |
|       // Fix for setting document.domain in sandboxed iframe
 | |
|       try {
 | |
|         const { domain } = document
 | |
|         if (domain && domains.some(d => domain.includes(d))) {
 | |
|           Object.defineProperty(document, 'domain', {
 | |
|             value: domain,
 | |
|             writable: true
 | |
|           })
 | |
|         }
 | |
|       } catch (e) {}
 | |
|     }
 | |
| 
 | |
|     //=========================================================================
 | |
|     // Inline script embed prevention fix
 | |
|     //=========================================================================
 | |
| 
 | |
|     const observeScripts = () => {
 | |
|       const scriptSnippets = [
 | |
|         { code: 'window.top !== window.self', replacement: 'false' },
 | |
|         { code: 'self == top', replacement: 'true' },
 | |
|         { code: 'top.location != window.location', replacement: 'false' }
 | |
|       ]
 | |
| 
 | |
|       const getAddedScripts = mutationList =>
 | |
|         mutationList.reduce((scripts, mutation) => {
 | |
|           if (mutation.type !== 'childList') return scripts
 | |
|           const inlineScripts = Array.from(mutation.addedNodes).filter(
 | |
|             node => node instanceof HTMLScriptElement && node.innerHTML.length > 0
 | |
|           )
 | |
|           return inlineScripts.length > 0 ? [...scripts, ...inlineScripts] : scripts
 | |
|         }, [])
 | |
| 
 | |
|       // Modifies inline scripts to allow embedding content in iframe
 | |
|       const inlineScriptModifier = mutationsList => {
 | |
|         const scripts = getAddedScripts(mutationsList)
 | |
|         for (let script of scripts) {
 | |
|           for (let snippet of scriptSnippets) {
 | |
|             if (script.innerHTML.includes(snippet.code)) {
 | |
|               script.innerHTML = script.innerHTML.split(snippet.code).join(snippet.replacement)
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       const observer = new MutationObserver(inlineScriptModifier)
 | |
|       observer.observe(document.documentElement, { childList: true, subtree: true })
 | |
| 
 | |
|       // Stop watching for changes after we finish loading
 | |
|       window.addEventListener('load', () => observer.disconnect())
 | |
|     }
 | |
| 
 | |
|     observeScripts()
 | |
|   }
 | |
| 
 | |
|   const script = document.createElement('script')
 | |
|   script.id = 'metastreaminitscript'
 | |
|   script.textContent = `(${mainWorldScript}());`
 | |
|   if (document.documentElement) {
 | |
|     document.documentElement.appendChild(script)
 | |
|   }
 | |
| })()
 |