diff options
| author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2021-02-08 17:52:56 -0500 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-02-08 17:52:56 -0500 | 
| commit | 73e91b3b62f5ca07bcf2bf66e54e0d16af73dd42 (patch) | |
| tree | f54ad3e54aac4fb23fb1ebb1755e89b9049dd833 /ext | |
| parent | 3e5b30ff764a34d2f51dc67d64a05d3a98cbed86 (diff) | |
Refactor FrameOffsetForwarder (#1353)
* Add getChildFrameElement to FrameAncestryHandler
* Add isRootFrame
* Initialize _frameOffset to [0, 0]
* Update FrameOffsetForwarder implementation
* Update documentation
Diffstat (limited to 'ext')
| -rw-r--r-- | ext/bg/popup-preview.html | 1 | ||||
| -rw-r--r-- | ext/fg/js/frame-ancestry-handler.js | 76 | ||||
| -rw-r--r-- | ext/fg/js/frame-offset-forwarder.js | 159 | ||||
| -rw-r--r-- | ext/fg/js/popup-proxy.js | 9 | ||||
| -rw-r--r-- | ext/manifest.json | 1 | ||||
| -rw-r--r-- | ext/mixed/js/display.js | 1 | 
6 files changed, 111 insertions, 136 deletions
| diff --git a/ext/bg/popup-preview.html b/ext/bg/popup-preview.html index d0bf77d3..c1ef7ca4 100644 --- a/ext/bg/popup-preview.html +++ b/ext/bg/popup-preview.html @@ -52,6 +52,7 @@  <script src="/fg/js/text-source-element.js"></script>  <script src="/fg/js/popup-factory.js"></script>  <script src="/fg/js/frontend.js"></script> +<script src="/fg/js/frame-ancestry-handler.js"></script>  <script src="/fg/js/frame-offset-forwarder.js"></script>  <script src="/bg/js/settings/popup-preview-frame.js"></script> diff --git a/ext/fg/js/frame-ancestry-handler.js b/ext/fg/js/frame-ancestry-handler.js index 31ee956b..b1ed7114 100644 --- a/ext/fg/js/frame-ancestry-handler.js +++ b/ext/fg/js/frame-ancestry-handler.js @@ -23,6 +23,7 @@   * This class is used to return the ancestor frame IDs for the current frame.   * This is a workaround to using the `webNavigation.getAllFrames` API, which   * would require an additional permission that is otherwise unnecessary. + * It is also used to track the correlation between child frame elements and their IDs.   */  class FrameAncestryHandler {      /** @@ -55,6 +56,14 @@ class FrameAncestryHandler {      }      /** +     * Returns whether or not this frame is the root frame in the tab. +     * @returns `true` if it is the root, otherwise `false`. +     */ +    isRootFrame() { +        return (window === window.parent); +    } + +    /**       * Gets the frame ancestry information for the current frame. If the frame is the       * root frame, an empty array is returned. Otherwise, an array of frame IDs is returned,       * starting from the nearest ancestor. @@ -68,6 +77,26 @@ class FrameAncestryHandler {          return await this._getFrameAncestryInfoPromise;      } +    /** +     * Gets the frame element of a child frame given a frame ID. +     * For this function to work, the `getFrameAncestryInfo` function needs to have +     * been invoked previously. +     * @param frameId The frame ID of the child frame to get. +     * @returns The element corresponding to the frame with ID `frameId`, otherwise `null`. +     */ +    getChildFrameElement(frameId) { +        const frameInfo = this._childFrameMap.get(frameId); +        if (typeof frameInfo === 'undefined') { return null; } + +        let {frameElement} = frameInfo; +        if (typeof frameElement === 'undefined') { +            frameElement = this._findFrameElementWithContentWindow(frameInfo.window); +            frameInfo.frameElement = frameElement; +        } + +        return frameElement; +    } +      // Private      _getFrameAncestryInfo(timeout=5000) { @@ -166,7 +195,7 @@ class FrameAncestryHandler {              }              if (!this._childFrameMap.has(childFrameId)) { -                this._childFrameMap.set(childFrameId, {window: source}); +                this._childFrameMap.set(childFrameId, {window: source, frameElement: void 0});              }              if (more) { @@ -192,4 +221,49 @@ class FrameAncestryHandler {              Math.floor(value) === value          );      } + +    _findFrameElementWithContentWindow(contentWindow) { +        // Check frameElement, for non-null same-origin frames +        try { +            const {frameElement} = contentWindow; +            if (frameElement !== null) { return frameElement; } +        } catch (e) { +            // NOP +        } + +        // Check frames +        const frameTypes = ['iframe', 'frame', 'embed']; +        for (const frameType of frameTypes) { +            for (const frame of document.getElementsByTagName(frameType)) { +                if (frame.contentWindow === contentWindow) { +                    return frame; +                } +            } +        } + +        // Check for shadow roots +        const rootElements = [document.documentElement]; +        while (rootElements.length > 0) { +            const rootElement = rootElements.shift(); +            const walker = document.createTreeWalker(rootElement, NodeFilter.SHOW_ELEMENT); +            while (walker.nextNode()) { +                const element = walker.currentNode; + +                if (element.contentWindow === contentWindow) { +                    return element; +                } + +                const shadowRoot = ( +                    element.shadowRoot || +                    element.openOrClosedShadowRoot // Available to Firefox 63+ for WebExtensions +                ); +                if (shadowRoot) { +                    rootElements.push(shadowRoot); +                } +            } +        } + +        // Not found +        return null; +    }  } 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};      }  } diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index 6d6c7fb9..bb037705 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -34,7 +34,7 @@ class PopupProxy extends EventDispatcher {          this._ownerFrameId = ownerFrameId;          this._frameOffsetForwarder = frameOffsetForwarder; -        this._frameOffset = null; +        this._frameOffset = [0, 0];          this._frameOffsetPromise = null;          this._frameOffsetUpdatedAt = null;          this._frameOffsetExpireTimeout = 1000; @@ -194,7 +194,12 @@ class PopupProxy extends EventDispatcher {      async _updateFrameOffsetInner(now) {          this._frameOffsetPromise = this._frameOffsetForwarder.getOffset();          try { -            const offset = await this._frameOffsetPromise; +            let offset = null; +            try { +                offset = await this._frameOffsetPromise; +            } catch (e) { +                // NOP +            }              this._frameOffset = offset !== null ? offset : [0, 0];              if (offset === null) {                  this.trigger('offsetNotFound'); diff --git a/ext/manifest.json b/ext/manifest.json index 54df2a89..238fabe3 100644 --- a/ext/manifest.json +++ b/ext/manifest.json @@ -52,6 +52,7 @@                  "fg/js/text-source-range.js",                  "fg/js/text-source-element.js",                  "fg/js/popup-factory.js", +                "fg/js/frame-ancestry-handler.js",                  "fg/js/frame-offset-forwarder.js",                  "fg/js/popup-proxy.js",                  "fg/js/popup-window.js", diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index c1044872..99ccfa85 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -1592,6 +1592,7 @@ class Display extends EventDispatcher {              '/fg/js/popup-proxy.js',              '/fg/js/popup-window.js',              '/fg/js/popup-factory.js', +            '/fg/js/frame-ancestry-handler.js',              '/fg/js/frame-offset-forwarder.js',              '/fg/js/frontend.js'          ]); |