aboutsummaryrefslogtreecommitdiff
path: root/ext/fg
diff options
context:
space:
mode:
Diffstat (limited to 'ext/fg')
-rw-r--r--ext/fg/float.html2
-rw-r--r--ext/fg/js/float.js38
-rw-r--r--ext/fg/js/frontend-api-sender.js10
-rw-r--r--ext/fg/js/frontend-initialize.js11
-rw-r--r--ext/fg/js/frontend.js2
-rw-r--r--ext/fg/js/popup-proxy-host.js64
-rw-r--r--ext/fg/js/popup-proxy.js6
-rw-r--r--ext/fg/js/popup.js109
-rw-r--r--ext/fg/js/source.js14
9 files changed, 161 insertions, 95 deletions
diff --git a/ext/fg/float.html b/ext/fg/float.html
index bec5ae68..082755f5 100644
--- a/ext/fg/float.html
+++ b/ext/fg/float.html
@@ -35,7 +35,7 @@
<h1>Yomichan Updated!</h1>
<p>
The Yomichan extension has been updated to a new version! In order to continue
- viewing definitions on this page you must reload this tab or restart your browser.
+ viewing definitions on this page, you must reload this tab or restart your browser.
</p>
</div>
</div>
diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js
index d31b8336..440a9731 100644
--- a/ext/fg/js/float.js
+++ b/ext/fg/js/float.js
@@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-/*global popupNestedInitialize, Display*/
+/*global popupNestedInitialize, apiForward, Display*/
class DisplayFloat extends Display {
constructor() {
@@ -29,11 +29,31 @@ class DisplayFloat extends Display {
};
this._orphaned = false;
+ this._prepareInvoked = false;
yomichan.on('orphaned', () => this.onOrphaned());
window.addEventListener('message', (e) => this.onMessage(e), false);
}
+ async prepare(options, popupInfo, url, childrenSupported, scale, uniqueId) {
+ if (this._prepareInvoked) { return; }
+ this._prepareInvoked = true;
+
+ await super.prepare(options);
+
+ const {id, depth, parentFrameId} = popupInfo;
+ this.optionsContext.depth = depth;
+ this.optionsContext.url = url;
+
+ if (childrenSupported) {
+ popupNestedInitialize(id, depth, parentFrameId, url);
+ }
+
+ this.setContentScale(scale);
+
+ apiForward('popupPrepareCompleted', {uniqueId});
+ }
+
onError(error) {
if (this._orphaned) {
this.setContent('orphaned');
@@ -93,20 +113,6 @@ class DisplayFloat extends Display {
setContentScale(scale) {
document.body.style.fontSize = `${scale}em`;
}
-
- async initialize(options, popupInfo, url, childrenSupported, scale) {
- await super.initialize(options);
-
- const {id, depth, parentFrameId} = popupInfo;
- this.optionsContext.depth = depth;
- this.optionsContext.url = url;
-
- if (childrenSupported) {
- popupNestedInitialize(id, depth, parentFrameId, url);
- }
-
- this.setContentScale(scale);
- }
}
DisplayFloat._onKeyDownHandlers = new Map([
@@ -123,7 +129,7 @@ DisplayFloat._messageHandlers = new Map([
['setContent', (self, {type, details}) => self.setContent(type, details)],
['clearAutoPlayTimer', (self) => self.clearAutoPlayTimer()],
['setCustomCss', (self, {css}) => self.setCustomCss(css)],
- ['initialize', (self, {options, popupInfo, url, childrenSupported, scale}) => self.initialize(options, popupInfo, url, childrenSupported, scale)],
+ ['prepare', (self, {options, popupInfo, url, childrenSupported, scale, uniqueId}) => self.prepare(options, popupInfo, url, childrenSupported, scale, uniqueId)],
['setContentScale', (self, {scale}) => self.setContentScale(scale)]
]);
diff --git a/ext/fg/js/frontend-api-sender.js b/ext/fg/js/frontend-api-sender.js
index 93c2e593..8dc6aaf3 100644
--- a/ext/fg/js/frontend-api-sender.js
+++ b/ext/fg/js/frontend-api-sender.js
@@ -19,7 +19,7 @@
class FrontendApiSender {
constructor() {
- this.senderId = FrontendApiSender.generateId(16);
+ this.senderId = yomichan.generateId(16);
this.ackTimeout = 3000; // 3 seconds
this.responseTimeout = 10000; // 10 seconds
this.callbacks = new Map();
@@ -123,12 +123,4 @@ class FrontendApiSender {
info.timer = null;
info.reject(new Error(reason));
}
-
- static generateId(length) {
- let id = '';
- for (let i = 0; i < length; ++i) {
- id += Math.floor(Math.random() * 256).toString(16).padStart(2, '0');
- }
- return id;
- }
}
diff --git a/ext/fg/js/frontend-initialize.js b/ext/fg/js/frontend-initialize.js
index c32e97d4..54b874f2 100644
--- a/ext/fg/js/frontend-initialize.js
+++ b/ext/fg/js/frontend-initialize.js
@@ -22,13 +22,16 @@ async function main() {
const data = window.frontendInitializationData || {};
const {id, depth=0, parentFrameId, ignoreNodes, url, proxy=false} = data;
- let popupHost = null;
- if (!proxy) {
- popupHost = new PopupProxyHost();
+ let popup;
+ if (proxy) {
+ popup = new PopupProxy(null, depth + 1, id, parentFrameId, url);
+ } else {
+ const popupHost = new PopupProxyHost();
await popupHost.prepare();
+
+ popup = popupHost.getOrCreatePopup();
}
- const popup = proxy ? new PopupProxy(depth + 1, id, parentFrameId, url) : popupHost.createPopup(null, depth);
const frontend = new Frontend(popup, ignoreNodes);
await frontend.prepare();
}
diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js
index 3611d44e..67045241 100644
--- a/ext/fg/js/frontend.js
+++ b/ext/fg/js/frontend.js
@@ -56,7 +56,7 @@ class Frontend extends TextScanner {
}
yomichan.on('orphaned', () => this.onOrphaned());
- yomichan.on('optionsUpdate', () => this.updateOptions());
+ yomichan.on('optionsUpdated', () => this.updateOptions());
yomichan.on('zoomChanged', (e) => this.onZoomChanged(e));
chrome.runtime.onMessage.addListener(this.onRuntimeMessage.bind(this));
diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js
index 98729796..e55801ff 100644
--- a/ext/fg/js/popup-proxy-host.js
+++ b/ext/fg/js/popup-proxy-host.js
@@ -34,7 +34,7 @@ class PopupProxyHost {
if (typeof frameId !== 'number') { return; }
this._apiReceiver = new FrontendApiReceiver(`popup-proxy-host#${frameId}`, new Map([
- ['createNestedPopup', ({parentId}) => this._onApiCreateNestedPopup(parentId)],
+ ['getOrCreatePopup', ({id, parentId}) => this._onApiGetOrCreatePopup(id, parentId)],
['setOptions', ({id, options}) => this._onApiSetOptions(id, options)],
['hide', ({id, changeFocus}) => this._onApiHide(id, changeFocus)],
['isVisible', ({id}) => this._onApiIsVisibleAsync(id)],
@@ -47,14 +47,51 @@ class PopupProxyHost {
]));
}
- createPopup(parentId, depth) {
- return this._createPopupInternal(parentId, depth).popup;
+ getOrCreatePopup(id=null, parentId=null) {
+ // Find by existing id
+ if (id !== null) {
+ const popup = this._popups.get(id);
+ if (typeof popup !== 'undefined') {
+ return popup;
+ }
+ }
+
+ // Find by existing parent id
+ let parent = null;
+ if (parentId !== null) {
+ parent = this._popups.get(parentId);
+ if (typeof parent !== 'undefined') {
+ const popup = parent.child;
+ if (popup !== null) {
+ return popup;
+ }
+ } else {
+ parent = null;
+ }
+ }
+
+ // New unique id
+ if (id === null) {
+ id = this._nextId++;
+ }
+
+ // Create new popup
+ const depth = (parent !== null ? parent.depth + 1 : 0);
+ const popup = new Popup(id, depth, this._frameIdPromise);
+ if (parent !== null) {
+ popup.setParent(parent);
+ }
+ this._popups.set(id, popup);
+ return popup;
}
// Message handlers
- async _onApiCreateNestedPopup(parentId) {
- return this._createPopupInternal(parentId, 0).id;
+ async _onApiGetOrCreatePopup(id, parentId) {
+ const popup = this.getOrCreatePopup(id, parentId);
+ return {
+ id: popup.id
+ };
}
async _onApiSetOptions(id, options) {
@@ -106,25 +143,10 @@ class PopupProxyHost {
// Private functions
- _createPopupInternal(parentId, depth) {
- const parent = (typeof parentId === 'string' && this._popups.has(parentId) ? this._popups.get(parentId) : null);
- const id = `${this._nextId}`;
- if (parent !== null) {
- depth = parent.depth + 1;
- }
- ++this._nextId;
- const popup = new Popup(id, depth, this._frameIdPromise);
- if (parent !== null) {
- popup.setParent(parent);
- }
- this._popups.set(id, popup);
- return {popup, id};
- }
-
_getPopup(id) {
const popup = this._popups.get(id);
if (typeof popup === 'undefined') {
- throw new Error('Invalid popup ID');
+ throw new Error(`Invalid popup ID ${id}`);
}
return popup;
}
diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js
index db6dffb1..093cdd2e 100644
--- a/ext/fg/js/popup-proxy.js
+++ b/ext/fg/js/popup-proxy.js
@@ -19,10 +19,10 @@
/*global FrontendApiSender*/
class PopupProxy {
- constructor(depth, parentId, parentFrameId, url) {
+ constructor(id, depth, parentId, parentFrameId, url) {
this._parentId = parentId;
this._parentFrameId = parentFrameId;
- this._id = null;
+ this._id = id;
this._idPromise = null;
this._depth = depth;
this._url = url;
@@ -113,7 +113,7 @@ class PopupProxy {
}
async _getPopupIdAsync() {
- const id = await this._invokeHostApi('createNestedPopup', {parentId: this._parentId});
+ const {id} = await this._invokeHostApi('getOrCreatePopup', {id: this._id, parentId: this._parentId});
this._id = id;
return id;
}
diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js
index 0b142dda..de05f9f5 100644
--- a/ext/fg/js/popup.js
+++ b/ext/fg/js/popup.js
@@ -28,8 +28,6 @@ class Popup {
this._child = null;
this._childrenSupported = true;
this._injectPromise = null;
- this._isInjected = false;
- this._isInjectedAndLoaded = false;
this._visible = false;
this._visibleOverride = null;
this._options = null;
@@ -41,19 +39,28 @@ class Popup {
this._container.className = 'yomichan-float';
this._container.addEventListener('mousedown', (e) => e.stopPropagation());
this._container.addEventListener('scroll', (e) => e.stopPropagation());
- this._container.setAttribute('src', chrome.runtime.getURL('/fg/float.html'));
this._container.style.width = '0px';
this._container.style.height = '0px';
+ this._fullscreenEventListeners = new EventListenerCollection();
+
this._updateVisibility();
}
// Public properties
+ get id() {
+ return this._id;
+ }
+
get parent() {
return this._parent;
}
+ get child() {
+ return this._child;
+ }
+
get depth() {
return this._depth;
}
@@ -118,16 +125,12 @@ class Popup {
}
clearAutoPlayTimer() {
- if (this._isInjectedAndLoaded) {
- this._invokeApi('clearAutoPlayTimer');
- }
+ this._invokeApi('clearAutoPlayTimer');
}
setContentScale(scale) {
this._contentScale = scale;
- if (this._isInjectedAndLoaded) {
- this._invokeApi('setContentScale', {scale});
- }
+ this._invokeApi('setContentScale', {scale});
}
// Popup-only public functions
@@ -147,7 +150,7 @@ class Popup {
}
isVisibleSync() {
- return this._isInjected && (this._visibleOverride !== null ? this._visibleOverride : this._visible);
+ return (this._visibleOverride !== null ? this._visibleOverride : this._visible);
}
updateTheme() {
@@ -226,8 +229,10 @@ class Popup {
return new Promise((resolve) => {
const parentFrameId = (typeof this._frameId === 'number' ? this._frameId : null);
this._container.addEventListener('load', () => {
- this._isInjectedAndLoaded = true;
- this._invokeApi('initialize', {
+ const uniqueId = yomichan.generateId(32);
+ Popup._listenForDisplayPrepareCompleted(uniqueId, resolve);
+
+ this._invokeApi('prepare', {
options: this._options,
popupInfo: {
id: this._id,
@@ -236,17 +241,47 @@ class Popup {
},
url: this.url,
childrenSupported: this._childrenSupported,
- scale: this._contentScale
+ scale: this._contentScale,
+ uniqueId
});
- resolve();
});
- this._observeFullscreen();
+ this._observeFullscreen(true);
this._onFullscreenChanged();
this.setCustomOuterCss(this._options.general.customPopupOuterCss, false);
- this._isInjected = true;
+ this._container.setAttribute('src', chrome.runtime.getURL('/fg/float.html'));
});
}
+ _observeFullscreen(observe) {
+ if (!observe) {
+ this._fullscreenEventListeners.removeAllEventListeners();
+ return;
+ }
+
+ if (this._fullscreenEventListeners.size > 0) {
+ // Already observing
+ return;
+ }
+
+ const fullscreenEvents = [
+ 'fullscreenchange',
+ 'MSFullscreenChange',
+ 'mozfullscreenchange',
+ 'webkitfullscreenchange'
+ ];
+ const onFullscreenChanged = () => this._onFullscreenChanged();
+ for (const eventName of fullscreenEvents) {
+ this._fullscreenEventListeners.addEventListener(document, eventName, onFullscreenChanged, false);
+ }
+ }
+
+ _onFullscreenChanged() {
+ const parent = (Popup._getFullscreenElement() || document.body || null);
+ if (parent !== null && this._container.parentNode !== parent) {
+ parent.appendChild(this._container);
+ }
+ }
+
async _show(elementRect, writingMode) {
await this._inject();
@@ -328,38 +363,36 @@ class Popup {
}
_invokeApi(action, params={}) {
- if (!this._isInjectedAndLoaded) {
- throw new Error('Frame not loaded');
- }
- this._container.contentWindow.postMessage({action, params}, '*');
- }
-
- _observeFullscreen() {
- const fullscreenEvents = [
- 'fullscreenchange',
- 'MSFullscreenChange',
- 'mozfullscreenchange',
- 'webkitfullscreenchange'
- ];
- for (const eventName of fullscreenEvents) {
- document.addEventListener(eventName, () => this._onFullscreenChanged(), false);
+ if (this._container.contentWindow) {
+ this._container.contentWindow.postMessage({action, params}, '*');
}
}
- _getFullscreenElement() {
+ static _getFullscreenElement() {
return (
document.fullscreenElement ||
document.msFullscreenElement ||
document.mozFullScreenElement ||
- document.webkitFullscreenElement
+ document.webkitFullscreenElement ||
+ null
);
}
- _onFullscreenChanged() {
- const parent = (this._getFullscreenElement() || document.body || null);
- if (parent !== null && this._container.parentNode !== parent) {
- parent.appendChild(this._container);
- }
+ static _listenForDisplayPrepareCompleted(uniqueId, resolve) {
+ const runtimeMessageCallback = ({action, params}, sender, callback) => {
+ if (
+ action === 'popupPrepareCompleted' &&
+ typeof params === 'object' &&
+ params !== null &&
+ params.uniqueId === uniqueId
+ ) {
+ chrome.runtime.onMessage.removeListener(runtimeMessageCallback);
+ callback();
+ resolve();
+ return false;
+ }
+ };
+ chrome.runtime.onMessage.addListener(runtimeMessageCallback);
}
static _getPositionForHorizontalText(elementRect, width, height, viewport, offsetScale, optionsGeneral) {
diff --git a/ext/fg/js/source.js b/ext/fg/js/source.js
index 11d3ff0e..fa785ec4 100644
--- a/ext/fg/js/source.js
+++ b/ext/fg/js/source.js
@@ -82,7 +82,11 @@ class TextSourceRange {
}
equals(other) {
- if (other === null) {
+ if (!(
+ typeof other === 'object' &&
+ other !== null &&
+ other instanceof TextSourceRange
+ )) {
return false;
}
if (this.imposterSourceElement !== null) {
@@ -409,6 +413,12 @@ class TextSourceElement {
}
equals(other) {
- return other && other.element === this.element && other.content === this.content;
+ return (
+ typeof other === 'object' &&
+ other !== null &&
+ other instanceof TextSourceElement &&
+ other.element === this.element &&
+ other.content === this.content
+ );
}
}