Fix messaging issues when iframes are present in the document

This commit is contained in:
toasted-nutbread 2019-08-18 20:51:19 -04:00
parent 42ec3e2a43
commit 53aad0bef6
11 changed files with 97 additions and 39 deletions

View File

@ -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});
}

View File

@ -127,6 +127,10 @@ class Backend {
forward: ({action, params}) => {
forward(apiForward(action, params, sender), callback);
},
frameInformationGet: () => {
forward(apiFrameInformationGet(sender), callback);
}
};

View File

@ -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) {

View File

@ -64,3 +64,7 @@ function apiScreenshotGet(options) {
function apiForward(action, params) {
return utilInvoke('forward', {action, params});
}
function apiFrameInformationGet() {
return utilInvoke('frameInformationGet');
}

View File

@ -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});
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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();

View File

@ -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) {

View File

@ -18,24 +18,43 @@
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) => {
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 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: {
@ -46,12 +65,10 @@ class Popup {
});
this.observeFullscreen();
this.onFullscreenChanged();
this.isInjected = true;
});
}
return this.injected;
}
async show(elementRect, writingMode, options) {
await this.inject(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');
}
}

View File

@ -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"],