Merge pull request #417 from siikamiika/iframe-popups-2
Show iframe popups on root page
This commit is contained in:
commit
3df78904cf
@ -108,7 +108,8 @@
|
|||||||
"enableClipboardMonitor",
|
"enableClipboardMonitor",
|
||||||
"showPitchAccentDownstepNotation",
|
"showPitchAccentDownstepNotation",
|
||||||
"showPitchAccentPositionNotation",
|
"showPitchAccentPositionNotation",
|
||||||
"showPitchAccentGraph"
|
"showPitchAccentGraph",
|
||||||
|
"showIframePopupsInRootFrame"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"enable": {
|
"enable": {
|
||||||
@ -242,6 +243,10 @@
|
|||||||
"showPitchAccentGraph": {
|
"showPitchAccentGraph": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": false
|
"default": false
|
||||||
|
},
|
||||||
|
"showIframePopupsInRootFrame": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -127,7 +127,8 @@ function profileOptionsCreateDefaults() {
|
|||||||
enableClipboardMonitor: false,
|
enableClipboardMonitor: false,
|
||||||
showPitchAccentDownstepNotation: true,
|
showPitchAccentDownstepNotation: true,
|
||||||
showPitchAccentPositionNotation: true,
|
showPitchAccentPositionNotation: true,
|
||||||
showPitchAccentGraph: false
|
showPitchAccentGraph: false,
|
||||||
|
showIframePopupsInRootFrame: false
|
||||||
},
|
},
|
||||||
|
|
||||||
audio: {
|
audio: {
|
||||||
|
@ -35,6 +35,7 @@ async function searchFrontendSetup() {
|
|||||||
const scriptSrcs = [
|
const scriptSrcs = [
|
||||||
'/mixed/js/text-scanner.js',
|
'/mixed/js/text-scanner.js',
|
||||||
'/fg/js/frontend-api-receiver.js',
|
'/fg/js/frontend-api-receiver.js',
|
||||||
|
'/fg/js/frame-offset-forwarder.js',
|
||||||
'/fg/js/popup.js',
|
'/fg/js/popup.js',
|
||||||
'/fg/js/popup-proxy-host.js',
|
'/fg/js/popup-proxy-host.js',
|
||||||
'/fg/js/frontend.js',
|
'/fg/js/frontend.js',
|
||||||
|
@ -87,6 +87,7 @@ async function formRead(options) {
|
|||||||
options.general.showPitchAccentDownstepNotation = $('#show-pitch-accent-downstep-notation').prop('checked');
|
options.general.showPitchAccentDownstepNotation = $('#show-pitch-accent-downstep-notation').prop('checked');
|
||||||
options.general.showPitchAccentPositionNotation = $('#show-pitch-accent-position-notation').prop('checked');
|
options.general.showPitchAccentPositionNotation = $('#show-pitch-accent-position-notation').prop('checked');
|
||||||
options.general.showPitchAccentGraph = $('#show-pitch-accent-graph').prop('checked');
|
options.general.showPitchAccentGraph = $('#show-pitch-accent-graph').prop('checked');
|
||||||
|
options.general.showIframePopupsInRootFrame = $('#show-iframe-popups-in-root-frame').prop('checked');
|
||||||
options.general.popupTheme = $('#popup-theme').val();
|
options.general.popupTheme = $('#popup-theme').val();
|
||||||
options.general.popupOuterTheme = $('#popup-outer-theme').val();
|
options.general.popupOuterTheme = $('#popup-outer-theme').val();
|
||||||
options.general.customPopupCss = $('#custom-popup-css').val();
|
options.general.customPopupCss = $('#custom-popup-css').val();
|
||||||
@ -167,6 +168,7 @@ async function formWrite(options) {
|
|||||||
$('#show-pitch-accent-downstep-notation').prop('checked', options.general.showPitchAccentDownstepNotation);
|
$('#show-pitch-accent-downstep-notation').prop('checked', options.general.showPitchAccentDownstepNotation);
|
||||||
$('#show-pitch-accent-position-notation').prop('checked', options.general.showPitchAccentPositionNotation);
|
$('#show-pitch-accent-position-notation').prop('checked', options.general.showPitchAccentPositionNotation);
|
||||||
$('#show-pitch-accent-graph').prop('checked', options.general.showPitchAccentGraph);
|
$('#show-pitch-accent-graph').prop('checked', options.general.showPitchAccentGraph);
|
||||||
|
$('#show-iframe-popups-in-root-frame').prop('checked', options.general.showIframePopupsInRootFrame);
|
||||||
$('#popup-theme').val(options.general.popupTheme);
|
$('#popup-theme').val(options.general.popupTheme);
|
||||||
$('#popup-outer-theme').val(options.general.popupOuterTheme);
|
$('#popup-outer-theme').val(options.general.popupOuterTheme);
|
||||||
$('#custom-popup-css').val(options.general.customPopupCss);
|
$('#custom-popup-css').val(options.general.customPopupCss);
|
||||||
|
@ -174,6 +174,10 @@
|
|||||||
<label><input type="checkbox" id="show-pitch-accent-graph"> Show graph for pitch accents</label>
|
<label><input type="checkbox" id="show-pitch-accent-graph"> Show graph for pitch accents</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="checkbox options-advanced">
|
||||||
|
<label><input type="checkbox" id="show-iframe-popups-in-root-frame"> Show iframe popups in root frame</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="checkbox options-advanced">
|
<div class="checkbox options-advanced">
|
||||||
<label><input type="checkbox" id="show-debug-info"> Show debug information</label>
|
<label><input type="checkbox" id="show-debug-info"> Show debug information</label>
|
||||||
</div>
|
</div>
|
||||||
|
102
ext/fg/js/frame-offset-forwarder.js
Normal file
102
ext/fg/js/frame-offset-forwarder.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Alex Yatskov <alex@foosoft.net>
|
||||||
|
* Author: Alex Yatskov <alex@foosoft.net>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global
|
||||||
|
* apiForward
|
||||||
|
*/
|
||||||
|
|
||||||
|
class FrameOffsetForwarder {
|
||||||
|
constructor() {
|
||||||
|
this._started = false;
|
||||||
|
|
||||||
|
this._forwardFrameOffset = (
|
||||||
|
window !== window.parent ?
|
||||||
|
this._forwardFrameOffsetParent.bind(this) :
|
||||||
|
this._forwardFrameOffsetOrigin.bind(this)
|
||||||
|
);
|
||||||
|
|
||||||
|
this._windowMessageHandlers = new Map([
|
||||||
|
['getFrameOffset', ({offset, uniqueId}, e) => this._onGetFrameOffset(offset, uniqueId, e)]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
if (this._started) { return; }
|
||||||
|
window.addEventListener('message', this.onMessage.bind(this), false);
|
||||||
|
this._started = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOffset() {
|
||||||
|
const uniqueId = yomichan.generateId(16);
|
||||||
|
|
||||||
|
const frameOffsetPromise = yomichan.getTemporaryListenerResult(
|
||||||
|
chrome.runtime.onMessage,
|
||||||
|
({action, params}, {resolve}) => {
|
||||||
|
if (action === 'frameOffset' && isObject(params) && params.uniqueId === uniqueId) {
|
||||||
|
resolve(params);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
5000
|
||||||
|
);
|
||||||
|
|
||||||
|
window.parent.postMessage({
|
||||||
|
action: 'getFrameOffset',
|
||||||
|
params: {
|
||||||
|
uniqueId,
|
||||||
|
offset: [0, 0]
|
||||||
|
}
|
||||||
|
}, '*');
|
||||||
|
|
||||||
|
const {offset} = await frameOffsetPromise;
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessage(e) {
|
||||||
|
const {action, params} = e.data;
|
||||||
|
const handler = this._windowMessageHandlers.get(action);
|
||||||
|
if (typeof handler !== 'function') { return; }
|
||||||
|
handler(params, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onGetFrameOffset(offset, uniqueId, e) {
|
||||||
|
let sourceFrame = null;
|
||||||
|
for (const frame of document.querySelectorAll('frame, iframe:not(.yomichan-float)')) {
|
||||||
|
if (frame.contentWindow !== e.source) { continue; }
|
||||||
|
sourceFrame = frame;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (sourceFrame === null) {
|
||||||
|
this._forwardFrameOffsetOrigin(null, uniqueId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [forwardedX, forwardedY] = offset;
|
||||||
|
const {x, y} = sourceFrame.getBoundingClientRect();
|
||||||
|
offset = [forwardedX + x, forwardedY + y];
|
||||||
|
|
||||||
|
this._forwardFrameOffset(offset, uniqueId);
|
||||||
|
}
|
||||||
|
|
||||||
|
_forwardFrameOffsetParent(offset, uniqueId) {
|
||||||
|
window.parent.postMessage({action: 'getFrameOffset', params: {offset, uniqueId}}, '*');
|
||||||
|
}
|
||||||
|
|
||||||
|
_forwardFrameOffsetOrigin(offset, uniqueId) {
|
||||||
|
apiForward('frameOffset', {offset, uniqueId});
|
||||||
|
}
|
||||||
|
}
|
@ -17,9 +17,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* global
|
/* global
|
||||||
|
* FrameOffsetForwarder
|
||||||
* Frontend
|
* Frontend
|
||||||
* PopupProxy
|
* PopupProxy
|
||||||
* PopupProxyHost
|
* PopupProxyHost
|
||||||
|
* apiForward
|
||||||
|
* apiOptionsGet
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
@ -28,10 +31,35 @@ async function main() {
|
|||||||
const data = window.frontendInitializationData || {};
|
const data = window.frontendInitializationData || {};
|
||||||
const {id, depth=0, parentFrameId, url, proxy=false} = data;
|
const {id, depth=0, parentFrameId, url, proxy=false} = data;
|
||||||
|
|
||||||
|
const optionsContext = {depth, url};
|
||||||
|
const options = await apiOptionsGet(optionsContext);
|
||||||
|
|
||||||
let popup;
|
let popup;
|
||||||
if (proxy) {
|
if (!proxy && (window !== window.parent) && options.general.showIframePopupsInRootFrame) {
|
||||||
|
const rootPopupInformationPromise = yomichan.getTemporaryListenerResult(
|
||||||
|
chrome.runtime.onMessage,
|
||||||
|
({action, params}, {resolve}) => {
|
||||||
|
if (action === 'rootPopupInformation') {
|
||||||
|
resolve(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
apiForward('rootPopupRequestInformationBroadcast');
|
||||||
|
const {popupId, frameId} = await rootPopupInformationPromise;
|
||||||
|
|
||||||
|
const frameOffsetForwarder = new FrameOffsetForwarder();
|
||||||
|
frameOffsetForwarder.start();
|
||||||
|
const getFrameOffset = frameOffsetForwarder.getOffset.bind(frameOffsetForwarder);
|
||||||
|
|
||||||
|
popup = new PopupProxy(popupId, 0, null, frameId, url, getFrameOffset);
|
||||||
|
await popup.prepare();
|
||||||
|
} else if (proxy) {
|
||||||
popup = new PopupProxy(null, depth + 1, id, parentFrameId, url);
|
popup = new PopupProxy(null, depth + 1, id, parentFrameId, url);
|
||||||
|
await popup.prepare();
|
||||||
} else {
|
} else {
|
||||||
|
const frameOffsetForwarder = new FrameOffsetForwarder();
|
||||||
|
frameOffsetForwarder.start();
|
||||||
|
|
||||||
const popupHost = new PopupProxyHost();
|
const popupHost = new PopupProxyHost();
|
||||||
await popupHost.prepare();
|
await popupHost.prepare();
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
/* global
|
/* global
|
||||||
* TextScanner
|
* TextScanner
|
||||||
|
* apiForward
|
||||||
* apiGetZoom
|
* apiGetZoom
|
||||||
* apiKanjiFind
|
* apiKanjiFind
|
||||||
* apiOptionsGet
|
* apiOptionsGet
|
||||||
@ -52,7 +53,8 @@ class Frontend extends TextScanner {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
this._runtimeMessageHandlers = new Map([
|
this._runtimeMessageHandlers = new Map([
|
||||||
['popupSetVisibleOverride', ({visible}) => { this.popup.setVisibleOverride(visible); }]
|
['popupSetVisibleOverride', ({visible}) => { this.popup.setVisibleOverride(visible); }],
|
||||||
|
['rootPopupRequestInformationBroadcast', () => { this._broadcastRootPopupInformation(); }]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,6 +78,7 @@ class Frontend extends TextScanner {
|
|||||||
chrome.runtime.onMessage.addListener(this.onRuntimeMessage.bind(this));
|
chrome.runtime.onMessage.addListener(this.onRuntimeMessage.bind(this));
|
||||||
|
|
||||||
this._updateContentScale();
|
this._updateContentScale();
|
||||||
|
this._broadcastRootPopupInformation();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.onError(e);
|
this.onError(e);
|
||||||
}
|
}
|
||||||
@ -255,6 +258,12 @@ class Frontend extends TextScanner {
|
|||||||
this._updatePopupPosition();
|
this._updatePopupPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_broadcastRootPopupInformation() {
|
||||||
|
if (!this.popup.isProxy() && this.popup.depth === 0) {
|
||||||
|
apiForward('rootPopupInformation', {popupId: this.popup.id, frameId: this.popup.frameId});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async _updatePopupPosition() {
|
async _updatePopupPosition() {
|
||||||
const textSource = this.getCurrentTextSource();
|
const textSource = this.getCurrentTextSource();
|
||||||
if (textSource !== null && await this.popup.isVisible()) {
|
if (textSource !== null && await this.popup.isVisible()) {
|
||||||
|
@ -26,17 +26,17 @@ class PopupProxyHost {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this._popups = new Map();
|
this._popups = new Map();
|
||||||
this._apiReceiver = null;
|
this._apiReceiver = null;
|
||||||
this._frameIdPromise = null;
|
this._frameId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public functions
|
// Public functions
|
||||||
|
|
||||||
async prepare() {
|
async prepare() {
|
||||||
this._frameIdPromise = apiFrameInformationGet();
|
const {frameId} = await apiFrameInformationGet();
|
||||||
const {frameId} = await this._frameIdPromise;
|
|
||||||
if (typeof frameId !== 'number') { return; }
|
if (typeof frameId !== 'number') { return; }
|
||||||
|
this._frameId = frameId;
|
||||||
|
|
||||||
this._apiReceiver = new FrontendApiReceiver(`popup-proxy-host#${frameId}`, new Map([
|
this._apiReceiver = new FrontendApiReceiver(`popup-proxy-host#${this._frameId}`, new Map([
|
||||||
['getOrCreatePopup', this._onApiGetOrCreatePopup.bind(this)],
|
['getOrCreatePopup', this._onApiGetOrCreatePopup.bind(this)],
|
||||||
['setOptions', this._onApiSetOptions.bind(this)],
|
['setOptions', this._onApiSetOptions.bind(this)],
|
||||||
['hide', this._onApiHide.bind(this)],
|
['hide', this._onApiHide.bind(this)],
|
||||||
@ -87,7 +87,7 @@ class PopupProxyHost {
|
|||||||
} else if (depth === null) {
|
} else if (depth === null) {
|
||||||
depth = 0;
|
depth = 0;
|
||||||
}
|
}
|
||||||
const popup = new Popup(id, depth, this._frameIdPromise);
|
const popup = new Popup(id, depth, this._frameId);
|
||||||
if (parent !== null) {
|
if (parent !== null) {
|
||||||
popup.setParent(parent);
|
popup.setParent(parent);
|
||||||
}
|
}
|
||||||
@ -95,7 +95,7 @@ class PopupProxyHost {
|
|||||||
return popup;
|
return popup;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message handlers
|
// API message handlers
|
||||||
|
|
||||||
async _onApiGetOrCreatePopup({id, parentId}) {
|
async _onApiGetOrCreatePopup({id, parentId}) {
|
||||||
const popup = this.getOrCreatePopup(id, parentId);
|
const popup = this.getOrCreatePopup(id, parentId);
|
||||||
|
@ -21,14 +21,18 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
class PopupProxy {
|
class PopupProxy {
|
||||||
constructor(id, depth, parentId, parentFrameId, url) {
|
constructor(id, depth, parentId, parentFrameId, url, getFrameOffset=null) {
|
||||||
this._parentId = parentId;
|
this._parentId = parentId;
|
||||||
this._parentFrameId = parentFrameId;
|
this._parentFrameId = parentFrameId;
|
||||||
this._id = id;
|
this._id = id;
|
||||||
this._idPromise = null;
|
|
||||||
this._depth = depth;
|
this._depth = depth;
|
||||||
this._url = url;
|
this._url = url;
|
||||||
this._apiSender = new FrontendApiSender();
|
this._apiSender = new FrontendApiSender();
|
||||||
|
this._getFrameOffset = getFrameOffset;
|
||||||
|
|
||||||
|
this._frameOffset = null;
|
||||||
|
this._frameOffsetPromise = null;
|
||||||
|
this._frameOffsetUpdatedAt = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public properties
|
// Public properties
|
||||||
@ -51,79 +55,63 @@ class PopupProxy {
|
|||||||
|
|
||||||
// Public functions
|
// Public functions
|
||||||
|
|
||||||
|
async prepare() {
|
||||||
|
const {id} = await this._invokeHostApi('getOrCreatePopup', {id: this._id, parentId: this._parentId});
|
||||||
|
this._id = id;
|
||||||
|
}
|
||||||
|
|
||||||
isProxy() {
|
isProxy() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async setOptions(options) {
|
async setOptions(options) {
|
||||||
const id = await this._getPopupId();
|
return await this._invokeHostApi('setOptions', {id: this._id, options});
|
||||||
return await this._invokeHostApi('setOptions', {id, options});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hide(changeFocus) {
|
hide(changeFocus) {
|
||||||
if (this._id === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._invokeHostApi('hide', {id: this._id, changeFocus});
|
this._invokeHostApi('hide', {id: this._id, changeFocus});
|
||||||
}
|
}
|
||||||
|
|
||||||
async isVisible() {
|
async isVisible() {
|
||||||
const id = await this._getPopupId();
|
return await this._invokeHostApi('isVisible', {id: this._id});
|
||||||
return await this._invokeHostApi('isVisible', {id});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setVisibleOverride(visible) {
|
setVisibleOverride(visible) {
|
||||||
if (this._id === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._invokeHostApi('setVisibleOverride', {id: this._id, visible});
|
this._invokeHostApi('setVisibleOverride', {id: this._id, visible});
|
||||||
}
|
}
|
||||||
|
|
||||||
async containsPoint(x, y) {
|
async containsPoint(x, y) {
|
||||||
if (this._id === null) {
|
if (this._getFrameOffset !== null) {
|
||||||
return false;
|
await this._updateFrameOffset();
|
||||||
|
[x, y] = this._applyFrameOffset(x, y);
|
||||||
}
|
}
|
||||||
return await this._invokeHostApi('containsPoint', {id: this._id, x, y});
|
return await this._invokeHostApi('containsPoint', {id: this._id, x, y});
|
||||||
}
|
}
|
||||||
|
|
||||||
async showContent(elementRect, writingMode, type=null, details=null) {
|
async showContent(elementRect, writingMode, type=null, details=null) {
|
||||||
const id = await this._getPopupId();
|
let {x, y, width, height} = elementRect;
|
||||||
elementRect = PopupProxy._convertDOMRectToJson(elementRect);
|
if (this._getFrameOffset !== null) {
|
||||||
return await this._invokeHostApi('showContent', {id, elementRect, writingMode, type, details});
|
await this._updateFrameOffset();
|
||||||
|
[x, y] = this._applyFrameOffset(x, y);
|
||||||
|
}
|
||||||
|
elementRect = {x, y, width, height};
|
||||||
|
return await this._invokeHostApi('showContent', {id: this._id, elementRect, writingMode, type, details});
|
||||||
}
|
}
|
||||||
|
|
||||||
async setCustomCss(css) {
|
async setCustomCss(css) {
|
||||||
const id = await this._getPopupId();
|
return await this._invokeHostApi('setCustomCss', {id: this._id, css});
|
||||||
return await this._invokeHostApi('setCustomCss', {id, css});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clearAutoPlayTimer() {
|
clearAutoPlayTimer() {
|
||||||
if (this._id === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._invokeHostApi('clearAutoPlayTimer', {id: this._id});
|
this._invokeHostApi('clearAutoPlayTimer', {id: this._id});
|
||||||
}
|
}
|
||||||
|
|
||||||
async setContentScale(scale) {
|
async setContentScale(scale) {
|
||||||
const id = await this._getPopupId();
|
this._invokeHostApi('setContentScale', {id: this._id, scale});
|
||||||
this._invokeHostApi('setContentScale', {id, scale});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private
|
// Private
|
||||||
|
|
||||||
_getPopupId() {
|
|
||||||
if (this._idPromise === null) {
|
|
||||||
this._idPromise = this._getPopupIdAsync();
|
|
||||||
}
|
|
||||||
return this._idPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
async _getPopupIdAsync() {
|
|
||||||
const {id} = await this._invokeHostApi('getOrCreatePopup', {id: this._id, parentId: this._parentId});
|
|
||||||
this._id = id;
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
_invokeHostApi(action, params={}) {
|
_invokeHostApi(action, params={}) {
|
||||||
if (typeof this._parentFrameId !== 'number') {
|
if (typeof this._parentFrameId !== 'number') {
|
||||||
return Promise.reject(new Error('Invalid frame'));
|
return Promise.reject(new Error('Invalid frame'));
|
||||||
@ -131,12 +119,42 @@ class PopupProxy {
|
|||||||
return this._apiSender.invoke(action, params, `popup-proxy-host#${this._parentFrameId}`);
|
return this._apiSender.invoke(action, params, `popup-proxy-host#${this._parentFrameId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
static _convertDOMRectToJson(domRect) {
|
async _updateFrameOffset() {
|
||||||
return {
|
const now = Date.now();
|
||||||
x: domRect.x,
|
const firstRun = this._frameOffsetUpdatedAt === null;
|
||||||
y: domRect.y,
|
const expired = firstRun || this._frameOffsetUpdatedAt < now - PopupProxy._frameOffsetExpireTimeout;
|
||||||
width: domRect.width,
|
if (this._frameOffsetPromise === null && !expired) { return; }
|
||||||
height: domRect.height
|
|
||||||
};
|
if (this._frameOffsetPromise !== null) {
|
||||||
|
if (firstRun) {
|
||||||
|
await this._frameOffsetPromise;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const promise = this._updateFrameOffsetInner(now);
|
||||||
|
if (firstRun) {
|
||||||
|
await promise;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _updateFrameOffsetInner(now) {
|
||||||
|
this._frameOffsetPromise = this._getFrameOffset();
|
||||||
|
try {
|
||||||
|
const offset = await this._frameOffsetPromise;
|
||||||
|
this._frameOffset = offset !== null ? offset : [0, 0];
|
||||||
|
this._frameOffsetUpdatedAt = now;
|
||||||
|
} catch (e) {
|
||||||
|
logError(e);
|
||||||
|
} finally {
|
||||||
|
this._frameOffsetPromise = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_applyFrameOffset(x, y) {
|
||||||
|
const [offsetX, offsetY] = this._frameOffset;
|
||||||
|
return [x + offsetX, y + offsetY];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PopupProxy._frameOffsetExpireTimeout = 1000;
|
||||||
|
@ -22,11 +22,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
class Popup {
|
class Popup {
|
||||||
constructor(id, depth, frameIdPromise) {
|
constructor(id, depth, frameId) {
|
||||||
this._id = id;
|
this._id = id;
|
||||||
this._depth = depth;
|
this._depth = depth;
|
||||||
this._frameIdPromise = frameIdPromise;
|
this._frameId = frameId;
|
||||||
this._frameId = null;
|
|
||||||
this._parent = null;
|
this._parent = null;
|
||||||
this._child = null;
|
this._child = null;
|
||||||
this._childrenSupported = true;
|
this._childrenSupported = true;
|
||||||
@ -69,6 +68,10 @@ class Popup {
|
|||||||
return this._depth;
|
return this._depth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get frameId() {
|
||||||
|
return this._frameId;
|
||||||
|
}
|
||||||
|
|
||||||
get url() {
|
get url() {
|
||||||
return window.location.href;
|
return window.location.href;
|
||||||
}
|
}
|
||||||
@ -193,25 +196,26 @@ class Popup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _createInjectPromise() {
|
async _createInjectPromise() {
|
||||||
try {
|
|
||||||
const {frameId} = await this._frameIdPromise;
|
|
||||||
if (typeof frameId === 'number') {
|
|
||||||
this._frameId = frameId;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// NOP
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._messageToken === null) {
|
if (this._messageToken === null) {
|
||||||
this._messageToken = await apiGetMessageToken();
|
this._messageToken = await apiGetMessageToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
const popupPreparedPromise = yomichan.getTemporaryListenerResult(
|
||||||
|
chrome.runtime.onMessage,
|
||||||
|
({action, params}, {resolve}) => {
|
||||||
|
if (
|
||||||
|
action === 'popupPrepareCompleted' &&
|
||||||
|
isObject(params) &&
|
||||||
|
params.targetPopupId === this._id
|
||||||
|
) {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const parentFrameId = (typeof this._frameId === 'number' ? this._frameId : null);
|
const parentFrameId = (typeof this._frameId === 'number' ? this._frameId : null);
|
||||||
this._container.setAttribute('src', chrome.runtime.getURL('/fg/float.html'));
|
this._container.setAttribute('src', chrome.runtime.getURL('/fg/float.html'));
|
||||||
this._container.addEventListener('load', () => {
|
this._container.addEventListener('load', () => {
|
||||||
this._listenForDisplayPrepareCompleted(resolve);
|
|
||||||
|
|
||||||
this._invokeApi('prepare', {
|
this._invokeApi('prepare', {
|
||||||
popupInfo: {
|
popupInfo: {
|
||||||
id: this._id,
|
id: this._id,
|
||||||
@ -226,7 +230,8 @@ class Popup {
|
|||||||
this._observeFullscreen(true);
|
this._observeFullscreen(true);
|
||||||
this._onFullscreenChanged();
|
this._onFullscreenChanged();
|
||||||
this._injectStyles();
|
this._injectStyles();
|
||||||
});
|
|
||||||
|
return popupPreparedPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _injectStyles() {
|
async _injectStyles() {
|
||||||
@ -361,22 +366,6 @@ class Popup {
|
|||||||
contentWindow.postMessage({action, params, token}, this._targetOrigin);
|
contentWindow.postMessage({action, params, token}, this._targetOrigin);
|
||||||
}
|
}
|
||||||
|
|
||||||
_listenForDisplayPrepareCompleted(resolve) {
|
|
||||||
const runtimeMessageCallback = ({action, params}, sender, callback) => {
|
|
||||||
if (
|
|
||||||
action === 'popupPrepareCompleted' &&
|
|
||||||
isObject(params) &&
|
|
||||||
params.targetPopupId === this._id
|
|
||||||
) {
|
|
||||||
chrome.runtime.onMessage.removeListener(runtimeMessageCallback);
|
|
||||||
callback();
|
|
||||||
resolve();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
chrome.runtime.onMessage.addListener(runtimeMessageCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
static _getFullscreenElement() {
|
static _getFullscreenElement() {
|
||||||
return (
|
return (
|
||||||
document.fullscreenElement ||
|
document.fullscreenElement ||
|
||||||
|
@ -23,9 +23,12 @@
|
|||||||
"mixed/js/api.js",
|
"mixed/js/api.js",
|
||||||
"mixed/js/text-scanner.js",
|
"mixed/js/text-scanner.js",
|
||||||
"fg/js/document.js",
|
"fg/js/document.js",
|
||||||
|
"fg/js/frontend-api-sender.js",
|
||||||
"fg/js/frontend-api-receiver.js",
|
"fg/js/frontend-api-receiver.js",
|
||||||
"fg/js/popup.js",
|
"fg/js/popup.js",
|
||||||
"fg/js/source.js",
|
"fg/js/source.js",
|
||||||
|
"fg/js/frame-offset-forwarder.js",
|
||||||
|
"fg/js/popup-proxy.js",
|
||||||
"fg/js/popup-proxy-host.js",
|
"fg/js/popup-proxy-host.js",
|
||||||
"fg/js/frontend.js",
|
"fg/js/frontend.js",
|
||||||
"fg/js/frontend-initialize.js"
|
"fg/js/frontend-initialize.js"
|
||||||
|
@ -278,11 +278,16 @@ const yomichan = (() => {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._isBackendPreparedResolve = null;
|
this._isBackendPreparedPromise = this.getTemporaryListenerResult(
|
||||||
this._isBackendPreparedPromise = new Promise((resolve) => (this._isBackendPreparedResolve = resolve));
|
chrome.runtime.onMessage,
|
||||||
|
({action}, {resolve}) => {
|
||||||
|
if (action === 'backendPrepared') {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
this._messageHandlers = new Map([
|
this._messageHandlers = new Map([
|
||||||
['backendPrepared', this._onBackendPrepared.bind(this)],
|
|
||||||
['getUrl', this._onMessageGetUrl.bind(this)],
|
['getUrl', this._onMessageGetUrl.bind(this)],
|
||||||
['optionsUpdated', this._onMessageOptionsUpdated.bind(this)],
|
['optionsUpdated', this._onMessageOptionsUpdated.bind(this)],
|
||||||
['zoomChanged', this._onMessageZoomChanged.bind(this)]
|
['zoomChanged', this._onMessageZoomChanged.bind(this)]
|
||||||
@ -312,6 +317,42 @@ const yomichan = (() => {
|
|||||||
this.trigger('orphaned', {error});
|
this.trigger('orphaned', {error});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTemporaryListenerResult(eventHandler, userCallback, timeout=null) {
|
||||||
|
if (!(
|
||||||
|
typeof eventHandler.addListener === 'function' &&
|
||||||
|
typeof eventHandler.removeListener === 'function'
|
||||||
|
)) {
|
||||||
|
throw new Error('Event handler type not supported');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const runtimeMessageCallback = ({action, params}, sender, sendResponse) => {
|
||||||
|
let timeoutId = null;
|
||||||
|
if (timeout !== null) {
|
||||||
|
timeoutId = window.setTimeout(() => {
|
||||||
|
timeoutId = null;
|
||||||
|
eventHandler.removeListener(runtimeMessageCallback);
|
||||||
|
reject(new Error(`Listener timed out in ${timeout} ms`));
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanupResolve = (value) => {
|
||||||
|
if (timeoutId !== null) {
|
||||||
|
window.clearTimeout(timeoutId);
|
||||||
|
timeoutId = null;
|
||||||
|
}
|
||||||
|
eventHandler.removeListener(runtimeMessageCallback);
|
||||||
|
sendResponse();
|
||||||
|
resolve(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
userCallback({action, params}, {resolve: cleanupResolve, sender});
|
||||||
|
};
|
||||||
|
|
||||||
|
eventHandler.addListener(runtimeMessageCallback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Private
|
// Private
|
||||||
|
|
||||||
_onMessage({action, params}, sender, callback) {
|
_onMessage({action, params}, sender, callback) {
|
||||||
@ -323,10 +364,6 @@ const yomichan = (() => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onBackendPrepared() {
|
|
||||||
this._isBackendPreparedResolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
_onMessageGetUrl() {
|
_onMessageGetUrl() {
|
||||||
return {url: window.location.href};
|
return {url: window.location.href};
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,8 @@ require('fake-indexeddb/auto');
|
|||||||
const chrome = {
|
const chrome = {
|
||||||
runtime: {
|
runtime: {
|
||||||
onMessage: {
|
onMessage: {
|
||||||
addListener() { /* NOP */ }
|
addListener() { /* NOP */ },
|
||||||
|
removeListener() { /* NOP */ }
|
||||||
},
|
},
|
||||||
getURL(path2) {
|
getURL(path2) {
|
||||||
return url.pathToFileURL(path.join(__dirname, '..', 'ext', path2.replace(/^\//, '')));
|
return url.pathToFileURL(path.join(__dirname, '..', 'ext', path2.replace(/^\//, '')));
|
||||||
|
Loading…
Reference in New Issue
Block a user