/**
 * This file contains common shared code that is used by all three main TypeScript files
 * These are background.ts, main.ts, and popup.ts.
 *
 * This trick is done by loading this script before any others when they are requested and
 * then adding the following line at the top of the file for support from WebStorm IDE.
 * ///<reference path="./shared.ts"/>
 */ type Tab = chrome . tabs . Tab | browser . tabs . Tab ; /** The font size percent, between 100 and 300 */ const keyTextSize = "textSize" ; /** The line height percent, between 100 and 300 */ const keyLineHeight = "lineHeight" ; /** Determines whether the extension is on or off, true is on */ const keyOnOff = "onOff" ; /** The font to update to, this is a string */ const keyFont = "font" ; /** The array of strings of whitelisted websites, this contains their hostnames in the format example.com */ const keyWhitelisted = "whitelisted" ; /** The array of {@linkcode CustomSettings} that represents the sites with custom settings */ const keyCustomSettings = "customSettings" ; /** The array of {@linkcode CustomFont}s, this is used in {@linkcode chrome.storage.local} */ const keyCustomFonts = "customFonts" ; /** The keys of the {@linkcode chrome.storage.sync} */ const keys = [ keyTextSize , keyLineHeight , keyOnOff , keyFont , keyWhitelisted , keyCustomSettings , keyCustomFonts ] ; const defaultFont = "Sahl Naskh" ; const defaultTextSize = 125 ; const defaultLineHeight = 145 ; const defaultDelay = 250 ; const reasonUpdateAllText = "updateAllText" ; const reasonInjectCustomFonts = "injectCustomFonts" ; const reasonToggleOff = "toggleOff" ; type Browser = "chrome" | "firefox" | "edge" | "opera" ; const browserName = ( ( ) : Browser => { const agent = navigator . userAgent . toLowerCase ( ) ; if ( agent . includes ( "firefox" ) ) return "firefox" ; if ( agent . includes ( "edg" ) ) return "edge" ; if ( agent . includes ( "opr" ) || agent . includes ( "opera" ) ) return "opera" ; if ( agent . includes ( "chrome" ) ) return "chrome" ; return null ; } ) ( ) ; const isChromium = ( ( ) : boolean => { return browserName === "chrome" || browserName === "edge" || browserName === "opera" ; } ) ( ) ; /**
 * Represents a site that uses different settings from the global settings
 * The settings themselves may be the same as the global but they will change independently
 */ class CustomSetting { /** The hostname url of this web site */ url : string ; /** The font size percent to use on this site */ textSize : number ; /** The line height percent to use on this site */ lineHeight : number ; /** The font to use on this site */ font : string ; constructor ( url , textSize , lineHeight , font ) { this . url = url ; this . textSize = textSize ; this . lineHeight = lineHeight ; this . font = font ; } static isValidCustomSetting ( customSettings ) { const url = customSettings . url ; const textSize = customSettings . textSize ; const lineHeight = customSettings . lineHeight ; const font = customSettings . font ; return ! ! url && typeof url === "string" && ! ! textSize && typeof textSize === "number" && textSize >= 100 && textSize <= 300 && ! ! lineHeight && typeof lineHeight === "number" && lineHeight >= 100 && lineHeight <= 300 && ! ! font && typeof font === "string" ; } static isCustomSetting ( obj ) { return ! ! obj && obj . hasOwnProperty ( "url" ) && obj . hasOwnProperty ( "textSize" ) && obj . hasOwnProperty ( "lineHeight" ) && obj . hasOwnProperty ( "font" ) && this . isValidCustomSetting ( obj as CustomSetting ) ; } static isCustomSettingsArray ( array ) { return array . length === 0 || array . every ( ( obj ) => this . isCustomSetting ( obj ) ) ; } } class CustomFont { fontName : string ; localName : string ; url : string ; constructor ( fontName , localName , url ) { this . fontName = fontName ; this . localName = localName ; this . url = url ; } static injectCSS ( font ) { const stringArray = [ ] ; stringArray . push ( `@font-face { font-family: '${ font . fontName }';` ) ; if ( font . url || font . localName ) stringArray . push ( `src: ` ) ; if ( font . url && font . localName ) stringArray . push ( `local('${ font . localName }'), url('${ font . url }');}\n` ) ; else if ( font . localName && ! font . url ) stringArray . push ( `local('${ font . localName }');}\n` ) ; else if ( font . url && ! font . localName ) stringArray . push ( `url('${ font . url }');}\n` ) ; return stringArray . join ( "" ) ; } /**
     * Trick to make sure that a font is installed on the client's machine.
     * I found this somewhere online and they claimed it works 99% of the time,
     * it's worked perfectly for me so far
     */ static isFontInstalled ( font ) { var container = document . createElement ( 'span' ) ; container . innerHTML = Array ( 100 ) . join ( 'wi' ) ; container . style . cssText = [ 'position:absolute' , 'width:auto' , 'font-size:128px' , 'left:-99999px' ] . join ( ' !important;' ) ; function getWidth ( fontFamily ) { container . style . fontFamily = fontFamily ; document . body . appendChild ( container ) ; let width = container . clientWidth ; document . body . removeChild ( container ) ; return width ; } var monoWidth = getWidth ( 'monospace' ) ; var serifWidth = getWidth ( 'serif' ) ; var sansWidth = getWidth ( 'sans-serif' ) ; return monoWidth !== getWidth ( font + ',monospace' ) || sansWidth !== getWidth ( font + ',sans-serif' ) || serifWidth !== getWidth ( font + ',serif' ) ; } static async isFontUrlValid ( url ) { return fetch ( url ) . then ( response => response . ok ) . catch ( ( ) => false ) ; } static async isFontValid ( customFont ) { let isFontInstalled = true ; let isFontUrlValid = true ; if ( customFont . localName ) isFontInstalled = CustomFont . isFontInstalled ( customFont . localName ) ; if ( customFont . url ) isFontUrlValid = ( await CustomFont . isFontUrlValid ( customFont . url ) ) ; return isFontInstalled && isFontUrlValid ; } static isValidCustomFont ( font ) { const fontName = font . fontName ; const localName = font . localName ; const url = font . url ; return ! ! fontName && typeof fontName === "string" && ! ! localName && typeof localName === "string" && ! ! url && typeof url === "string" ; } static isCustomFont ( obj ) { return ! ! obj && obj . hasOwnProperty ( "fontName" ) && obj . hasOwnProperty ( "localName" ) && obj . hasOwnProperty ( "url" ) && this . isValidCustomFont ( obj as CustomFont ) ; } static isCustomFontsArray ( array ) { return array . length === 0 || array . every ( ( obj ) => this . isCustomFont ( obj ) ) ; } } /**
 * Represents the storage that Wudooh uses.
 * This adds type and key safety to any storage modifications.
 */ interface WudoohStorage { readonly textSize ? : number ; readonly lineHeight ? : number ; readonly onOff ? : boolean ; readonly font ? : string ; readonly whitelisted ? : Array < string > ; readonly customSettings ? : Array < CustomSetting > ; readonly customFonts ? : Array < CustomFont > ; } const runtime = ( ( ) => { if ( isChromium ) return chrome . runtime ; else return browser . runtime ; } ) ( ) ; /**
 * An abstraction and simplification of the storage.sync API to make it use Promises
 */ const sync = { async get ( keys = null ) { return new Promise < WudoohStorage > ( resolve => { if ( isChromium ) chrome . storage . sync . get ( keys , storage => resolve ( storage as WudoohStorage ) ) ; else browser . storage . sync . get ( keys ) . then ( storage => resolve ( storage as WudoohStorage ) ) ; } ) ; } , async set ( wudoohStorage ) { return new Promise < void > ( resolve => { if ( isChromium ) chrome . storage . sync . set ( wudoohStorage , ( ) => resolve ( ) ) ; else browser . storage . sync . set ( wudoohStorage ) . then ( ( ) => resolve ( ) ) ; } ) ; } } ; /**
 * An abstraction and simplification of the tabs API to make it use Promises
 */ const tabs = { async create ( url ) { return new Promise < Tab > ( resolve => { if ( isChromium ) chrome . tabs . create ( { url : url } , tab => resolve ( tab ) ) ; else browser . tabs . create ( { url : url } ) . then ( tab => resolve ( tab ) ) ; } ) ; } , async queryAllTabs ( ) { return new Promise < Array < Tab > > ( resolve => { if ( isChromium ) chrome . tabs . query ( { } , tabs => resolve ( tabs ) ) ; else browser . tabs . query ( { } ) . then ( tabs => resolve ( tabs ) ) ; } ) ; } , async queryCurrentTab ( ) { return new Promise < Array < Tab > > ( resolve => { if ( isChromium ) chrome . tabs . query ( { active : true , currentWindow : true } , tabs => resolve ( tabs ) ) ; else browser . tabs . query ( { active : true , currentWindow : true } ) . then ( tabs => resolve ( tabs ) ) ; } ) ; } , sendMessage ( tabId , message ) { if ( isChromium ) chrome . tabs . sendMessage ( tabId , message ) ; else browser . tabs . sendMessage ( tabId , message ) ; } } ; async function injectCustomFonts ( customFonts ) { let customFontsStyle = get ( "wudoohCustomFontsStyle" ) ; if ( customFontsStyle ) { customFontsStyle . textContent = "" ; document . head . removeChild ( customFontsStyle ) ; customFontsStyle = null ; } customFontsStyle = document . createElement ( "style" ) ; customFontsStyle . id = "wudoohCustomFontsStyle" ; customFonts . forEach ( ( customFont ) => { customFontsStyle . textContent = customFontsStyle . textContent . concat ( CustomFont . injectCSS ( customFont ) ) ; } ) ; document . head . append ( customFontsStyle ) ; return customFonts ; } function analytics ( whatToSend ) { if ( ! window [ "ga" ] ) { window [ "ga" ] = function ( ) { ( window [ "ga" ] . q = window [ "ga" ] . q || [ ] ) . push ( arguments ) ; } ; window [ "ga" ] . l = new Date ( ) . valueOf ( ) ; const analyticsScript = document . createElement ( 'script' ) ; const firstScript = document . getElementsByTagName ( 'script' ) [ 0 ] ; analyticsScript . async = true ; analyticsScript . src = 'https://www.google-analytics.com/analytics.js' ; firstScript . parentNode . insertBefore ( analyticsScript , firstScript ) ; window [ "ga" ] ( 'create' , 'UA-164482478-2' , 'auto' ) ; window [ "ga" ] ( 'set' , 'checkProtocolTask' , null ) ; } window [ "ga" ] ( 'send' , 'pageview' , whatToSend ) ; } param elementId function get < T extends HTMLElement > ( elementId ) { return document . getElementById ( elementId ) as T | null ; } function wait ( millis , func ) { return setTimeout ( func , millis ) ; } function onDOMContentLoaded ( listener ) { document . addEventListener ( "DOMContentLoaded" , listener ) ; } interface Array < T > { contains ( element ) : boolean ; } Array . prototype . contains = function < T > ( element ) : boolean { return this . indexOf ( element ) !== - 1 ; } ; interface String { contains ( string ) : boolean ; } String . prototype . contains = function ( string ) { return this . indexOf ( string ) !== - 1 ; } ; interface Element { currentTask : number ; postDelayed ( millis , func ) ; } Element . prototype . postDelayed = function ( millis , func ) { let localTask = wait ( millis , ( ) => { if ( localTask === this . currentTask ) func . call ( this ) ; } ) ; this . currentTask = localTask ; } ;