import { Config } from './configuration' ; import { DictEntryData } from './data' ; declare global { interface Window { rikaichan ? : Rikaichan ; } } type Rikaichan = { config : Config ; prevTarget ? : HTMLElement ; popX ? : number ; popY ? : number ; prevSelView ? : Window ; selText ? : string ; oldTA ? : HTMLTextAreaElement | HTMLInputElement ; oldCaret ? : number ; uofs ? : number ; uofsNext ? : number ; prevRangeNode ? : CharacterData ; prevRangeOfs ? : number ; title ? : string ; timer ? : number ; } ; class RcxContent { private dictCount = 3 ; private altView = 0 ; private sameDict = 0 ; private forceKanji = 0 ; private defaultDict = 2 ; private nextDict = 3 ; enableTab ( config ) { if ( window . rikaichan === undefined ) { window . rikaichan = { config : config } ; window . addEventListener ( 'mousemove' , this . onMouseMove , false ) ; window . addEventListener ( 'keydown' , this . onKeyDown , true ) ; window . addEventListener ( 'keyup' , this . onKeyUp , true ) ; window . addEventListener ( 'mousedown' , this . onMouseDown , false ) ; window . addEventListener ( 'mouseup' , this . onMouseUp , false ) ; } window . rikaichan . config = config ; this . altView = config . popupLocation ; } disableTab ( ) { if ( window . rikaichan !== undefined ) { let e ; window . removeEventListener ( 'mousemove' , this . onMouseMove , false ) ; window . removeEventListener ( 'keydown' , this . onKeyDown , true ) ; window . removeEventListener ( 'keyup' , this . onKeyUp , true ) ; window . removeEventListener ( 'mousedown' , this . onMouseDown , false ) ; window . removeEventListener ( 'mouseup' , this . onMouseUp , false ) ; e = document . getElementById ( 'rikaichan-css' ) ; if ( e ?. parentNode ) { e . parentNode . removeChild ( e ) ; } e = document . getElementById ( 'rikaichan-window' ) ; if ( e ?. parentNode ) { e . parentNode . removeChild ( e ) ; } this . clearHi ( ) ; delete window . rikaichan ; } } getContentType ( tDoc ) { const m = tDoc . getElementsByTagName ( 'meta' ) ; for ( const i in m ) { if ( m [ i ] . httpEquiv === 'Content-Type' ) { const con = m [ i ] . content . split ( ';' ) ; return con [ 0 ] ; } } return null ; } showPopup ( text , elem ? , x = 0 , y = 0 , looseWidth ? ) { const topdoc = window . document ; if ( isNaN ( x ) || isNaN ( y ) ) { x = y = 0 ; } let popup = topdoc . getElementById ( 'rikaichan-window' ) ; if ( ! popup ) { const css = topdoc . createElementNS ( 'http://www.w3.org/1999/xhtml' , 'link' ) ; css . setAttribute ( 'rel' , 'stylesheet' ) ; css . setAttribute ( 'type' , 'text/css' ) ; css . setAttribute ( 'href' , chrome . extension . getURL ( 'css/popup.css' ) ) ; css . setAttribute ( 'id' , 'rikaichan-css' ) ; topdoc . getElementsByTagName ( 'head' ) [ 0 ] . appendChild ( css ) ; popup = topdoc . createElementNS ( 'http://www.w3.org/1999/xhtml' , 'div' ) ; popup . setAttribute ( 'id' , 'rikaichan-window' ) ; popup . setAttribute ( 'lang' , 'ja' ) ; topdoc . documentElement . appendChild ( popup ) ; popup . addEventListener ( 'dblclick' , ( ev ) => { this . hidePopup ( ) ; ev . stopPropagation ( ) ; } , true ) ; } popup . setAttribute ( 'data-theme' , window . rikaichan ! . config . popupcolor ) ; popup . style . width = 'auto' ; popup . style . height = 'auto' ; popup . style . maxWidth = looseWidth ? '' : '600px' ; if ( this . getContentType ( topdoc ) === 'text/plain' ) { const docFragment = document . createDocumentFragment ( ) ; docFragment . appendChild ( document . createElementNS ( 'http://www.w3.org/1999/xhtml' , 'span' ) ) ; ( docFragment . firstChild ! as HTMLElement ) . innerHTML = text ; while ( popup . firstChild ) { popup . removeChild ( popup . firstChild ) ; } popup . appendChild ( docFragment . firstChild ! ) ; } else { popup . innerHTML = text ; } if ( elem ) { popup . style . top = '-1000px' ; popup . style . left = '0px' ; popup . style . display = '' ; let pW = popup . offsetWidth ; let pH = popup . offsetHeight ; if ( pW <= 0 ) { pW = 200 ; } if ( pH <= 0 ) { pH = 0 ; let j = 0 ; while ( ( j = text . indexOf ( '<br/>' , j ) ) !== - 1 ) { j += 5 ; pH += 22 ; } pH += 25 ; } if ( this . altView === 1 ) { x = window . scrollX ; y = window . scrollY ; } else if ( this . altView === 2 ) { x = window . innerWidth - ( pW + 20 ) + window . scrollX ; y = window . innerHeight - ( pH + 20 ) + window . scrollY ; } else if ( elem instanceof window . HTMLOptionElement ) { x = 0 ; y = 0 ; let p = elem as HTMLElement ; while ( p ) { x += p . offsetLeft ; y += p . offsetTop ; p = p . offsetParent ! as HTMLElement ; } if ( elem . offsetTop > ( elem . parentNode ! as HTMLElement ) . clientHeight ) { y -= elem . offsetTop ; } if ( x + popup . offsetWidth > window . innerWidth ) { x -= popup . offsetWidth + 5 ; if ( x < 0 ) { x = 0 ; } } else { x += ( elem . parentNode ! as HTMLElement ) . offsetWidth + 5 ; } } else { if ( x + pW > window . innerWidth - 20 ) { x = window . innerWidth - pW - 20 ; if ( x < 0 ) { x = 0 ; } } let v = 25 ; if ( elem . title && elem . title !== '' ) { v += 20 ; } if ( y + v + pH > window . innerHeight ) { const t = y - pH - 30 ; if ( t >= 0 ) { y = t ; } else { y += v ; } } else { y += v ; } x += window . scrollX ; y += window . scrollY ; } } else { x += window . scrollX ; y += window . scrollY ; } popup . style . left = x + 'px' ; popup . style . top = y + 'px' ; popup . style . display = '' ; } hidePopup ( ) { const popup = document . getElementById ( 'rikaichan-window' ) ; if ( popup ) { popup . style . display = 'none' ; popup . innerHTML = '' ; } } isVisible ( ) { const popup = document . getElementById ( 'rikaichan-window' ) ; return popup && popup . style . display !== 'none' ; } clearHi ( ) { const tdata = window . rikaichan ; if ( ! tdata || ! tdata . prevSelView ) { return ; } if ( tdata . prevSelView . closed ) { delete tdata . prevSelView ; return ; } const sel = tdata . prevSelView . getSelection ( ) ! ; if ( ! sel . toString ( ) || tdata . selText === sel . toString ( ) ) { if ( ! sel . toString ( ) ) { delete tdata . oldTA ; } sel . removeAllRanges ( ) ; if ( tdata . oldTA && tdata . oldCaret ! >= 0 ) { tdata . oldTA . selectionStart = tdata . oldTA . selectionEnd = tdata . oldCaret ! ; } } delete tdata . prevSelView ; delete tdata . selText ; } lastFound = [ ] as DictEntryData [ ] ; keysDown : number [ ] = [ ] ; lastPos : { x : number | null ; y : number | null ; } = { x : null , y : null } ; lastTarget : EventTarget | null = null ; onKeyDown = ( ev ) => { this . _onKeyDown ( ev ) ; } ; _onKeyDown ( ev ) { if ( window . rikaichan ! . config . showOnKey !== '' && ( ev . altKey || ev . ctrlKey || ev . key === 'AltGraph' ) ) { if ( this . lastTarget !== null ) { const myEv = { clientX : this . lastPos . x , clientY : this . lastPos . y , target : this . lastTarget , altKey : ev . altKey || ev . key === 'AltGraph' , ctrlKey : ev . ctrlKey , shiftKey : ev . shiftKey , noDelay : true , } ; this . tryUpdatePopup ( myEv as unknown as MouseEvent & { noDelay ? : boolean ; } ) ; } return ; } if ( ev . shiftKey && ev . keyCode !== 16 ) { return ; } if ( this . keysDown [ ev . keyCode ] ) { return ; } if ( ! this . isVisible ( ) ) { return ; } if ( window . rikaichan ! . config . disablekeys && ev . keyCode !== 16 ) { return ; } let i ; let shouldPreventDefault = true ; const maxDictEntries = window . rikaichan ! . config . maxDictEntries ; let e ; switch ( ev . keyCode ) { case 16 : case 13 : this . show ( ( ev . currentTarget ! as Window ) . rikaichan ! , this . nextDict ) ; break ; case 74 : e = this . lastFound [ 0 ] ; if ( e . data . length < maxDictEntries ) { break ; } if ( ! e . index ) { e . index = 0 ; } if ( e . index > 0 ) { e . index -= 1 ; } else { e . index = e . data . length - maxDictEntries ; } chrome . runtime . sendMessage ( { type : 'makehtml' , entry : e } , this . processHtml ) ; this . lastFound = [ e ] ; break ; case 75 : e = this . lastFound [ 0 ] ; if ( e . data . length < maxDictEntries ) { break ; } if ( ! e . index ) { e . index = 0 ; } if ( e . index >= e . data . length - maxDictEntries ) { e . index = 0 ; } else { e . index += 1 ; } chrome . runtime . sendMessage ( { type : 'makehtml' , entry : e } , this . processHtml ) ; this . lastFound = [ e ] ; break ; case 27 : this . hidePopup ( ) ; this . clearHi ( ) ; break ; case 65 : this . altView = ( this . altView + 1 ) % 3 ; this . show ( ( ev . currentTarget ! as Window ) . rikaichan ! , this . sameDict ) ; break ; case 67 : if ( ev . ctrlKey || ev . metaKey ) { shouldPreventDefault = false ; } else { chrome . runtime . sendMessage ( { type : 'copyToClip' , entry : this . lastFound , } ) ; } break ; case 66 : { const rikaichan = ( ev . currentTarget ! as Window ) . rikaichan ! ; let ofs = rikaichan . uofs ; for ( i = 50 ; i > 0 ; -- i ) { rikaichan . uofs = -- ofs ! ; if ( this . show ( rikaichan , this . defaultDict ) >= 0 ) { if ( ofs ! >= rikaichan . uofs ) { break ; } } } break ; } case 68 : chrome . runtime . sendMessage ( { type : 'switchOnlyReading' } ) ; this . show ( ( ev . currentTarget ! as Window ) . rikaichan ! , this . sameDict ) ; break ; case 77 : ( ev . currentTarget ! as Window ) . rikaichan ! . uofsNext = 1 ; case 78 : { const rikaiData = ( ev . currentTarget ! as Window ) . rikaichan ! ; for ( i = 50 ; i > 0 ; -- i ) { rikaiData . uofs ! += rikaiData . uofsNext ! ; if ( this . show ( rikaiData , this . defaultDict ) >= 0 ) { break ; } } break ; } case 89 : this . altView = 0 ; ( ev . currentTarget ! as Window ) . rikaichan ! . popY ! += 20 ; this . show ( ( ev . currentTarget ! as Window ) . rikaichan ! , this . sameDict ) ; break ; default : return ; } this . keysDown [ ev . keyCode ] = 1 ; if ( shouldPreventDefault ) { ev . preventDefault ( ) ; } } mDown = false ; onMouseDown = ( ev ) => { this . _onMouseDown ( ev ) ; } ; _onMouseDown ( ev ) { if ( ev . button !== 0 ) { return ; } if ( this . isVisible ( ) ) { this . clearHi ( ) ; } this . mDown = true ; if ( ! ( 'form' in ev . target ! ) ) { window . rikaichan ! . oldCaret = - 1 ; } else { window . rikaichan ! . oldTA = ev . target ; } } onMouseUp = ( ev ) => { this . _onMouseUp ( ev ) ; } ; _onMouseUp ( ev ) { if ( ev . button !== 0 ) { return ; } this . mDown = false ; } onKeyUp = ( ev ) => { if ( this . keysDown [ ev . keyCode ] ) { this . keysDown [ ev . keyCode ] = 0 ; } } ; unicodeInfo ( c ) { const hex = '0123456789ABCDEF' ; const u = c . charCodeAt ( 0 ) ; return ( c + ' U' + hex [ ( u >>> 12 ) & 15 ] + hex [ ( u >>> 8 ) & 15 ] + hex [ ( u >>> 4 ) & 15 ] + hex [ u & 15 ] ) ; } namesN = 2 ; inlineNames = { '#text' : true , FONT : true , TT : true , I : true , B : true , BIG : true , SMALL : true , STRIKE : true , S : true , U : true , EM : true , STRONG : true , DFN : true , CODE : true , SAMP : true , KBD : true , let : true , CITE : true , ABBR : true , ACRONYM : true , A : true , Q : true , SUB : true , SUP : true , SPAN : true , WBR : true , RUBY : true , RBC : true , RTC : true , RB : true , RT : true , RP : true , } ; isInline ( node ) { return ( Object . prototype . hasOwnProperty . call ( this . inlineNames , node . nodeName ) || ( document . nodeType === Node . ELEMENT_NODE && ( document . defaultView ! . getComputedStyle ( node as Element , null ) . getPropertyValue ( 'display' ) === 'inline' || document . defaultView ! . getComputedStyle ( node as Element , null ) . getPropertyValue ( 'display' ) === 'inline-block' ) ) ) ; } textNodeExpr = 'descendant-or-self::text()[not(parent::rp) and not(ancestor::rt)]' ; startElementExpr = 'boolean(parent::rp or ancestor::rt)' ; getInlineText ( node , selEndList , maxLength , xpathExpr ) { let text = '' ; let endIndex ; if ( node . nodeName === '#text' ) { const textNode = node as Text ; endIndex = Math . min ( maxLength , textNode . data . length ) ; selEndList . push ( { node : textNode , offset : endIndex } ) ; return textNode . data . substring ( 0 , endIndex ) ; } const result = xpathExpr . evaluate ( node , XPathResult . ORDERED_NODE_ITERATOR_TYPE , null ) ; let nextNode ; while ( text . length < maxLength && ( nextNode = result . iterateNext ( ) as Text | null ) ) { if ( ! this . isElementVisible ( nextNode . parentElement ! ) ) { continue ; } endIndex = Math . min ( nextNode . data . length , maxLength - text . length ) ; text += nextNode . data . substring ( 0 , endIndex ) ; selEndList . push ( { node : nextNode , offset : endIndex } ) ; } return text ; } private isElementVisible ( element ) { const style = window . getComputedStyle ( element ) ; return style . visibility !== 'hidden' && style . display !== 'none' ; } getNext ( node ) { let nextNode ; if ( ( nextNode = node . nextSibling ) !== null ) { return nextNode ; } if ( ( nextNode = node . parentNode ) !== null && this . isInline ( nextNode ) ) { return this . getNext ( nextNode ) ; } return null ; } getTextFromRange ( rangeParent , offset , selEndList , maxLength ) { if ( rangeParent . nodeName === 'TEXTAREA' || rangeParent . nodeName === 'INPUT' ) { const pseudoTextNode = rangeParent as CharacterData & ( HTMLTextAreaElement | HTMLInputElement ) ; const endIndex = Math . min ( pseudoTextNode . data . length , offset + maxLength ) ; return pseudoTextNode . value . substring ( offset , endIndex ) ; } if ( rangeParent . nodeType !== Node . TEXT_NODE ) { return '' ; } const textRange = rangeParent as Text ; let text = '' ; const xpathExpr = textRange . ownerDocument . createExpression ( this . textNodeExpr , null ) ; if ( textRange . ownerDocument . evaluate ( this . startElementExpr , textRange , null , XPathResult . BOOLEAN_TYPE , null ) . booleanValue ) { return '' ; } const endIndex = Math . min ( textRange . data . length , offset + maxLength ) ; text += textRange . data . substring ( offset , endIndex ) ; selEndList . push ( { node : textRange , offset : endIndex } ) ; let nextNode = textRange as Node | null ; while ( nextNode !== null && ( nextNode = this . getNext ( nextNode ) ) !== null && this . isInline ( nextNode ) && text . length < maxLength ) { text += this . getInlineText ( nextNode , selEndList , maxLength - text . length , xpathExpr ) ; } return text ; } lastSelEnd : { node : CharacterData ; offset : number ; } [ ] = [ ] ; lastRo = 0 ; show ( tdata , dictOption ) { const rp = tdata . prevRangeNode ; let ro = tdata . prevRangeOfs ! + tdata . uofs ! ; let u ; tdata . uofsNext = 1 ; if ( ! rp ) { this . clearHi ( ) ; this . hidePopup ( ) ; return 0 ; } if ( ro < 0 || ro >= rp . data . length ) { this . clearHi ( ) ; this . hidePopup ( ) ; return 0 ; } while ( ( u = rp . data . charCodeAt ( ro ) ) === 32 || u === 9 || u === 10 ) { ++ ro ; if ( ro >= rp . data . length ) { this . clearHi ( ) ; this . hidePopup ( ) ; return 0 ; } } if ( isNaN ( u ) || ( u !== 0x25cb && ( u < 0x3001 || u > 0x30ff ) && ( u < 0x3400 || u > 0x9fff ) && ( u < 0xf900 || u > 0xfaff ) && ( u < 0xff10 || u > 0xff9d ) ) ) { this . clearHi ( ) ; this . hidePopup ( ) ; return - 2 ; } const selEndList = [ ] ; const text = this . getTextFromRange ( rp , ro , selEndList , 13 ) ; this . lastSelEnd = selEndList ; this . lastRo = ro ; chrome . runtime . sendMessage ( { type : 'xsearch' , text : text , dictOption : String ( dictOption ) } , this . processEntry ) ; return 1 ; } processEntry = ( e ) : void => { const tdata = window . rikaichan ! ; const ro = this . lastRo ; const selEndList = this . lastSelEnd ; if ( ! e ) { this . hidePopup ( ) ; this . clearHi ( ) ; return ; } this . lastFound = [ e ] ; if ( ! e . matchLen ) { e . matchLen = 1 ; } tdata . uofsNext = e . matchLen ; tdata . uofs = ro - tdata . prevRangeOfs ! ; const rp = tdata . prevRangeNode ; if ( rp && ( ( tdata . config . highlight && ! this . mDown && ! ( 'form' in tdata . prevTarget ! ) ) || ( 'form' in tdata . prevTarget ! && tdata . config . textboxhl ) ) ) { const doc = rp . ownerDocument ; if ( ! doc ) { this . clearHi ( ) ; this . hidePopup ( ) ; return ; } this . highlightMatch ( doc , rp , ro , e . matchLen , selEndList , tdata ) ; tdata . prevSelView = doc . defaultView ! ; } chrome . runtime . sendMessage ( { type : 'makehtml' , entry : e } , this . processHtml ) ; } ; processHtml = ( html ) => { const tdata = window . rikaichan ! ; this . showPopup ( html , tdata . prevTarget , tdata . popX , tdata . popY , false ) ; return 1 ; } ; highlightMatch ( doc , rp , ro , matchLen , selEndList , tdata ) { const sel = doc . defaultView ! . getSelection ( ) ! ; if ( selEndList . length === 0 ) { try { if ( rp . nodeName === 'TEXTAREA' || rp . nodeName === 'INPUT' ) { const textNode = rp as HTMLTextAreaElement | HTMLInputElement ; if ( sel . toString ( ) && tdata . selText !== sel . toString ( ) ) { return ; } if ( ! sel . toString ( ) && tdata . oldTA ! === textNode ) { tdata . oldCaret = textNode . selectionStart ! ; tdata . oldTA = textNode ; } textNode . selectionStart = ro ; textNode . selectionEnd = matchLen + ro ; tdata . selText = textNode . value . substring ( ro , matchLen + ro ) ; } } catch ( err ) { delete tdata . oldTA ; if ( err instanceof Error ) { console . log ( err . message ) ; } } return ; } if ( tdata . oldTA && ! sel . toString ( ) && tdata . oldCaret ! >= 0 ) { tdata . oldCaret = tdata . oldTA . selectionStart ! ; } let selEnd ; let offset = matchLen + ro ; for ( let i = 0 , len = selEndList . length ; i < len ; i ++ ) { selEnd = selEndList [ i ] ; if ( offset <= selEnd . offset ) { break ; } offset -= selEnd . offset ; } const range = doc . createRange ( ) ; range . setStart ( rp , ro ) ; range . setEnd ( selEnd ! . node , offset ) ; if ( sel . toString ( ) && tdata . selText !== sel . toString ( ) ) { return ; } sel . removeAllRanges ( ) ; sel . addRange ( range ) ; tdata . selText = sel . toString ( ) ; if ( window . rikaichan ! . config . ttsEnabled ) { const text = sel . toString ( ) ; if ( text . length > 0 ) { chrome . runtime . sendMessage ( { type : 'playTTS' , text : text } ) ; } } } showTitle ( tdata ) { chrome . runtime . sendMessage ( { type : 'translate' , title : tdata . title } , this . processTitle ) ; } processTitle = ( e ) => { const tdata = window . rikaichan ! ; if ( ! e ) { this . hidePopup ( ) ; return ; } e . title = tdata . title ! . substr ( 0 , e . textLen ) . replace ( /[\x00-\xff]/g , ( c ) => { return '&#' + c . charCodeAt ( 0 ) + ';' ; } ) ; if ( tdata . title ! . length > e . textLen ) { e . title += '...' ; } this . lastFound = [ e ] ; chrome . runtime . sendMessage ( { type : 'makehtml' , entry : e } , this . processHtml ) ; } ; getFirstTextChild ( node ) { return document . evaluate ( 'descendant::text()[not(parent::rp) and not(ancestor::rt)]' , node , null , XPathResult . ANY_TYPE , null ) . iterateNext ( ) ; } makeFake ( real ) { const fake = document . createElement ( 'div' ) ; const realRect = real . getBoundingClientRect ( ) ; fake . innerText = real . value ; fake . style . cssText = document . defaultView ! . getComputedStyle ( real , '' ) . cssText ; fake . scrollTop = real . scrollTop ; fake . scrollLeft = real . scrollLeft ; fake . style . position = 'absolute' ; fake . style . zIndex = '7777' ; fake . style . top = realRect . top + 'px' ; fake . style . left = realRect . left + 'px' ; return fake ; } getTotalOffset ( parent , tNode , offset ) { let fChild = parent . firstChild ! ; let realO = offset ; if ( fChild === tNode ) { return offset ; } do { let val = 0 ; if ( fChild . nodeName === 'BR' ) { val = 1 ; } else { const maybeText = fChild as CharacterData ; val = maybeText . data ? maybeText . data . length : 0 ; } realO += val ; } while ( ( fChild = fChild . nextSibling ! ) !== tNode ) ; return realO ; } onMouseMove = ( ev ) => { this . lastPos . x = ev . clientX ; this . lastPos . y = ev . clientY ; this . lastTarget = ev . target ; this . tryUpdatePopup ( ev ) ; } ; tryUpdatePopup ( ev ) { const altGraph = ev . getModifierState && ev . getModifierState ( 'AltGraph' ) ; if ( ( window . rikaichan ! . config . showOnKey . includes ( 'Alt' ) && ! ev . altKey && ! altGraph ) || ( window . rikaichan ! . config . showOnKey . includes ( 'Ctrl' ) && ! ev . ctrlKey ) ) { this . clearHi ( ) ; this . hidePopup ( ) ; return ; } let fake ; const tdata = window . rikaichan ! ; let range ; let rp ; let ro ; const eventTarget = ev . target as HTMLElement & ( HTMLTextAreaElement | HTMLInputElement ) & HTMLImageElement & HTMLOptionElement & HTMLSelectElement ; try { if ( eventTarget . nodeName === 'TEXTAREA' || eventTarget . nodeName === 'INPUT' ) { fake = this . makeFake ( eventTarget as HTMLTextAreaElement | HTMLInputElement ) ; document . body . appendChild ( fake ) ; fake . scrollTop = eventTarget . scrollTop ; fake . scrollLeft = eventTarget . scrollLeft ; } range = document . caretRangeFromPoint ( ev . clientX , ev . clientY ) ; if ( range === null ) { return ; } const startNode = range . startContainer ; ro = range . startOffset ; rp = startNode as CharacterData ; if ( fake ) { if ( rp . data && rp . data . length === ro ) { document . body . removeChild ( fake ) ; return ; } fake . style . display = 'none' ; ro = this . getTotalOffset ( rp . parentNode ! , rp , ro ) ; } if ( rp . data && ro === rp . data . length ) { if ( rp . nextSibling && rp . nextSibling . nodeName === 'WBR' ) { rp = rp . nextSibling . nextSibling ; ro = 0 ; } else if ( this . isInline ( eventTarget ) ) { if ( rp . parentNode === eventTarget ) { } else if ( fake && rp . parentNode . innerText === eventTarget . value ) { } else { rp = eventTarget . firstChild ; ro = 0 ; } } else { rp = rp . parentNode . nextSibling ; ro = 0 ; } } if ( ! fake && ! ( 'form' in eventTarget ) && rp && rp . parentNode !== eventTarget && ro === 1 ) { rp = this . getFirstTextChild ( eventTarget ) ; ro = 0 ; } else if ( ! fake && ( ! rp || rp . parentNode !== eventTarget ) ) { rp = null ; ro = - 1 ; } if ( fake ) { rp = eventTarget ; rp . data = rp . value ; } if ( eventTarget === tdata . prevTarget && this . isVisible ( ) ) { if ( tdata . title ) { if ( fake ) { document . body . removeChild ( fake ) ; } return ; } if ( rp === tdata . prevRangeNode && ro === tdata . prevRangeOfs ) { if ( fake ) { document . body . removeChild ( fake ) ; } return ; } } if ( fake ) { document . body . removeChild ( fake ) ; } } catch ( err ) { if ( err instanceof Error ) { console . log ( err . message ) ; } if ( fake ) { document . body . removeChild ( fake ) ; } return ; } tdata . prevTarget = eventTarget ; tdata . prevRangeNode = rp ; tdata . prevRangeOfs = ro ; delete tdata . title ; tdata . uofs = 0 ; tdata . uofsNext = 1 ; const delay = ev . noDelay ? 1 : window . rikaichan ! . config . popupDelay ; if ( rp && rp . data && ro < rp . data . length ) { this . forceKanji = ev . shiftKey ? 1 : 0 ; tdata . popX = ev . clientX ; tdata . popY = ev . clientY ; tdata . timer = window . setTimeout ( ( rangeNode , rangeOffset ) => { if ( ! window . rikaichan || rangeNode !== window . rikaichan . prevRangeNode || rangeOffset !== window . rikaichan . prevRangeOfs ) { return ; } this . show ( tdata , this . forceKanji ? this . forceKanji : this . defaultDict ) ; } , delay , rp , ro ) ; return ; } if ( typeof eventTarget . title === 'string' && eventTarget . title . length ) { tdata . title = eventTarget . title ; } else if ( typeof eventTarget . alt === 'string' && eventTarget . alt . length ) { tdata . title = eventTarget . alt ; } if ( eventTarget . nodeName === 'OPTION' ) { tdata . title = eventTarget . text ; } else if ( eventTarget . nodeName === 'SELECT' ) { tdata . title = eventTarget . options [ eventTarget . selectedIndex ] . text ; } if ( tdata . title ) { tdata . popX = ev . clientX ; tdata . popY = ev . clientY ; tdata . timer = window . setTimeout ( ( tdata , title ) => { if ( ! window . rikaichan || title !== window . rikaichan . title ) { return ; } this . showTitle ( tdata ) ; } , delay , tdata , tdata . title ) ; } else { const dx = tdata . popX ! - ev . clientX ; const dy = tdata . popY ! - ev . clientY ; const distance = Math . sqrt ( dx * dx + dy * dy ) ; if ( distance > 4 ) { this . clearHi ( ) ; this . hidePopup ( ) ; } } } } const rcxContent = new RcxContent ( ) ; chrome . runtime . onMessage . addListener ( ( request ) => { switch ( request . type ) { case 'enable' : rcxContent . enableTab ( request . config ) ; break ; case 'disable' : rcxContent . disableTab ( ) ; break ; case 'showPopup' : rcxContent . showPopup ( request . text ) ; break ; default : } } ) ; chrome . runtime . sendMessage ( { type : 'enable?' } ) ; export { RcxContent as TestOnlyRcxContent } ;