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)); 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: ({action, params}) => {
forward(apiForward(action, params, sender), callback); 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/api.js',
'/fg/js/frontend-api-receiver.js', '/fg/js/frontend-api-receiver.js',
'/fg/js/popup.js', '/fg/js/popup.js',
'/fg/js/popup-proxy-host.js',
'/fg/js/util.js', '/fg/js/util.js',
'/fg/js/popup-proxy-host.js',
'/fg/js/frontend.js' '/fg/js/frontend.js'
]; ];
for (const src of scriptSrcs) { for (const src of scriptSrcs) {

View File

@ -64,3 +64,7 @@ function apiScreenshotGet(options) {
function apiForward(action, params) { function apiForward(action, params) {
return utilInvoke('forward', {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)); port.onMessage.addListener(this.onMessage.bind(this, port));
} }
onMessage(port, {id, action, params, target}) { onMessage(port, {id, action, params, target, senderId}) {
if ( if (
target !== this.source || target !== this.source ||
!this.handlers.hasOwnProperty(action) !this.handlers.hasOwnProperty(action)
@ -39,24 +39,24 @@ class FrontendApiReceiver {
return; return;
} }
this.sendAck(port, id); this.sendAck(port, id, senderId);
const handler = this.handlers[action]; const handler = this.handlers[action];
handler(params).then( handler(params).then(
result => { result => {
this.sendResult(port, id, {result}); this.sendResult(port, id, senderId, {result});
}, },
e => { e => {
const error = typeof e.toString === 'function' ? e.toString() : e; const error = typeof e.toString === 'function' ? e.toString() : e;
this.sendResult(port, id, {error}); this.sendResult(port, id, senderId, {error});
}); });
} }
sendAck(port, id) { sendAck(port, id, senderId) {
port.postMessage({type: 'ack', id}); port.postMessage({type: 'ack', id, senderId});
} }
sendResult(port, id, data) { sendResult(port, id, senderId, data) {
port.postMessage({type: 'result', id, data}); port.postMessage({type: 'result', id, senderId, data});
} }
} }

View File

@ -19,6 +19,7 @@
class FrontendApiSender { class FrontendApiSender {
constructor() { constructor() {
this.senderId = FrontendApiSender.generateId(16);
this.ackTimeout = 3000; // 3 seconds this.ackTimeout = 3000; // 3 seconds
this.responseTimeout = 10000; // 10 seconds this.responseTimeout = 10000; // 10 seconds
this.callbacks = {}; this.callbacks = {};
@ -43,11 +44,12 @@ class FrontendApiSender {
this.callbacks[id] = info; this.callbacks[id] = info;
info.timer = setTimeout(() => this.onError(id, 'Timeout (ack)'), this.ackTimeout); 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) { switch (type) {
case 'ack': case 'ack':
this.onAck(id); this.onAck(id);
@ -69,7 +71,7 @@ class FrontendApiSender {
onAck(id) { onAck(id) {
if (!this.callbacks.hasOwnProperty(id)) { if (!this.callbacks.hasOwnProperty(id)) {
console.warn(`ID ${id} not found`); console.warn(`ID ${id} not found for ack`);
return; return;
} }

View File

@ -42,14 +42,20 @@ class Frontend {
const isNested = (currentUrl === floatUrl); const isNested = (currentUrl === floatUrl);
let id = null; let id = null;
let parentFrameId = null;
if (isNested) { if (isNested) {
const match = /[&?]id=([^&]*?)(?:&|$)/.exec(location.href); let match = /[&?]id=([^&]*?)(?:&|$)/.exec(location.href);
if (match !== null) { if (match !== null) {
id = match[1]; 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); const frontend = new Frontend(popup);
frontend.prepare(); frontend.prepare();
return frontend; return frontend;

View File

@ -21,7 +21,22 @@ class PopupProxyHost {
constructor() { constructor() {
this.popups = {}; this.popups = {};
this.nextId = 0; 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), createNestedPopup: ({parentId}) => this.createNestedPopup(parentId),
show: ({id, elementRect, options}) => this.show(id, elementRect, options), show: ({id, elementRect, options}) => this.show(id, elementRect, options),
showOrphaned: ({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 depth = (parent !== null ? parent.depth + 1 : 0);
const id = `${this.nextId}`; const id = `${this.nextId}`;
++this.nextId; ++this.nextId;
const popup = new Popup(id, depth); const popup = new Popup(id, depth, this.frameIdPromise);
if (parent !== null) { if (parent !== null) {
popup.parent = parent; popup.parent = parent;
parent.children.push(popup); 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 { class PopupProxy {
constructor(parentId) { constructor(parentId, parentFrameId) {
this.parentId = parentId; this.parentId = parentId;
this.parentFrameId = parentFrameId;
this.id = null; this.id = null;
this.idPromise = null; this.idPromise = null;
this.parent = null; this.parent = null;
this.children = []; this.children = [];
this.depth = 0;
this.container = null; this.container = null;
@ -102,7 +104,10 @@ class PopupProxy {
} }
invokeHostApi(action, params={}) { 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) { static DOMRectToJson(domRect) {

View File

@ -18,24 +18,43 @@
class Popup { class Popup {
constructor(id, depth) { constructor(id, depth, frameIdPromise) {
this.id = id; this.id = id;
this.depth = depth; this.depth = depth;
this.frameIdPromise = frameIdPromise;
this.frameId = null;
this.parent = null; this.parent = null;
this.children = []; this.children = [];
this.container = document.createElement('iframe'); this.container = document.createElement('iframe');
this.container.id = 'yomichan-float'; this.container.id = 'yomichan-float';
this.container.addEventListener('mousedown', e => e.stopPropagation()); this.container.addEventListener('mousedown', e => e.stopPropagation());
this.container.addEventListener('scroll', 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.width = '0px';
this.container.style.height = '0px'; this.container.style.height = '0px';
this.injected = null; this.injectPromise = null;
this.isInjected = false;
} }
inject(options) { inject(options) {
if (!this.injected) { if (this.injectPromise === null) {
this.injected = new Promise((resolve, reject) => { 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.container.addEventListener('load', () => {
this.invokeApi('setOptions', { this.invokeApi('setOptions', {
general: { general: {
@ -46,12 +65,10 @@ class Popup {
}); });
this.observeFullscreen(); this.observeFullscreen();
this.onFullscreenChanged(); this.onFullscreenChanged();
this.isInjected = true;
}); });
} }
return this.injected;
}
async show(elementRect, writingMode, options) { async show(elementRect, writingMode, options) {
await this.inject(options); await this.inject(options);
@ -215,7 +232,7 @@ class Popup {
} }
isVisible() { isVisible() {
return this.injected && this.container.style.visibility !== 'hidden'; return this.isInjected && this.container.style.visibility !== 'hidden';
} }
setVisible(visible) { setVisible(visible) {
@ -260,7 +277,7 @@ class Popup {
} }
clearAutoPlayTimer() { clearAutoPlayTimer() {
if (this.injected) { if (this.isInjected) {
this.invokeApi('clearAutoPlayTimer'); this.invokeApi('clearAutoPlayTimer');
} }
} }

View File

@ -23,9 +23,9 @@
"fg/js/document.js", "fg/js/document.js",
"fg/js/frontend-api-receiver.js", "fg/js/frontend-api-receiver.js",
"fg/js/popup.js", "fg/js/popup.js",
"fg/js/popup-proxy-host.js",
"fg/js/source.js", "fg/js/source.js",
"fg/js/util.js", "fg/js/util.js",
"fg/js/popup-proxy-host.js",
"fg/js/frontend.js" "fg/js/frontend.js"
], ],
"css": ["fg/css/client.css"], "css": ["fg/css/client.css"],