From 37c5a6a70f6d682feb03cfa5ac04498d8e0e9b64 Mon Sep 17 00:00:00 2001 From: Jordan Thompson Date: Sun, 28 Dec 2025 12:42:29 -0800 Subject: [PATCH] Initial Commit --- .github/workflows/publish_action.yml | 24 ++ .gitignore | 364 ++++++++++++--------------- .gitignore copy | 157 ++++++++++++ README.md | 68 +++++ __init__.py | 24 ++ pyproject.toml | 25 ++ requirements.txt | 1 + web/comfy_retroarch_player.js | 239 ++++++++++++++++++ 8 files changed, 695 insertions(+), 207 deletions(-) create mode 100644 .github/workflows/publish_action.yml create mode 100644 .gitignore copy create mode 100644 README.md create mode 100644 __init__.py create mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100644 web/comfy_retroarch_player.js diff --git a/.github/workflows/publish_action.yml b/.github/workflows/publish_action.yml new file mode 100644 index 0000000..8696711 --- /dev/null +++ b/.github/workflows/publish_action.yml @@ -0,0 +1,24 @@ +name: Publish to Comfy registry +on: + workflow_dispatch: + push: + branches: + - main + paths: + - "pyproject.toml" + +permissions: + issues: write + +jobs: + publish-node: + name: Publish Custom Node to registry + runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'WASasquatch' }} + steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Publish Custom Node + uses: Comfy-Org/publish-node-action@v1 + with: + personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }} ## Add your own personal access token to your Github Repository secrets and reference it here. diff --git a/.gitignore b/.gitignore index b7faf40..c74ef74 100644 --- a/.gitignore +++ b/.gitignore @@ -1,207 +1,157 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[codz] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py.cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# UV -# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -#uv.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock -#poetry.toml - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. -# https://pdm-project.org/en/latest/usage/project/#working-with-version-control -#pdm.lock -#pdm.toml -.pdm-python -.pdm-build/ - -# pixi -# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. -#pixi.lock -# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one -# in the .venv directory. It is recommended not to include this directory in version control. -.pixi - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.envrc -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - -# Abstra -# Abstra is an AI-powered process automation framework. -# Ignore directories containing user credentials, local state, and settings. -# Learn more at https://abstra.io/docs -.abstra/ - -# Visual Studio Code -# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore -# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore -# and can be added to the global gitignore or merged into this file. However, if you prefer, -# you could uncomment the following to ignore the entire vscode folder -# .vscode/ - -# Ruff stuff: -.ruff_cache/ - -# PyPI configuration file -.pypirc - -# Cursor -# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to -# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data -# refer to https://docs.cursor.com/context/ignore-files -.cursorignore -.cursorindexingignore - -# Marimo -marimo/_static/ -marimo/_lsp/ -__marimo__/ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ +*.pyc + +# Ignore Windows ADS quarantine metadata files +*:Zone.Identifier +*:zone.identifier diff --git a/.gitignore copy b/.gitignore copy new file mode 100644 index 0000000..f58b280 --- /dev/null +++ b/.gitignore copy @@ -0,0 +1,157 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ +*.pyc + +# Ignore Windows ADS quarantine metadata files +*:Zone.Identifier +*:zone.identifier diff --git a/README.md b/README.md new file mode 100644 index 0000000..eeff957 --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# ComfyUI RetroArch Player + +![](screenshot.png) + +Embeds the RetroArch Web Player inside a ComfyUI node. + +This project uses the hosted RetroArch web player: +- https://web.libretro.com/ + +## Install + +- **Option A (git clone)** + - Clone this repo into: + - `ComfyUI/custom_nodes/ComfyUI_RetroArch_Player` + +- **Option B (Manager)** + - Open Manager and install **ComfyUI_RetroArch_Player** + +- **Option C (zip)** + - Download the repo as a zip. + - Extract it to: + - `ComfyUI/custom_nodes/ComfyUI_RetroArch_Player` + +Restart ComfyUI after installing. + +## Usage + +- **Add the node** + - In ComfyUI, add the node: + - `ComfyRetroArchPlayer` (RetroArch Player) + +- **Use RetroArch** + - The player UI is embedded inside the node. + - Use the RetroArch menu to select a core and load content. + - Supports many systems including NES, SNES, Genesis, PlayStation, and more. + +## Notes + +- **Hosted web app** + - This embed loads `https://web.libretro.com/` in an iframe, so it requires internet access. + +- **Fullscreen limitations** + - Due to cross-origin restrictions, the fullscreen button in RetroArch may not work within the iframe. + +## Troubleshooting + +- **The player UI doesn't update after changes** + - Do a hard refresh in your browser: + - `Ctrl+F5` + - Or clear browser cache for the ComfyUI site. + +- **Node appears but iframe is missing/blank** + - Open DevTools Console and look for errors. + - Confirm that `https://web.libretro.com/` is reachable from your browser. + +- **Iframe seems misaligned during zoom/pan** + - Make sure you're running the latest `web/comfy_retroarch_player.js` from this repo. + - If you have multiple copies installed, remove old versions from other `custom_nodes` folders. + +- **I uploaded a rom with "Add Content" but it doesn't show up** + - Now that you've uploaded a ROM, click "Load Content" and and click "Start Directory" to see uploaded ROMs to select. + - Select the ROM file and it will list compatible cores to select. + - Select the core and the game should launch. + +## Credits / License + +- RetroArch Web Player: [web.libretro.com](https://web.libretro.com/) by the libretro team +- This extension embeds the hosted RetroArch web player via iframe. diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..b68fc2a --- /dev/null +++ b/__init__.py @@ -0,0 +1,24 @@ +WEB_DIRECTORY = "./web" + + +class ComfyRetroArchPlayer: + @classmethod + def INPUT_TYPES(cls): + return {"required": {}} + + RETURN_TYPES = () + OUTPUT_NODE = True + FUNCTION = "run" + CATEGORY = "fun" + + def run(self): + return () + + +NODE_CLASS_MAPPINGS = { + "ComfyRetroArchPlayer": ComfyRetroArchPlayer, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "ComfyRetroArchPlayer": "RetroArch Player", +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..508d547 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,25 @@ +[project] +name = "comfyui-retroarch-player" +version = "0.0.1" +description = "Simple ComfyUI RetroArch Web Player embed" +readme = "README.md" +requires-python = ">=3.10" +license = { text = "MIT License" } +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent" +] +dynamic = ["dependencies"] + +[tool.setuptools.dynamic] +dependencies = { file = ["requirements.txt"] } + +[project.urls] +Repository = "https://github.com/WASasquatch/ComfyUI_RetroArch_Player" +"Bug Tracker" = "https://github.com/WASasquatch/ComfyUI_RetroArch_Player/issues" + +[tool.comfy] +PublisherId = "was" +DisplayName = "ComfyUI RetroArch Player" +requires-comfyui = ">=1.0.0" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d3f5a12 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ + diff --git a/web/comfy_retroarch_player.js b/web/comfy_retroarch_player.js new file mode 100644 index 0000000..d7f9996 --- /dev/null +++ b/web/comfy_retroarch_player.js @@ -0,0 +1,239 @@ +/* WAS RetroArch Player v2024.12.28.1155 */ +import { app } from "../../scripts/app.js"; + +console.log("[WAS RetroArch] ===== SCRIPT LOADED v2024.12.28.1155 ====="); + +const EXT_NAME = "WAS.RetroArchPlayer"; +const NODE_NAME = "ComfyRetroArchPlayer"; + +const DEFAULT_NODE_SIZE = [980, 600]; + +const STATE = { + container: null, + nodeIdToIframe: new Map(), + cleanupIntervalId: null, + cleanupListenersAttached: false, +}; + +function getActiveGraphNodes() { + const g = app?.graph || app?.canvas?.graph; + const nodes = g?._nodes || g?.nodes; + return Array.isArray(nodes) ? nodes : []; +} + +function isRetroArchPlayerNode(node) { + try { + return ( + node?.comfyClass === NODE_NAME || + node?.type === NODE_NAME || + node?.constructor?.comfyClass === NODE_NAME || + node?.constructor?.type === NODE_NAME + ); + } catch (e) { + return false; + } +} + +function removeIframeByKey(key) { + try { + const iframe = STATE.nodeIdToIframe.get(key); + if (!iframe) return; + iframe.remove(); + } catch (e) { + } finally { + STATE.nodeIdToIframe.delete(key); + } +} + +function cleanupOrphanIframes() { + try { + const nodes = getActiveGraphNodes(); + const activeIds = new Set( + nodes + .filter((n) => isRetroArchPlayerNode(n)) + .map((n) => String(n.id)) + ); + + for (const key of Array.from(STATE.nodeIdToIframe.keys())) { + if (!activeIds.has(key)) { + removeIframeByKey(key); + } + } + + if (STATE.nodeIdToIframe.size === 0 && STATE.container) { + try { + STATE.container.remove(); + } catch (e) { + } + STATE.container = null; + } + } catch (e) { + } +} + +function ensureCleanupRunning() { + if (STATE.cleanupIntervalId != null) return; + STATE.cleanupIntervalId = window.setInterval(() => cleanupOrphanIframes(), 1000); + if (!STATE.cleanupListenersAttached) { + STATE.cleanupListenersAttached = true; + const onVis = () => cleanupOrphanIframes(); + const onFocus = () => cleanupOrphanIframes(); + window.addEventListener("visibilitychange", onVis); + window.addEventListener("focus", onFocus); + } +} + +function getContainer() { + if (STATE.container) return STATE.container; + const el = document.createElement("div"); + el.id = "was-retroarch-overlay"; + el.style.position = "absolute"; + el.style.left = "0"; + el.style.top = "0"; + el.style.width = "0"; + el.style.height = "0"; + el.style.pointerEvents = "none"; + el.style.zIndex = "1000"; + document.body.appendChild(el); + STATE.container = el; + return el; +} + +function ensureIframeForNode(node) { + cleanupOrphanIframes(); + ensureCleanupRunning(); + + const key = String(node.id); + const existing = STATE.nodeIdToIframe.get(key); + if (existing) return existing; + + const iframe = document.createElement("iframe"); + iframe.setAttribute( + "sandbox", + "allow-scripts allow-same-origin allow-forms allow-popups allow-downloads allow-pointer-lock" + ); + iframe.style.position = "absolute"; + iframe.style.border = "0"; + iframe.style.pointerEvents = "auto"; + iframe.style.background = "#000"; + iframe.style.left = "0px"; + iframe.style.top = "0px"; + iframe.style.width = "0px"; + iframe.style.height = "0px"; + iframe.style.display = "none"; + iframe.style.borderRadius = "8px"; + + + iframe.src = "https://web.libretro.com/"; + iframe.allowFullscreen = true; + iframe.allow = "fullscreen"; + + getContainer().appendChild(iframe); + STATE.nodeIdToIframe.set(key, iframe); + + const oldOnRemoved = node.onRemoved; + node.onRemoved = function () { + try { + removeIframeByKey(String(this.id)); + cleanupOrphanIframes(); + } catch (e) { + } + return oldOnRemoved ? oldOnRemoved.apply(this, arguments) : undefined; + }; + + return iframe; +} + +function updateIframeRect(node, iframe) { + const canvas = app.canvas; + const canvasEl = canvas?.canvas; + if (!canvasEl) return; + + const rect = canvasEl.getBoundingClientRect(); + const ds = canvas.ds; + const scale = ds?.scale ?? 1; + const offset = ds?.offset ?? [0, 0]; + + const x = rect.left + (node.pos[0] + offset[0]) * scale; + const y = rect.top + (node.pos[1] + offset[1]) * scale; + const w = node.size[0] * scale; + const h = node.size[1] * scale; + + const titleHRaw = + (typeof node?.title_height === "number" && Number.isFinite(node.title_height) && node.title_height) || + (typeof node?.constructor?.title_height === "number" && + Number.isFinite(node.constructor.title_height) && + node.constructor.title_height) || + globalThis?.LiteGraph?.NODE_TITLE_HEIGHT || + 30; + const titleH = titleHRaw * scale; + const insetX = 8 * scale; + const insetTop = 0; + const insetBottom = 8 * scale; + const headerAdjust = 20 * scale; + const innerX = x + insetX; + const innerY = y + titleH + insetTop - headerAdjust; + const innerW = w - insetX * 2; + const innerH = h - titleH - insetTop - insetBottom + headerAdjust; + + const nx = Number.isFinite(innerX) ? innerX : 0; + const ny = Number.isFinite(innerY) ? innerY : 0; + const nw = Number.isFinite(innerW) ? innerW : 0; + const nh = Number.isFinite(innerH) ? innerH : 0; + + iframe.style.left = `${Math.round(nx)}px`; + iframe.style.top = `${Math.round(ny)}px`; + iframe.style.width = `${Math.max(0, Math.round(nw))}px`; + iframe.style.height = `${Math.max(0, Math.round(nh))}px`; + + const isCollapsed = !!node.flags?.collapsed; + const hasArea = nw >= 2 && nh >= 2; + iframe.style.display = !isCollapsed && hasArea ? "block" : "none"; +} + +app.registerExtension({ + name: EXT_NAME, + async beforeRegisterNodeDef(nodeType, nodeData) { + if (nodeData?.name !== NODE_NAME) return; + + ensureCleanupRunning(); + + const oldOnNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function () { + const r = oldOnNodeCreated ? oldOnNodeCreated.apply(this, arguments) : undefined; + try { + if (!Array.isArray(this.size) || this.size.length < 2) { + this.size = [...DEFAULT_NODE_SIZE]; + } else { + this.size[0] = Math.max(this.size[0] ?? 0, DEFAULT_NODE_SIZE[0]); + this.size[1] = Math.max(this.size[1] ?? 0, DEFAULT_NODE_SIZE[1]); + } + this.setDirtyCanvas?.(true, true); + } catch (e) { + } + return r; + }; + + const oldOnDrawForeground = nodeType.prototype.onDrawForeground; + nodeType.prototype.onDrawForeground = function (ctx) { + const r = oldOnDrawForeground ? oldOnDrawForeground.apply(this, arguments) : undefined; + try { + const iframe = ensureIframeForNode(this); + updateIframeRect(this, iframe); + } catch (e) { + } + return r; + }; + + const oldOnResize = nodeType.prototype.onResize; + nodeType.prototype.onResize = function (size) { + const r = oldOnResize ? oldOnResize.apply(this, arguments) : undefined; + try { + const iframe = ensureIframeForNode(this); + updateIframeRect(this, iframe); + } catch (e) { + } + return r; + }; + }, +});