Fix messaging issues when iframes are present in the document
This commit is contained in:
parent
42ec3e2a43
commit
53aad0bef6
@ -205,3 +205,8 @@ function apiForward(action, params, sender) {
|
||||
chrome.tabs.sendMessage(tabId, {action, params}, (response) => resolve(response));
|
||||
});
|
||||
}
|
||||
|
||||
function apiFrameInformationGet(sender) {
|
||||
const frameId = sender.frameId;
|
||||
return Promise.resolve({frameId});
|
||||
}
|
||||
|
@ -127,6 +127,10 @@ class Backend {
|
||||
|
||||
forward: ({action, params}) => {
|
||||
forward(apiForward(action, params, sender), callback);
|
||||
},
|
||||
|
||||
frameInformationGet: () => {
|
||||
forward(apiFrameInformationGet(sender), callback);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -25,8 +25,8 @@ async function searchFrontendSetup() {
|
||||
'/fg/js/api.js',
|
||||
'/fg/js/frontend-api-receiver.js',
|
||||
'/fg/js/popup.js',
|
||||
'/fg/js/popup-proxy-host.js',
|
||||
'/fg/js/util.js',
|
||||
'/fg/js/popup-proxy-host.js',
|
||||
'/fg/js/frontend.js'
|
||||
];
|
||||
for (const src of scriptSrcs) {
|
||||
|
@ -64,3 +64,7 @@ function apiScreenshotGet(options) {
|
||||
function apiForward(action, params) {
|
||||
return utilInvoke('forward', {action, params});
|
||||
}
|
||||
|
||||
function apiFrameInformationGet() {
|
||||
return utilInvoke('frameInformationGet');
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ class FrontendApiReceiver {
|
||||
port.onMessage.addListener(this.onMessage.bind(this, port));
|
||||
}
|
||||
|
||||
onMessage(port, {id, action, params, target}) {
|
||||
onMessage(port, {id, action, params, target, senderId}) {
|
||||
if (
|
||||
target !== this.source ||
|
||||
!this.handlers.hasOwnProperty(action)
|
||||
@ -39,24 +39,24 @@ class FrontendApiReceiver {
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendAck(port, id);
|
||||
this.sendAck(port, id, senderId);
|
||||
|
||||
const handler = this.handlers[action];
|
||||
handler(params).then(
|
||||
result => {
|
||||
this.sendResult(port, id, {result});
|
||||
this.sendResult(port, id, senderId, {result});
|
||||
},
|
||||
e => {
|
||||
const error = typeof e.toString === 'function' ? e.toString() : e;
|
||||
this.sendResult(port, id, {error});
|
||||
this.sendResult(port, id, senderId, {error});
|
||||
});
|
||||
}
|
||||
|
||||
sendAck(port, id) {
|
||||
port.postMessage({type: 'ack', id});
|
||||
sendAck(port, id, senderId) {
|
||||
port.postMessage({type: 'ack', id, senderId});
|
||||
}
|
||||
|
||||
sendResult(port, id, data) {
|
||||
port.postMessage({type: 'result', id, data});
|
||||
sendResult(port, id, senderId, data) {
|
||||
port.postMessage({type: 'result', id, senderId, data});
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
class FrontendApiSender {
|
||||
constructor() {
|
||||
this.senderId = FrontendApiSender.generateId(16);
|
||||
this.ackTimeout = 3000; // 3 seconds
|
||||
this.responseTimeout = 10000; // 10 seconds
|
||||
this.callbacks = {};
|
||||
@ -43,11 +44,12 @@ class FrontendApiSender {
|
||||
this.callbacks[id] = info;
|
||||
info.timer = setTimeout(() => this.onError(id, 'Timeout (ack)'), this.ackTimeout);
|
||||
|
||||
this.port.postMessage({id, action, params, target});
|
||||
this.port.postMessage({id, action, params, target, senderId: this.senderId});
|
||||
});
|
||||
}
|
||||
|
||||
onMessage({type, id, data}) {
|
||||
onMessage({type, id, data, senderId}) {
|
||||
if (senderId !== this.senderId) { return; }
|
||||
switch (type) {
|
||||
case 'ack':
|
||||
this.onAck(id);
|
||||
@ -69,7 +71,7 @@ class FrontendApiSender {
|
||||
|
||||
onAck(id) {
|
||||
if (!this.callbacks.hasOwnProperty(id)) {
|
||||
console.warn(`ID ${id} not found`);
|
||||
console.warn(`ID ${id} not found for ack`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -42,14 +42,20 @@ class Frontend {
|
||||
const isNested = (currentUrl === floatUrl);
|
||||
|
||||
let id = null;
|
||||
let parentFrameId = null;
|
||||
if (isNested) {
|
||||
const match = /[&?]id=([^&]*?)(?:&|$)/.exec(location.href);
|
||||
let match = /[&?]id=([^&]*?)(?:&|$)/.exec(location.href);
|
||||
if (match !== null) {
|
||||
id = match[1];
|
||||
}
|
||||
|
||||
match = /[&?]parent=(\d+)(?:&|$)/.exec(location.href);
|
||||
if (match !== null) {
|
||||
parentFrameId = parseInt(match[1], 10);
|
||||
}
|
||||
}
|
||||
|
||||
const popup = isNested ? new PopupProxy(id) : PopupProxyHost.instance.createPopup();
|
||||
const popup = isNested ? new PopupProxy(id, parentFrameId) : PopupProxyHost.instance.createPopup(null);
|
||||
const frontend = new Frontend(popup);
|
||||
frontend.prepare();
|
||||
return frontend;
|
||||
|
@ -21,7 +21,22 @@ class PopupProxyHost {
|
||||
constructor() {
|
||||
this.popups = {};
|
||||
this.nextId = 0;
|
||||
this.apiReceiver = new FrontendApiReceiver('popup-proxy-host', {
|
||||
this.apiReceiver = null;
|
||||
this.frameIdPromise = null;
|
||||
}
|
||||
|
||||
static create() {
|
||||
const popupProxyHost = new PopupProxyHost();
|
||||
popupProxyHost.prepare();
|
||||
return popupProxyHost;
|
||||
}
|
||||
|
||||
async prepare() {
|
||||
this.frameIdPromise = apiFrameInformationGet();
|
||||
const {frameId} = await this.frameIdPromise;
|
||||
if (typeof frameId !== 'number') { return; }
|
||||
|
||||
this.apiReceiver = new FrontendApiReceiver(`popup-proxy-host#${frameId}`, {
|
||||
createNestedPopup: ({parentId}) => this.createNestedPopup(parentId),
|
||||
show: ({id, elementRect, options}) => this.show(id, elementRect, options),
|
||||
showOrphaned: ({id, elementRect, options}) => this.show(id, elementRect, options),
|
||||
@ -39,7 +54,7 @@ class PopupProxyHost {
|
||||
const depth = (parent !== null ? parent.depth + 1 : 0);
|
||||
const id = `${this.nextId}`;
|
||||
++this.nextId;
|
||||
const popup = new Popup(id, depth);
|
||||
const popup = new Popup(id, depth, this.frameIdPromise);
|
||||
if (parent !== null) {
|
||||
popup.parent = parent;
|
||||
parent.children.push(popup);
|
||||
@ -116,4 +131,4 @@ class PopupProxyHost {
|
||||
}
|
||||
}
|
||||
|
||||
PopupProxyHost.instance = new PopupProxyHost();
|
||||
PopupProxyHost.instance = PopupProxyHost.create();
|
||||
|
@ -18,12 +18,14 @@
|
||||
|
||||
|
||||
class PopupProxy {
|
||||
constructor(parentId) {
|
||||
constructor(parentId, parentFrameId) {
|
||||
this.parentId = parentId;
|
||||
this.parentFrameId = parentFrameId;
|
||||
this.id = null;
|
||||
this.idPromise = null;
|
||||
this.parent = null;
|
||||
this.children = [];
|
||||
this.depth = 0;
|
||||
|
||||
this.container = null;
|
||||
|
||||
@ -102,7 +104,10 @@ class PopupProxy {
|
||||
}
|
||||
|
||||
invokeHostApi(action, params={}) {
|
||||
return this.apiSender.invoke(action, params, 'popup-proxy-host');
|
||||
if (typeof this.parentFrameId !== 'number') {
|
||||
return Promise.reject('Invalid frame');
|
||||
}
|
||||
return this.apiSender.invoke(action, params, `popup-proxy-host#${this.parentFrameId}`);
|
||||
}
|
||||
|
||||
static DOMRectToJson(domRect) {
|
||||
|
@ -18,38 +18,55 @@
|
||||
|
||||
|
||||
class Popup {
|
||||
constructor(id, depth) {
|
||||
constructor(id, depth, frameIdPromise) {
|
||||
this.id = id;
|
||||
this.depth = depth;
|
||||
this.frameIdPromise = frameIdPromise;
|
||||
this.frameId = null;
|
||||
this.parent = null;
|
||||
this.children = [];
|
||||
this.container = document.createElement('iframe');
|
||||
this.container.id = 'yomichan-float';
|
||||
this.container.addEventListener('mousedown', e => e.stopPropagation());
|
||||
this.container.addEventListener('scroll', e => e.stopPropagation());
|
||||
this.container.setAttribute('src', chrome.extension.getURL(`/fg/float.html?id=${id}&depth=${depth}`));
|
||||
this.container.style.width = '0px';
|
||||
this.container.style.height = '0px';
|
||||
this.injected = null;
|
||||
this.injectPromise = null;
|
||||
this.isInjected = false;
|
||||
}
|
||||
|
||||
inject(options) {
|
||||
if (!this.injected) {
|
||||
this.injected = new Promise((resolve, reject) => {
|
||||
this.container.addEventListener('load', () => {
|
||||
this.invokeApi('setOptions', {
|
||||
general: {
|
||||
customPopupCss: options.general.customPopupCss
|
||||
}
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
this.observeFullscreen();
|
||||
this.onFullscreenChanged();
|
||||
});
|
||||
if (this.injectPromise === null) {
|
||||
this.injectPromise = this.createInjectPromise(options);
|
||||
}
|
||||
return this.injectPromise;
|
||||
}
|
||||
|
||||
async createInjectPromise(options) {
|
||||
try {
|
||||
const {frameId} = await this.frameIdPromise;
|
||||
if (typeof frameId === 'number') {
|
||||
this.frameId = frameId;
|
||||
}
|
||||
} catch (e) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
return this.injected;
|
||||
return new Promise((resolve) => {
|
||||
const parent = (typeof this.frameId === 'number' ? this.frameId : '');
|
||||
this.container.setAttribute('src', chrome.extension.getURL(`/fg/float.html?id=${this.id}&depth=${this.depth}&parent=${parent}`));
|
||||
this.container.addEventListener('load', () => {
|
||||
this.invokeApi('setOptions', {
|
||||
general: {
|
||||
customPopupCss: options.general.customPopupCss
|
||||
}
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
this.observeFullscreen();
|
||||
this.onFullscreenChanged();
|
||||
this.isInjected = true;
|
||||
});
|
||||
}
|
||||
|
||||
async show(elementRect, writingMode, options) {
|
||||
@ -215,7 +232,7 @@ class Popup {
|
||||
}
|
||||
|
||||
isVisible() {
|
||||
return this.injected && this.container.style.visibility !== 'hidden';
|
||||
return this.isInjected && this.container.style.visibility !== 'hidden';
|
||||
}
|
||||
|
||||
setVisible(visible) {
|
||||
@ -260,7 +277,7 @@ class Popup {
|
||||
}
|
||||
|
||||
clearAutoPlayTimer() {
|
||||
if (this.injected) {
|
||||
if (this.isInjected) {
|
||||
this.invokeApi('clearAutoPlayTimer');
|
||||
}
|
||||
}
|
||||
|
@ -23,9 +23,9 @@
|
||||
"fg/js/document.js",
|
||||
"fg/js/frontend-api-receiver.js",
|
||||
"fg/js/popup.js",
|
||||
"fg/js/popup-proxy-host.js",
|
||||
"fg/js/source.js",
|
||||
"fg/js/util.js",
|
||||
"fg/js/popup-proxy-host.js",
|
||||
"fg/js/frontend.js"
|
||||
],
|
||||
"css": ["fg/css/client.css"],
|
||||
|
Loading…
Reference in New Issue
Block a user