yomichan/ext/fg/js/frame-offset-forwarder.js

146 lines
4.6 KiB
JavaScript
Raw Normal View History

/*
* Copyright (C) 2020 Yomichan Authors
*
* 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
2020-04-11 00:00:18 +00:00
* apiBroadcastTab
*/
class FrameOffsetForwarder {
constructor() {
2020-03-22 01:29:09 +00:00
this._started = false;
2020-04-18 13:48:49 +00:00
this._frameCache = new Set();
2020-03-22 01:29:09 +00:00
this._forwardFrameOffset = (
window !== window.parent ?
this._forwardFrameOffsetParent.bind(this) :
2020-03-22 01:29:09 +00:00
this._forwardFrameOffsetOrigin.bind(this)
);
this._windowMessageHandlers = new Map([
2020-03-22 01:29:09 +00:00
['getFrameOffset', ({offset, uniqueId}, e) => this._onGetFrameOffset(offset, uniqueId, e)]
]);
2020-03-22 01:29:09 +00:00
}
2020-03-22 01:29:09 +00:00
start() {
if (this._started) { return; }
window.addEventListener('message', this.onMessage.bind(this), false);
2020-03-22 01:29:09 +00:00
this._started = true;
}
2020-03-22 12:11:43 +00:00
async getOffset() {
const uniqueId = yomichan.generateId(16);
2020-03-22 02:55:16 +00:00
const frameOffsetPromise = yomichan.getTemporaryListenerResult(
chrome.runtime.onMessage,
({action, params}, {resolve}) => {
if (action === 'frameOffset' && isObject(params) && params.uniqueId === uniqueId) {
resolve(params);
}
},
5000
2020-03-22 02:55:16 +00:00
);
window.parent.postMessage({
action: 'getFrameOffset',
params: {
uniqueId,
2020-03-22 12:11:43 +00:00
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) {
2020-04-18 13:48:49 +00:00
const sourceFrame = this._findFrameWithContentWindow(e.source);
if (sourceFrame === null) {
2020-04-18 13:48:49 +00:00
// closed shadow root etc.
this._forwardFrameOffsetOrigin(null, uniqueId);
return;
}
const [forwardedX, forwardedY] = offset;
const {x, y} = sourceFrame.getBoundingClientRect();
offset = [forwardedX + x, forwardedY + y];
this._forwardFrameOffset(offset, uniqueId);
}
2020-04-18 13:48:49 +00:00
_findFrameWithContentWindow(contentWindow) {
2020-04-18 14:18:33 +00:00
const elementSources = [
() => [...this._frameCache],
2020-04-18 13:48:49 +00:00
// will contain duplicates, but frame elements are cheap to handle
2020-04-18 14:18:33 +00:00
() => [...document.querySelectorAll('frame, iframe:not(.yomichan-float)')],
() => [document.documentElement]
2020-04-18 13:48:49 +00:00
];
2020-04-18 14:18:33 +00:00
const getMoreElements = () => {
while (true) {
const source = elementSources.shift();
if (source) {
const elements = source();
if (elements.length === 0) { continue; }
return elements;
}
return [];
}
};
const elements = [];
2020-04-18 13:48:49 +00:00
const ELEMENT_NODE = Node.ELEMENT_NODE;
2020-04-18 14:18:33 +00:00
while (elements.length > 0 || elements.push(...getMoreElements())) {
2020-04-18 13:48:49 +00:00
const element = elements.shift();
if (element.contentWindow === contentWindow) {
this._frameCache.add(element);
return element;
}
const shadowRoot = element.shadowRoot;
if (shadowRoot) {
for (const child of shadowRoot.children) {
if (child.nodeType === ELEMENT_NODE) {
elements.push(child);
}
}
}
for (const child of element.children) {
if (child.nodeType === ELEMENT_NODE) {
elements.push(child);
}
}
}
return null;
}
_forwardFrameOffsetParent(offset, uniqueId) {
window.parent.postMessage({action: 'getFrameOffset', params: {offset, uniqueId}}, '*');
}
_forwardFrameOffsetOrigin(offset, uniqueId) {
2020-04-11 00:00:18 +00:00
apiBroadcastTab('frameOffset', {offset, uniqueId});
}
}