diff options
| -rw-r--r-- | ext/bg/js/search-main.js | 44 | ||||
| -rw-r--r-- | ext/bg/js/search-query-parser.js | 1 | ||||
| -rw-r--r-- | ext/bg/js/search.js | 120 | ||||
| -rw-r--r-- | ext/bg/js/settings/popup-preview-frame-main.js | 17 | ||||
| -rw-r--r-- | ext/bg/js/settings/popup-preview-frame.js | 46 | ||||
| -rw-r--r-- | ext/bg/settings-popup-preview.html | 1 | ||||
| -rw-r--r-- | ext/fg/js/content-script-main.js | 152 | ||||
| -rw-r--r-- | ext/fg/js/float-main.js | 43 | ||||
| -rw-r--r-- | ext/fg/js/float.js | 65 | ||||
| -rw-r--r-- | ext/fg/js/frontend.js | 231 | ||||
| -rw-r--r-- | ext/fg/js/popup-factory.js | 7 | ||||
| -rw-r--r-- | ext/fg/js/popup-proxy.js | 4 | ||||
| -rw-r--r-- | ext/fg/js/popup.js | 11 | ||||
| -rw-r--r-- | ext/mixed/js/dom.js | 18 | ||||
| -rw-r--r-- | ext/mixed/js/text-scanner.js | 8 | 
15 files changed, 437 insertions, 331 deletions
| diff --git a/ext/bg/js/search-main.js b/ext/bg/js/search-main.js index f18d6d88..13bd8767 100644 --- a/ext/bg/js/search-main.js +++ b/ext/bg/js/search-main.js @@ -18,42 +18,16 @@  /* global   * DisplaySearch   * api - * dynamicLoader   */ -async function injectSearchFrontend() { -    await dynamicLoader.loadScripts([ -        '/mixed/js/text-scanner.js', -        '/fg/js/frame-offset-forwarder.js', -        '/fg/js/popup.js', -        '/fg/js/popup-factory.js', -        '/fg/js/frontend.js', -        '/fg/js/content-script-main.js' -    ]); -} -  (async () => { -    api.forwardLogsToBackend(); -    await yomichan.prepare(); - -    const displaySearch = new DisplaySearch(); -    await displaySearch.prepare(); - -    let optionsApplied = false; - -    const applyOptions = async () => { -        const optionsContext = {depth: 0, url: window.location.href}; -        const options = await api.optionsGet(optionsContext); -        if (!options.scanning.enableOnSearchPage || optionsApplied) { return; } - -        optionsApplied = true; -        yomichan.off('optionsUpdated', applyOptions); - -        window.frontendInitializationData = {depth: 1, proxy: false, isSearchPage: true}; -        await injectSearchFrontend(); -    }; - -    yomichan.on('optionsUpdated', applyOptions); - -    await applyOptions(); +    try { +        api.forwardLogsToBackend(); +        await yomichan.prepare(); + +        const displaySearch = new DisplaySearch(); +        await displaySearch.prepare(); +    } catch (e) { +        yomichan.logError(e); +    }  })(); diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index 97e98b40..86524b66 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -42,6 +42,7 @@ class QueryParser {      async prepare() {          await this._queryParserGenerator.prepare(); +        this._textScanner.prepare();          this._queryParser.addEventListener('click', this._onClick.bind(this));      } diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 08c02624..88be335f 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -19,8 +19,11 @@   * ClipboardMonitor   * DOM   * Display + * Frontend + * PopupFactory   * QueryParser   * api + * dynamicLoader   * wanakana   */ @@ -73,51 +76,49 @@ class DisplaySearch extends Display {      }      async prepare() { -        try { -            await super.prepare(); -            await this.updateOptions(); -            yomichan.on('optionsUpdated', () => this.updateOptions()); -            await this.queryParser.prepare(); +        await super.prepare(); +        await this.updateOptions(); +        yomichan.on('optionsUpdated', () => this.updateOptions()); +        await this.queryParser.prepare(); + +        const {queryParams: {query='', mode=''}} = parseUrl(window.location.href); + +        document.documentElement.dataset.searchMode = mode; -            const {queryParams: {query='', mode=''}} = parseUrl(window.location.href); +        if (this.options.general.enableWanakana === true) { +            this.wanakanaEnable.checked = true; +            wanakana.bind(this.query); +        } else { +            this.wanakanaEnable.checked = false; +        } -            document.documentElement.dataset.searchMode = mode; +        this.setQuery(query); +        this.onSearchQueryUpdated(this.query.value, false); -            if (this.options.general.enableWanakana === true) { -                this.wanakanaEnable.checked = true; -                wanakana.bind(this.query); +        if (mode !== 'popup') { +            if (this.options.general.enableClipboardMonitor === true) { +                this.clipboardMonitorEnable.checked = true; +                this.clipboardMonitor.start();              } else { -                this.wanakanaEnable.checked = false; +                this.clipboardMonitorEnable.checked = false;              } +            this.clipboardMonitorEnable.addEventListener('change', this.onClipboardMonitorEnableChange.bind(this)); +        } -            this.setQuery(query); -            this.onSearchQueryUpdated(this.query.value, false); - -            if (mode !== 'popup') { -                if (this.options.general.enableClipboardMonitor === true) { -                    this.clipboardMonitorEnable.checked = true; -                    this.clipboardMonitor.start(); -                } else { -                    this.clipboardMonitorEnable.checked = false; -                } -                this.clipboardMonitorEnable.addEventListener('change', this.onClipboardMonitorEnableChange.bind(this)); -            } +        chrome.runtime.onMessage.addListener(this.onRuntimeMessage.bind(this)); -            chrome.runtime.onMessage.addListener(this.onRuntimeMessage.bind(this)); +        this.search.addEventListener('click', this.onSearch.bind(this), false); +        this.query.addEventListener('input', this.onSearchInput.bind(this), false); +        this.wanakanaEnable.addEventListener('change', this.onWanakanaEnableChange.bind(this)); +        window.addEventListener('popstate', this.onPopState.bind(this)); +        window.addEventListener('copy', this.onCopy.bind(this)); +        this.clipboardMonitor.on('change', this.onExternalSearchUpdate.bind(this)); -            this.search.addEventListener('click', this.onSearch.bind(this), false); -            this.query.addEventListener('input', this.onSearchInput.bind(this), false); -            this.wanakanaEnable.addEventListener('change', this.onWanakanaEnableChange.bind(this)); -            window.addEventListener('popstate', this.onPopState.bind(this)); -            window.addEventListener('copy', this.onCopy.bind(this)); -            this.clipboardMonitor.on('change', this.onExternalSearchUpdate.bind(this)); +        this.updateSearchButton(); -            this.updateSearchButton(); +        await this._prepareNestedPopups(); -            this._isPrepared = true; -        } catch (e) { -            this.onError(e); -        } +        this._isPrepared = true;      }      onError(error) { @@ -401,4 +402,53 @@ class DisplaySearch extends Display {              document.title = `${text} - Yomichan Search`;          }      } + +    async _prepareNestedPopups() { +        let complete = false; + +        const onOptionsUpdated = async () => { +            const optionsContext = this.getOptionsContext(); +            const options = await api.optionsGet(optionsContext); +            if (!options.scanning.enableOnSearchPage || complete) { return; } + +            complete = true; +            yomichan.off('optionsUpdated', onOptionsUpdated); + +            try { +                await this._setupNestedPopups(); +            } catch (e) { +                yomichan.logError(e); +            } +        }; + +        yomichan.on('optionsUpdated', onOptionsUpdated); + +        await onOptionsUpdated(); +    } + +    async _setupNestedPopups() { +        await dynamicLoader.loadScripts([ +            '/mixed/js/text-scanner.js', +            '/fg/js/frame-offset-forwarder.js', +            '/fg/js/popup.js', +            '/fg/js/popup-factory.js', +            '/fg/js/frontend.js' +        ]); + +        const {frameId} = await api.frameInformationGet(); + +        const popupFactory = new PopupFactory(frameId); +        await popupFactory.prepare(); + +        const frontend = new Frontend( +            frameId, +            popupFactory, +            { +                depth: 1, +                proxy: false, +                isSearchPage: true +            } +        ); +        await frontend.prepare(); +    }  } diff --git a/ext/bg/js/settings/popup-preview-frame-main.js b/ext/bg/js/settings/popup-preview-frame-main.js index 7c4e2eb9..4c6096ec 100644 --- a/ext/bg/js/settings/popup-preview-frame-main.js +++ b/ext/bg/js/settings/popup-preview-frame-main.js @@ -16,12 +16,23 @@   */  /* global + * PopupFactory   * PopupPreviewFrame   * api   */  (async () => { -    api.forwardLogsToBackend(); -    const preview = new PopupPreviewFrame(); -    await preview.prepare(); +    try { +        api.forwardLogsToBackend(); + +        const {frameId} = await api.frameInformationGet(); + +        const popupFactory = new PopupFactory(frameId); +        await popupFactory.prepare(); + +        const preview = new PopupPreviewFrame(frameId, popupFactory); +        await preview.prepare(); +    } catch (e) { +        yomichan.logError(e); +    }  })(); diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js index 21fee7ee..98630503 100644 --- a/ext/bg/js/settings/popup-preview-frame.js +++ b/ext/bg/js/settings/popup-preview-frame.js @@ -18,17 +18,17 @@  /* global   * Frontend   * Popup - * PopupFactory   * TextSourceRange   * api   */  class PopupPreviewFrame { -    constructor() { +    constructor(frameId, popupFactory) { +        this._frameId = frameId; +        this._popupFactory = popupFactory;          this._frontend = null;          this._frontendGetOptionsContextOld = null;          this._apiOptionsGetOld = null; -        this._popup = null;          this._popupSetCustomOuterCssOld = null;          this._popupShown = false;          this._themeChangeTimeout = null; @@ -55,24 +55,25 @@ class PopupPreviewFrame {          api.optionsGet = this._apiOptionsGet.bind(this);          // Overwrite frontend -        const {frameId} = await api.frameInformationGet(); - -        const popupFactory = new PopupFactory(frameId); -        await popupFactory.prepare(); - -        this._popup = popupFactory.getOrCreatePopup(); -        this._popup.setChildrenSupported(false); - -        this._popupSetCustomOuterCssOld = this._popup.setCustomOuterCss.bind(this._popup); -        this._popup.setCustomOuterCss = this._popupSetCustomOuterCss.bind(this); - -        this._frontend = new Frontend(this._popup); +        this._frontend = new Frontend( +            this._frameId, +            this._popupFactory, +            { +                allowRootFramePopupProxy: false +            } +        );          this._frontendGetOptionsContextOld = this._frontend.getOptionsContext.bind(this._frontend);          this._frontend.getOptionsContext = this._getOptionsContext.bind(this);          await this._frontend.prepare();          this._frontend.setDisabledOverride(true);          this._frontend.canClearSelection = false; +        const popup = this._frontend.popup; +        popup.setChildrenSupported(false); + +        this._popupSetCustomOuterCssOld = popup.setCustomOuterCss.bind(popup); +        popup.setCustomOuterCss = this._popupSetCustomOuterCss.bind(this); +          // Update search          this._updateSearch();      } @@ -132,7 +133,9 @@ class PopupPreviewFrame {          }          this._themeChangeTimeout = setTimeout(() => {              this._themeChangeTimeout = null; -            this._popup.updateTheme(); +            const popup = this._frontend.popup; +            if (popup === null) { return; } +            popup.updateTheme();          }, 300);      } @@ -154,12 +157,16 @@ class PopupPreviewFrame {      _setCustomCss({css}) {          if (this._frontend === null) { return; } -        this._popup.setCustomCss(css); +        const popup = this._frontend.popup; +        if (popup === null) { return; } +        popup.setCustomCss(css);      }      _setCustomOuterCss({css}) {          if (this._frontend === null) { return; } -        this._popup.setCustomOuterCss(css, false); +        const popup = this._frontend.popup; +        if (popup === null) { return; } +        popup.setCustomOuterCss(css, false);      }      async _updateOptionsContext({optionsContext}) { @@ -188,7 +195,8 @@ class PopupPreviewFrame {          this._textSource = source;          await this._frontend.showContentCompleted(); -        if (this._popup.isVisibleSync()) { +        const popup = this._frontend.popup; +        if (popup !== null && popup.isVisibleSync()) {              this._popupShown = true;          } diff --git a/ext/bg/settings-popup-preview.html b/ext/bg/settings-popup-preview.html index 5eecd005..75bf06c8 100644 --- a/ext/bg/settings-popup-preview.html +++ b/ext/bg/settings-popup-preview.html @@ -131,6 +131,7 @@          <script src="/fg/js/source.js"></script>          <script src="/fg/js/popup-factory.js"></script>          <script src="/fg/js/frontend.js"></script> +        <script src="/fg/js/frame-offset-forwarder.js"></script>          <script src="/bg/js/settings/popup-preview-frame.js"></script>          <script src="/bg/js/settings/popup-preview-frame-main.js"></script> diff --git a/ext/fg/js/content-script-main.js b/ext/fg/js/content-script-main.js index c31cde3f..c4aa1bca 100644 --- a/ext/fg/js/content-script-main.js +++ b/ext/fg/js/content-script-main.js @@ -16,147 +16,31 @@   */  /* global - * DOM - * FrameOffsetForwarder   * Frontend   * PopupFactory - * PopupProxy   * api   */ -async function createPopupFactory() { -    const {frameId} = await api.frameInformationGet(); -    if (typeof frameId !== 'number') { -        const error = new Error('Failed to get frameId'); -        yomichan.logError(error); -        throw error; -    } - -    const popupFactory = new PopupFactory(frameId); -    await popupFactory.prepare(); -    return popupFactory; -} - -async function createIframePopupProxy(frameOffsetForwarder, setDisabled) { -    const rootPopupInformationPromise = yomichan.getTemporaryListenerResult( -        chrome.runtime.onMessage, -        ({action, params}, {resolve}) => { -            if (action === 'rootPopupInformation') { -                resolve(params); -            } -        } -    ); -    api.broadcastTab('rootPopupRequestInformationBroadcast'); -    const {popupId, frameId: parentFrameId} = await rootPopupInformationPromise; - -    const popup = new PopupProxy(popupId, 0, null, parentFrameId, frameOffsetForwarder); -    popup.on('offsetNotFound', setDisabled); -    await popup.prepare(); - -    return popup; -} - -async function getOrCreatePopup(depth, popupFactory) { -    return popupFactory.getOrCreatePopup(null, null, depth); -} - -async function createPopupProxy(depth, id, parentFrameId) { -    const popup = new PopupProxy(null, depth + 1, id, parentFrameId); -    await popup.prepare(); - -    return popup; -} -  (async () => { -    api.forwardLogsToBackend(); -    await yomichan.prepare(); - -    const data = window.frontendInitializationData || {}; -    const {id, depth=0, parentFrameId, url=window.location.href, proxy=false, isSearchPage=false} = data; - -    const isIframe = !proxy && (window !== window.parent); - -    const popups = { -        iframe: null, -        proxy: null, -        normal: null -    }; +    try { +        api.forwardLogsToBackend(); +        await yomichan.prepare(); -    let frontend = null; -    let frontendPreparePromise = null; -    let frameOffsetForwarder = null; -    let popupFactoryPromise = null; - -    let iframePopupsInRootFrameAvailable = true; - -    const disableIframePopupsInRootFrame = () => { -        iframePopupsInRootFrameAvailable = false; -        applyOptions(); -    }; - -    let urlUpdatedAt = 0; -    let popupProxyUrlCached = url; -    const getPopupProxyUrl = async () => { -        const now = Date.now(); -        if (popups.proxy !== null && now - urlUpdatedAt > 500) { -            popupProxyUrlCached = await popups.proxy.getUrl(); -            urlUpdatedAt = now; +        const {frameId} = await api.frameInformationGet(); +        if (typeof frameId !== 'number') { +            throw new Error('Failed to get frameId');          } -        return popupProxyUrlCached; -    }; - -    const applyOptions = async () => { -        const optionsContext = { -            depth: isSearchPage ? 0 : depth, -            url: proxy ? await getPopupProxyUrl() : window.location.href -        }; -        const options = await api.optionsGet(optionsContext); -        if (!proxy && frameOffsetForwarder === null) { -            frameOffsetForwarder = new FrameOffsetForwarder(); -            frameOffsetForwarder.prepare(); -        } - -        let popup; -        if (isIframe && options.general.showIframePopupsInRootFrame && DOM.getFullscreenElement() === null && iframePopupsInRootFrameAvailable) { -            popup = popups.iframe || await createIframePopupProxy(frameOffsetForwarder, disableIframePopupsInRootFrame); -            popups.iframe = popup; -        } else if (proxy) { -            popup = popups.proxy || await createPopupProxy(depth, id, parentFrameId); -            popups.proxy = popup; -        } else { -            popup = popups.normal; -            if (!popup) { -                if (popupFactoryPromise === null) { -                    popupFactoryPromise = createPopupFactory(); -                } -                const popupFactory = await popupFactoryPromise; -                const popupNormal = await getOrCreatePopup(depth, popupFactory); -                popups.normal = popupNormal; -                popup = popupNormal; -            } -        } - -        if (frontend === null) { -            const getUrl = proxy ? getPopupProxyUrl : null; -            frontend = new Frontend(popup, getUrl); -            frontendPreparePromise = frontend.prepare(); -            await frontendPreparePromise; -        } else { -            await frontendPreparePromise; -            if (isSearchPage) { -                const disabled = !options.scanning.enableOnSearchPage; -                frontend.setDisabledOverride(disabled); -            } - -            if (isIframe) { -                await frontend.setPopup(popup); -            } -        } -    }; - -    yomichan.on('optionsUpdated', applyOptions); -    window.addEventListener('fullscreenchange', applyOptions, false); - -    await applyOptions(); +        const popupFactory = new PopupFactory(frameId); +        await popupFactory.prepare(); + +        const frontend = new Frontend( +            frameId, +            popupFactory, +            {} +        ); +        await frontend.prepare(); +    } catch (e) { +        yomichan.logError(e); +    }  })(); diff --git a/ext/fg/js/float-main.js b/ext/fg/js/float-main.js index 2ec334c8..3bedfe58 100644 --- a/ext/fg/js/float-main.js +++ b/ext/fg/js/float-main.js @@ -18,42 +18,15 @@  /* global   * DisplayFloat   * api - * dynamicLoader   */ -async function injectPopupNested() { -    await dynamicLoader.loadScripts([ -        '/mixed/js/text-scanner.js', -        '/fg/js/popup.js', -        '/fg/js/popup-proxy.js', -        '/fg/js/frontend.js', -        '/fg/js/content-script-main.js' -    ]); -} - -async function popupNestedInitialize(id, depth, parentFrameId, url) { -    let optionsApplied = false; - -    const applyOptions = async () => { -        const optionsContext = {depth, url}; -        const options = await api.optionsGet(optionsContext); -        const maxPopupDepthExceeded = !(typeof depth === 'number' && depth < options.scanning.popupNestingMaxDepth); -        if (maxPopupDepthExceeded || optionsApplied) { return; } - -        optionsApplied = true; -        yomichan.off('optionsUpdated', applyOptions); - -        window.frontendInitializationData = {id, depth, parentFrameId, url, proxy: true}; -        await injectPopupNested(); -    }; - -    yomichan.on('optionsUpdated', applyOptions); - -    await applyOptions(); -} -  (async () => { -    api.forwardLogsToBackend(); -    const display = new DisplayFloat(); -    await display.prepare(); +    try { +        api.forwardLogsToBackend(); + +        const display = new DisplayFloat(); +        await display.prepare(); +    } catch (e) { +        yomichan.logError(e); +    }  })(); diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 12d27a9f..199990e5 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -17,8 +17,10 @@  /* global   * Display + * Frontend + * PopupFactory   * api - * popupNestedInitialize + * dynamicLoader   */  class DisplayFloat extends Display { @@ -30,7 +32,7 @@ class DisplayFloat extends Display {          this._token = null;          this._orphaned = false; -        this._initializedNestedPopups = false; +        this._nestedPopupsPrepared = false;          this._onKeyDownHandlers = new Map([              ['C', (e) => { @@ -183,10 +185,10 @@ class DisplayFloat extends Display {          await this.updateOptions(); -        if (childrenSupported && !this._initializedNestedPopups) { +        if (childrenSupported && !this._nestedPopupsPrepared) {              const {depth, url} = optionsContext; -            popupNestedInitialize(popupId, depth, frameId, url); -            this._initializedNestedPopups = true; +            this._prepareNestedPopups(popupId, depth, frameId, url); +            this._nestedPopupsPrepared = true;          }          this.setContentScale(scale); @@ -201,4 +203,57 @@ class DisplayFloat extends Display {              this._secret === message.secret          );      } + +    async _prepareNestedPopups(id, depth, parentFrameId, url) { +        let complete = false; + +        const onOptionsUpdated = async () => { +            const optionsContext = this.optionsContext; +            const options = await api.optionsGet(optionsContext); +            const maxPopupDepthExceeded = !(typeof depth === 'number' && depth < options.scanning.popupNestingMaxDepth); +            if (maxPopupDepthExceeded || complete) { return; } + +            complete = true; +            yomichan.off('optionsUpdated', onOptionsUpdated); + +            try { +                await this._setupNestedPopups(id, depth, parentFrameId, url); +            } catch (e) { +                yomichan.logError(e); +            } +        }; + +        yomichan.on('optionsUpdated', onOptionsUpdated); + +        await onOptionsUpdated(); +    } + +    async _setupNestedPopups(id, depth, parentFrameId, url) { +        await dynamicLoader.loadScripts([ +            '/mixed/js/text-scanner.js', +            '/fg/js/popup.js', +            '/fg/js/popup-proxy.js', +            '/fg/js/popup-factory.js', +            '/fg/js/frame-offset-forwarder.js', +            '/fg/js/frontend.js' +        ]); + +        const {frameId} = await api.frameInformationGet(); + +        const popupFactory = new PopupFactory(frameId); +        await popupFactory.prepare(); + +        const frontend = new Frontend( +            frameId, +            popupFactory, +            { +                id, +                depth, +                parentFrameId, +                url, +                proxy: true +            } +        ); +        await frontend.prepare(); +    }  } diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index ab455c09..71e53b03 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -16,16 +16,18 @@   */  /* global + * DOM + * FrameOffsetForwarder + * PopupProxy   * TextScanner   * api   * docSentenceExtract   */  class Frontend { -    constructor(popup, getUrl=null) { +    constructor(frameId, popupFactory, frontendInitializationData) {          this._id = yomichan.generateId(16); -        this._popup = popup; -        this._getUrl = getUrl; +        this._popup = null;          this._disabledOverride = false;          this._options = null;          this._pageZoomFactor = 1.0; @@ -37,11 +39,31 @@ class Frontend {          this._optionsUpdatePending = false;          this._textScanner = new TextScanner({              node: window, -            ignoreElements: () => this._popup.isProxy() ? [] : [this._popup.getFrame()], -            ignorePoint: (x, y) => this._popup.containsPoint(x, y), +            ignoreElements: this._ignoreElements.bind(this), +            ignorePoint: this._ignorePoint.bind(this),              search: this._search.bind(this)          }); +        const { +            depth=0, +            id: proxyPopupId, +            parentFrameId, +            proxy: useProxyPopup=false, +            isSearchPage=false, +            allowRootFramePopupProxy=true +        } = frontendInitializationData; +        this._proxyPopupId = proxyPopupId; +        this._parentFrameId = parentFrameId; +        this._useProxyPopup = useProxyPopup; +        this._isSearchPage = isSearchPage; +        this._depth = depth; +        this._frameId = frameId; +        this._frameOffsetForwarder = new FrameOffsetForwarder(); +        this._popupFactory = popupFactory; +        this._allowRootFramePopupProxy = allowRootFramePopupProxy; +        this._popupCache = new Map(); +        this._updatePopupToken = null; +          this._windowMessageHandlers = new Map([              ['popupClose', this._onMessagePopupClose.bind(this)],              ['selectionCopy', this._onMessageSelectionCopy.bind()] @@ -62,43 +84,46 @@ class Frontend {          this._textScanner.canClearSelection = value;      } +    get popup() { +        return this._popup; +    } +      async prepare() { +        this._frameOffsetForwarder.prepare(); + +        await this.updateOptions();          try { -            await this.updateOptions(); -            try { -                const {zoomFactor} = await api.getZoom(); -                this._pageZoomFactor = zoomFactor; -            } catch (e) { -                // Ignore exceptions which may occur due to being on an unsupported page (e.g. about:blank) -            } +            const {zoomFactor} = await api.getZoom(); +            this._pageZoomFactor = zoomFactor; +        } catch (e) { +            // Ignore exceptions which may occur due to being on an unsupported page (e.g. about:blank) +        } -            window.addEventListener('resize', this._onResize.bind(this), false); +        this._textScanner.prepare(); -            const visualViewport = window.visualViewport; -            if (visualViewport !== null && typeof visualViewport === 'object') { -                window.visualViewport.addEventListener('scroll', this._onVisualViewportScroll.bind(this)); -                window.visualViewport.addEventListener('resize', this._onVisualViewportResize.bind(this)); -            } +        window.addEventListener('resize', this._onResize.bind(this), false); +        DOM.addFullscreenChangeEventListener(this._updatePopup.bind(this)); -            yomichan.on('orphaned', this._onOrphaned.bind(this)); -            yomichan.on('optionsUpdated', this.updateOptions.bind(this)); -            yomichan.on('zoomChanged', this._onZoomChanged.bind(this)); -            chrome.runtime.onMessage.addListener(this._onRuntimeMessage.bind(this)); +        const visualViewport = window.visualViewport; +        if (visualViewport !== null && typeof visualViewport === 'object') { +            window.visualViewport.addEventListener('scroll', this._onVisualViewportScroll.bind(this)); +            window.visualViewport.addEventListener('resize', this._onVisualViewportResize.bind(this)); +        } -            this._textScanner.on('clearSelection', this._onClearSelection.bind(this)); -            this._textScanner.on('activeModifiersChanged', this._onActiveModifiersChanged.bind(this)); +        yomichan.on('orphaned', this._onOrphaned.bind(this)); +        yomichan.on('optionsUpdated', this.updateOptions.bind(this)); +        yomichan.on('zoomChanged', this._onZoomChanged.bind(this)); +        chrome.runtime.onMessage.addListener(this._onRuntimeMessage.bind(this)); -            this._updateContentScale(); -            this._broadcastRootPopupInformation(); -        } catch (e) { -            yomichan.logError(e); -        } -    } +        this._textScanner.on('clearSelection', this._onClearSelection.bind(this)); +        this._textScanner.on('activeModifiersChanged', this._onActiveModifiersChanged.bind(this)); -    async setPopup(popup) { -        this._textScanner.clearSelection(true); -        this._popup = popup; -        await popup.setOptionsContext(await this.getOptionsContext(), this._id); +        api.crossFrame.registerHandlers([ +            ['getUrl', {async: false, handler: this._onApiGetUrl.bind(this)}] +        ]); + +        this._updateContentScale(); +        this._broadcastRootPopupInformation();      }      setDisabledOverride(disabled) { @@ -112,8 +137,16 @@ class Frontend {      }      async getOptionsContext() { -        const url = this._getUrl !== null ? await this._getUrl() : window.location.href; -        const depth = this._popup.depth; +        let url = window.location.href; +        if (this._useProxyPopup) { +            try { +                url = await api.crossFrame.invoke(this._parentFrameId, 'getUrl', {}); +            } catch (e) { +                // NOP +            } +        } + +        const depth = this._depth;          const modifierKeys = [...this._activeModifiers];          return {depth, url, modifierKeys};      } @@ -121,6 +154,9 @@ class Frontend {      async updateOptions() {          const optionsContext = await this.getOptionsContext();          this._options = await api.optionsGet(optionsContext); + +        await this._updatePopup(); +          this._textScanner.setOptions(this._options);          this._updateTextScannerEnabled(); @@ -130,8 +166,6 @@ class Frontend {          }          this._textScanner.ignoreNodes = ignoreNodes.join(','); -        await this._popup.setOptionsContext(optionsContext, this._id); -          this._updateContentScale();          const textSourceCurrent = this._textScanner.getCurrentTextSource(); @@ -167,6 +201,12 @@ class Frontend {          this._broadcastDocumentInformation(uniqueId);      } +    // API message handlers + +    _onApiGetUrl() { +        return window.location.href; +    } +      // Private      _onResize() { @@ -223,6 +263,95 @@ class Frontend {          await this.updateOptions();      } +    async _updatePopup() { +        const showIframePopupsInRootFrame = this._options.general.showIframePopupsInRootFrame; +        const isIframe = !this._useProxyPopup && (window !== window.parent); + +        let popupPromise; +        if ( +            isIframe && +            showIframePopupsInRootFrame && +            DOM.getFullscreenElement() === null && +            this._allowRootFramePopupProxy +        ) { +            popupPromise = this._popupCache.get('iframe'); +            if (typeof popupPromise === 'undefined') { +                popupPromise = this._getIframeProxyPopup(); +                this._popupCache.set('iframe', popupPromise); +            } +        } else if (this._useProxyPopup) { +            popupPromise = this._popupCache.get('proxy'); +            if (typeof popupPromise === 'undefined') { +                popupPromise = this._getProxyPopup(); +                this._popupCache.set('proxy', popupPromise); +            } +        } else { +            popupPromise = this._popupCache.get('default'); +            if (typeof popupPromise === 'undefined') { +                popupPromise = this._getDefaultPopup(); +                this._popupCache.set('default', popupPromise); +            } +        } + +        // The token below is used as a unique identifier to ensure that a new _updatePopup call +        // hasn't been started during the await. +        const token = {}; +        this._updatePopupToken = token; +        const popup = await popupPromise; +        const optionsContext = await this.getOptionsContext(); +        if (this._updatePopupToken !== token) { return; } +        await popup.setOptionsContext(optionsContext, this._id); +        if (this._updatePopupToken !== token) { return; } + +        if (this._isSearchPage) { +            this.setDisabledOverride(!this._options.scanning.enableOnSearchPage); +        } + +        this._textScanner.clearSelection(true); +        this._popup = popup; +        this._depth = popup.depth; +    } + +    async _getDefaultPopup() { +        return this._popupFactory.getOrCreatePopup(null, null, this._depth); +    } + +    async _getProxyPopup() { +        const popup = new PopupProxy(null, this._depth + 1, this._proxyPopupId, this._parentFrameId); +        await popup.prepare(); +        return popup; +    } + +    async _getIframeProxyPopup() { +        const rootPopupInformationPromise = yomichan.getTemporaryListenerResult( +            chrome.runtime.onMessage, +            ({action, params}, {resolve}) => { +                if (action === 'rootPopupInformation') { +                    resolve(params); +                } +            } +        ); +        api.broadcastTab('rootPopupRequestInformationBroadcast'); +        const {popupId, frameId: parentFrameId} = await rootPopupInformationPromise; + +        const popup = new PopupProxy(popupId, 0, null, parentFrameId, this._frameOffsetForwarder); +        popup.on('offsetNotFound', () => { +            this._allowRootFramePopupProxy = false; +            this._updatePopup(); +        }); +        await popup.prepare(); + +        return popup; +    } + +    _ignoreElements() { +        return this._popup === null || this._popup.isProxy() ? [] : [this._popup.getFrame()]; +    } + +    _ignorePoint(x, y) { +        return this._popup !== null && this._popup.containsPoint(x, y); +    } +      async _search(textSource, cause) {          await this._updatePendingOptions(); @@ -318,7 +447,7 @@ class Frontend {      _updateTextScannerEnabled() {          const enabled = (              this._options.general.enable && -            this._popup.depth <= this._options.scanning.popupNestingMaxDepth && +            this._depth <= this._options.scanning.popupNestingMaxDepth &&              !this._disabledOverride          );          this._enabledEventListeners.removeAllEventListeners(); @@ -342,27 +471,41 @@ class Frontend {          if (contentScale === this._contentScale) { return; }          this._contentScale = contentScale; -        this._popup.setContentScale(this._contentScale); +        if (this._popup !== null) { +            this._popup.setContentScale(this._contentScale); +        }          this._updatePopupPosition();      }      async _updatePopupPosition() {          const textSource = this._textScanner.getCurrentTextSource(); -        if (textSource !== null && await this._popup.isVisible()) { +        if ( +            textSource !== null && +            this._popup !== null && +            await this._popup.isVisible() +        ) {              this._showPopupContent(textSource, await this.getOptionsContext());          }      }      _broadcastRootPopupInformation() { -        if (!this._popup.isProxy() && this._popup.depth === 0 && this._popup.frameId === 0) { -            api.broadcastTab('rootPopupInformation', {popupId: this._popup.id, frameId: this._popup.frameId}); +        if ( +            this._popup !== null && +            !this._popup.isProxy() && +            this._depth === 0 && +            this._frameId === 0 +        ) { +            api.broadcastTab('rootPopupInformation', { +                popupId: this._popup.id, +                frameId: this._frameId +            });          }      }      _broadcastDocumentInformation(uniqueId) {          api.broadcastTab('documentInformationBroadcast', {              uniqueId, -            frameId: this._popup.frameId, +            frameId: this._frameId,              title: document.title          });      } diff --git a/ext/fg/js/popup-factory.js b/ext/fg/js/popup-factory.js index b5997253..4edda91f 100644 --- a/ext/fg/js/popup-factory.js +++ b/ext/fg/js/popup-factory.js @@ -39,8 +39,7 @@ class PopupFactory {              ['showContent',        {async: true,  handler: this._onApiShowContent.bind(this)}],              ['setCustomCss',       {async: false, handler: this._onApiSetCustomCss.bind(this)}],              ['clearAutoPlayTimer', {async: false, handler: this._onApiClearAutoPlayTimer.bind(this)}], -            ['setContentScale',    {async: false, handler: this._onApiSetContentScale.bind(this)}], -            ['getUrl',             {async: false, handler: this._onApiGetUrl.bind(this)}] +            ['setContentScale',    {async: false, handler: this._onApiSetContentScale.bind(this)}]          ]);      } @@ -147,10 +146,6 @@ class PopupFactory {          return popup.setContentScale(scale);      } -    _onApiGetUrl() { -        return window.location.href; -    } -      // Private functions      _getPopup(id) { diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index 14ddcafb..a6602eae 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -104,10 +104,6 @@ class PopupProxy extends EventDispatcher {          this._invoke('setContentScale', {id: this._id, scale});      } -    async getUrl() { -        return await this._invoke('getUrl', {}); -    } -      // Private      _invoke(action, params={}) { diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index af24989f..4394a965 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -415,16 +415,7 @@ class Popup {              return;          } -        const fullscreenEvents = [ -            'fullscreenchange', -            'MSFullscreenChange', -            'mozfullscreenchange', -            'webkitfullscreenchange' -        ]; -        const onFullscreenChanged = this._onFullscreenChanged.bind(this); -        for (const eventName of fullscreenEvents) { -            this._fullscreenEventListeners.addEventListener(document, eventName, onFullscreenChanged, false); -        } +        DOM.addFullscreenChangeEventListener(this._onFullscreenChanged.bind(this), this._fullscreenEventListeners);      }      _onFullscreenChanged() { diff --git a/ext/mixed/js/dom.js b/ext/mixed/js/dom.js index 05764443..59fea9f6 100644 --- a/ext/mixed/js/dom.js +++ b/ext/mixed/js/dom.js @@ -77,6 +77,24 @@ class DOM {          return (typeof key === 'string' ? (key.length === 1 ? key.toUpperCase() : key) : '');      } +    static addFullscreenChangeEventListener(onFullscreenChanged, eventListenerCollection=null) { +        const target = document; +        const options = false; +        const fullscreenEventNames = [ +            'fullscreenchange', +            'MSFullscreenChange', +            'mozfullscreenchange', +            'webkitfullscreenchange' +        ]; +        for (const eventName of fullscreenEventNames) { +            if (eventListenerCollection === null) { +                target.addEventListener(eventName, onFullscreenChanged, options); +            } else { +                eventListenerCollection.addEventListener(target, eventName, onFullscreenChanged, options); +            } +        } +    } +      static getFullscreenElement() {          return (              document.fullscreenElement || diff --git a/ext/mixed/js/text-scanner.js b/ext/mixed/js/text-scanner.js index fb275452..7c705fc8 100644 --- a/ext/mixed/js/text-scanner.js +++ b/ext/mixed/js/text-scanner.js @@ -28,6 +28,7 @@ class TextScanner extends EventDispatcher {          this._ignorePoint = ignorePoint;          this._search = search; +        this._isPrepared = false;          this._ignoreNodes = null;          this._causeCurrent = null; @@ -69,10 +70,15 @@ class TextScanner extends EventDispatcher {          return this._causeCurrent;      } +    prepare() { +        this._isPrepared = true; +        this.setEnabled(this._enabled); +    } +      setEnabled(enabled) {          this._eventListeners.removeAllEventListeners();          this._enabled = enabled; -        if (this._enabled) { +        if (this._enabled && this._isPrepared) {              this._hookEvents();          } else {              this.clearSelection(true); |