summaryrefslogtreecommitdiff
path: root/ext/fg/js/frame-offset-forwarder.js
diff options
context:
space:
mode:
Diffstat (limited to 'ext/fg/js/frame-offset-forwarder.js')
-rw-r--r--ext/fg/js/frame-offset-forwarder.js159
1 files changed, 26 insertions, 133 deletions
diff --git a/ext/fg/js/frame-offset-forwarder.js b/ext/fg/js/frame-offset-forwarder.js
index b5d5424c..0a0b4a18 100644
--- a/ext/fg/js/frame-offset-forwarder.js
+++ b/ext/fg/js/frame-offset-forwarder.js
@@ -16,162 +16,55 @@
*/
/* global
+ * FrameAncestryHandler
* api
*/
class FrameOffsetForwarder {
constructor(frameId) {
this._frameId = frameId;
- this._isPrepared = false;
- this._cacheMaxSize = 1000;
- this._frameCache = new Set();
- this._unreachableContentWindowCache = new Set();
- this._windowMessageHandlers = new Map([
- ['getFrameOffset', this._onMessageGetFrameOffset.bind(this)]
- ]);
+ this._frameAncestryHandler = new FrameAncestryHandler(frameId);
}
prepare() {
- if (this._isPrepared) { return; }
- window.addEventListener('message', this._onMessage.bind(this), false);
- this._isPrepared = true;
+ this._frameAncestryHandler.prepare();
+ api.crossFrame.registerHandlers([
+ ['FrameOffsetForwarder.getChildFrameRect', {async: false, handler: this._onMessageGetChildFrameRect.bind(this)}]
+ ]);
}
async getOffset() {
- if (window === window.parent) {
+ if (this._frameAncestryHandler.isRootFrame()) {
return [0, 0];
}
- const uniqueId = generateId(16);
-
- const frameOffsetPromise = yomichan.getTemporaryListenerResult(
- chrome.runtime.onMessage,
- ({action, params}, {resolve}) => {
- if (action === 'frameOffset' && isObject(params) && params.uniqueId === uniqueId) {
- resolve(params);
- }
- },
- 5000
- );
-
- this._getFrameOffsetParent([0, 0], uniqueId, this._frameId);
-
- const {offset} = await frameOffsetPromise;
- return offset;
- }
-
- // Private
-
- _onMessage(event) {
- const data = event.data;
- if (data === null || typeof data !== 'object') { return; }
-
- try {
- const {action, params} = event.data;
- const handler = this._windowMessageHandlers.get(action);
- if (typeof handler !== 'function') { return; }
- handler(params, event);
- } catch (e) {
- // NOP
- }
- }
-
- _onMessageGetFrameOffset({offset, uniqueId, frameId}, e) {
- let sourceFrame = null;
- if (!this._unreachableContentWindowCache.has(e.source)) {
- sourceFrame = this._findFrameWithContentWindow(e.source);
- }
- if (sourceFrame === null) {
- // closed shadow root etc.
- this._addToCache(this._unreachableContentWindowCache, e.source);
- this._replyFrameOffset(null, uniqueId, frameId);
- return;
- }
-
- const [forwardedX, forwardedY] = offset;
- const {x, y} = sourceFrame.getBoundingClientRect();
- offset = [forwardedX + x, forwardedY + y];
+ const ancestorFrameIds = await this._frameAncestryHandler.getFrameAncestryInfo();
- if (window === window.parent) {
- this._replyFrameOffset(offset, uniqueId, frameId);
- } else {
- this._getFrameOffsetParent(offset, uniqueId, frameId);
+ let childFrameId = this._frameId;
+ const promises = [];
+ for (const frameId of ancestorFrameIds) {
+ promises.push(api.crossFrame.invoke(frameId, 'FrameOffsetForwarder.getChildFrameRect', {frameId: childFrameId}));
+ childFrameId = frameId;
}
- }
- _findFrameWithContentWindow(contentWindow) {
- const ELEMENT_NODE = Node.ELEMENT_NODE;
- for (const elements of this._getFrameElementSources()) {
- while (elements.length > 0) {
- const element = elements.shift();
- if (element.contentWindow === contentWindow) {
- this._addToCache(this._frameCache, element);
- return element;
- }
+ const results = await Promise.all(promises);
- const shadowRoot = (
- element.shadowRoot ||
- element.openOrClosedShadowRoot // Available to Firefox 63+ for WebExtensions
- );
- 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);
- }
- }
- }
+ let xOffset = 0;
+ let yOffset = 0;
+ for (const {x, y} of results) {
+ xOffset += x;
+ yOffset += y;
}
-
- return null;
+ return [xOffset, yOffset];
}
- *_getFrameElementSources() {
- const frameCache = [];
- for (const frame of this._frameCache) {
- // removed from DOM
- if (!frame.isConnected) {
- this._frameCache.delete(frame);
- continue;
- }
- frameCache.push(frame);
- }
- yield frameCache;
- // will contain duplicates, but frame elements are cheap to handle
- yield [...document.querySelectorAll('frame,iframe')];
- yield [document.documentElement];
- }
-
- _addToCache(cache, value) {
- let freeSlots = this._cacheMaxSize - cache.size;
- if (freeSlots <= 0) {
- for (const cachedValue of cache) {
- cache.delete(cachedValue);
- ++freeSlots;
- if (freeSlots > 0) { break; }
- }
- }
- cache.add(value);
- }
+ // Private
- _getFrameOffsetParent(offset, uniqueId, frameId) {
- window.parent.postMessage({
- action: 'getFrameOffset',
- params: {
- offset,
- uniqueId,
- frameId
- }
- }, '*');
- }
+ _onMessageGetChildFrameRect({frameId}) {
+ const frameElement = this._frameAncestryHandler.getChildFrameElement(frameId);
+ if (frameElement === null) { return null; }
- _replyFrameOffset(offset, uniqueId, frameId) {
- api.sendMessageToFrame(frameId, 'frameOffset', {offset, uniqueId});
+ const {x, y, width, height} = frameElement.getBoundingClientRect();
+ return {x, y, width, height};
}
}