summaryrefslogtreecommitdiff
path: root/ext/fg
diff options
context:
space:
mode:
Diffstat (limited to 'ext/fg')
-rw-r--r--ext/fg/js/content-script-main.js152
-rw-r--r--ext/fg/js/float-main.js43
-rw-r--r--ext/fg/js/float.js65
-rw-r--r--ext/fg/js/frontend.js231
-rw-r--r--ext/fg/js/popup-factory.js7
-rw-r--r--ext/fg/js/popup-proxy.js4
-rw-r--r--ext/fg/js/popup.js11
7 files changed, 275 insertions, 238 deletions
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() {