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();
+ try {
+ 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();
+ 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);
+ const {queryParams: {query='', mode=''}} = parseUrl(window.location.href);
- document.documentElement.dataset.searchMode = mode;
+ document.documentElement.dataset.searchMode = mode;
- if (this.options.general.enableWanakana === true) {
- this.wanakanaEnable.checked = true;
- wanakana.bind(this.query);
- } else {
- this.wanakanaEnable.checked = false;
- }
-
- 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));
-
- 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._isPrepared = true;
- } catch (e) {
- this.onError(e);
+ if (this.options.general.enableWanakana === true) {
+ this.wanakanaEnable.checked = true;
+ wanakana.bind(this.query);
+ } else {
+ this.wanakanaEnable.checked = false;
}
+
+ 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));
+
+ 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();
+
+ await this._prepareNestedPopups();
+
+ 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 @@
+
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();
+ try {
+ 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
- };
-
- 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;
- }
- 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();
+ const {frameId} = await api.frameInformationGet();
+ if (typeof frameId !== 'number') {
+ throw new Error('Failed to get frameId');
}
- 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;
- }
- }
+ const popupFactory = new PopupFactory(frameId);
+ await popupFactory.prepare();
- 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 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;
}
- async prepare() {
- 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)
- }
-
- window.addEventListener('resize', this._onResize.bind(this), false);
-
- 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));
- }
-
- 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._textScanner.on('clearSelection', this._onClearSelection.bind(this));
- this._textScanner.on('activeModifiersChanged', this._onActiveModifiersChanged.bind(this));
-
- this._updateContentScale();
- this._broadcastRootPopupInformation();
- } catch (e) {
- yomichan.logError(e);
- }
+ get popup() {
+ return this._popup;
}
- async setPopup(popup) {
- this._textScanner.clearSelection(true);
- this._popup = popup;
- await popup.setOptionsContext(await this.getOptionsContext(), this._id);
+ async prepare() {
+ this._frameOffsetForwarder.prepare();
+
+ 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)
+ }
+
+ this._textScanner.prepare();
+
+ window.addEventListener('resize', this._onResize.bind(this), false);
+ DOM.addFullscreenChangeEventListener(this._updatePopup.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));
+ }
+
+ 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._textScanner.on('clearSelection', this._onClearSelection.bind(this));
+ this._textScanner.on('activeModifiersChanged', this._onActiveModifiersChanged.bind(this));
+
+ 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);