From 04b0f00fe80d3acde6e25f6d70afb121626bd0c7 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Tue, 28 Oct 2025 15:16:40 +0000 Subject: [PATCH 01/38] Extract checkReadyYet from child --- packages/child/check/ready.js | 15 +++++++++++++++ packages/child/index.js | 11 +---------- 2 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 packages/child/check/ready.js diff --git a/packages/child/check/ready.js b/packages/child/check/ready.js new file mode 100644 index 000000000..38edd0e51 --- /dev/null +++ b/packages/child/check/ready.js @@ -0,0 +1,15 @@ +import { READY_STATE_CHANGE } from '../../common/consts' +import { isolateUserCode } from '../../common/utils' +import { addEventListener } from '../listeners' + +const COMPLETE = 'complete' +let readyChecked = false + +export default function checkReadyYet(readyCallback) { + if (document.readyState === COMPLETE) isolateUserCode(readyCallback) + else if (!readyChecked) + addEventListener(document, READY_STATE_CHANGE, () => + checkReadyYet(readyCallback), + ) + readyChecked = true +} diff --git a/packages/child/index.js b/packages/child/index.js index 7c398c3f4..9f1876711 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -74,6 +74,7 @@ import { round, typeAssert, } from '../common/utils' +import checkReadyYet from './check/ready' import checkBlockingCSS from './check-blocking-css' import { advise, @@ -276,16 +277,6 @@ function iframeResizerChild() { const setupOnPageHide = () => addEventListener(window, lower(PAGE_HIDE), onPageHide) - let readyChecked = false - function checkReadyYet(readyCallback) { - if (document.readyState === 'complete') isolateUserCode(readyCallback) - else if (!readyChecked) - addEventListener(document, READY_STATE_CHANGE, () => - checkReadyYet(readyCallback), - ) - readyChecked = true - } - function checkAndSetupTags() { taggedElements = document.querySelectorAll(`[${SIZE_ATTR}]`) hasTags = taggedElements.length > 0 From f5a746d605f8a8d645b90a74c597e0ce8202ec2b Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Tue, 28 Oct 2025 15:24:23 +0000 Subject: [PATCH 02/38] Extract quirks and version --- packages/child/check/quirks-mode.js | 14 ++++++++++ packages/child/check/version.js | 27 ++++++++++++++++++ packages/child/index.js | 43 ++--------------------------- 3 files changed, 44 insertions(+), 40 deletions(-) create mode 100644 packages/child/check/quirks-mode.js create mode 100644 packages/child/check/version.js diff --git a/packages/child/check/quirks-mode.js b/packages/child/check/quirks-mode.js new file mode 100644 index 000000000..925fd1e97 --- /dev/null +++ b/packages/child/check/quirks-mode.js @@ -0,0 +1,14 @@ +import { advise } from '../console' + +export default function checkQuirksMode() { + if (document.compatMode !== 'BackCompat') return + + advise( + `Quirks Mode Detected + +This iframe is running in the browser's legacy Quirks Mode, this may cause issues with the correct operation of iframe-resizer. It is recommended that you switch to the modern Standards Mode. + +For more information see https://iframe-resizer.com/quirks-mode. +`, + ) +} diff --git a/packages/child/check/version.js b/packages/child/check/version.js new file mode 100644 index 000000000..fc6485100 --- /dev/null +++ b/packages/child/check/version.js @@ -0,0 +1,27 @@ +import { FALSE, VERSION } from '../../common/consts' +import { advise } from '../console' + +export default function checkVersion(version) { + if (!version || version === '' || version === FALSE) { + advise( + `Legacy version detected on parent page + +Detected legacy version of parent page script. It is recommended to update the parent page to use @iframe-resizer/parent. + +See https://iframe-resizer.com/setup/ for more details. +`, + ) + return + } + + if (version !== VERSION) { + advise( + `Version mismatch + +The parent and child pages are running different versions of iframe resizer. + +Parent page: ${version} - Child page: ${VERSION}. +`, + ) + } +} diff --git a/packages/child/index.js b/packages/child/index.js index 9f1876711..37af94226 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -10,7 +10,6 @@ import { CHILD_READY_MESSAGE, CLOSE, ENABLE, - FALSE, FUNCTION, HEIGHT, HEIGHT_EDGE, @@ -74,7 +73,9 @@ import { round, typeAssert, } from '../common/utils' +import checkQuirksMode from './check/quirks-mode' import checkReadyYet from './check/ready' +import checkVersion from './check/version' import checkBlockingCSS from './check-blocking-css' import { advise, @@ -218,7 +219,7 @@ function iframeResizerChild() { readDataFromPage() const setup = [ - checkVersion, + () => checkVersion(version), checkBoth, checkMode, checkIgnoredElements, @@ -312,44 +313,6 @@ function iframeResizerChild() { return hasIgnored } - function checkQuirksMode() { - if (document.compatMode !== 'BackCompat') return - - advise( - `Quirks Mode Detected - -This iframe is running in the browser's legacy Quirks Mode, this may cause issues with the correct operation of iframe-resizer. It is recommended that you switch to the modern Standards Mode. - -For more information see https://iframe-resizer.com/quirks-mode. -`, - ) - } - - function checkVersion() { - if (!version || version === '' || version === FALSE) { - advise( - `Legacy version detected on parent page - -Detected legacy version of parent page script. It is recommended to update the parent page to use @iframe-resizer/parent. - -See https://iframe-resizer.com/setup/ for more details. -`, - ) - return - } - - if (version !== VERSION) { - advise( - `Version mismatch - -The parent and child pages are running different versions of iframe resizer. - -Parent page: ${version} - Child page: ${VERSION}. -`, - ) - } - } - function checkCrossDomain() { try { sameOrigin = mode === 1 || 'iframeParentListener' in window.parent From 4e5115dbc364e291592d85477a9456578c937416 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Tue, 28 Oct 2025 15:36:16 +0000 Subject: [PATCH 03/38] extract stopInfiniteResize --- packages/child/index.js | 35 +++++++------------ packages/child/page/stop-infinite-resizing.js | 15 ++++++++ 2 files changed, 27 insertions(+), 23 deletions(-) create mode 100644 packages/child/page/stop-infinite-resizing.js diff --git a/packages/child/index.js b/packages/child/index.js index 37af94226..8592d5b32 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -110,6 +110,7 @@ import createPerformanceObserver, { } from './observers/perf' import createResizeObserver from './observers/resize' import createVisibilityObserver from './observers/visibility' +import stopInfiniteResizingOfIframe from './page/stop-infinite-resizing' import { readFunction, readNumber, readString } from './read' function iframeResizerChild() { @@ -413,18 +414,7 @@ See https://iframe-resizer.com/api/child for more details.`, } function checkBoth() { - if (calculateWidth === calculateHeight) { - bothDirections = true - } - } - - function chkCSS(attr, value) { - if (value.includes('-')) { - warn(`Negative CSS value ignored for ${attr}`) - value = '' - } - - return value + bothDirections = calculateWidth === calculateHeight } function setBodyStyle(attr, value) { @@ -450,23 +440,22 @@ See https://iframe-resizer.com/api/child for more details.`, applySelector('ignoreSelector', IGNORE_ATTR, ignoreSelector) } + function checkCSS(attr, value) { + if (value.includes('-')) { + warn(`Negative CSS value ignored for ${attr}`) + value = '' + } + + return value + } + function setMargin() { // If called via V1 script, convert bodyMargin from int to str if (undefined === bodyMarginStr) { bodyMarginStr = `${bodyMargin}px` } - setBodyStyle('margin', chkCSS('margin', bodyMarginStr)) - } - - function stopInfiniteResizingOfIframe() { - const setAutoHeight = (el) => - el.style.setProperty(HEIGHT, AUTO, 'important') - - setAutoHeight(document.documentElement) - setAutoHeight(document.body) - - log('Set HTML & body height: %cauto !important', HIGHLIGHT) + setBodyStyle('margin', checkCSS('margin', bodyMarginStr)) } function manageTriggerEvent(options) { diff --git a/packages/child/page/stop-infinite-resizing.js b/packages/child/page/stop-infinite-resizing.js new file mode 100644 index 000000000..30615d404 --- /dev/null +++ b/packages/child/page/stop-infinite-resizing.js @@ -0,0 +1,15 @@ +import { HIGHLIGHT } from 'auto-console-group' + +import { AUTO, HEIGHT } from '../../common/consts' +import { log } from '../console' + +const IMPORTANT = 'important' + +export default function stopInfiniteResizingOfIframe() { + const setAutoHeight = (el) => el.style.setProperty(HEIGHT, AUTO, IMPORTANT) + + setAutoHeight(document.documentElement) + setAutoHeight(document.body) + + log('Set HTML & body height: %cauto !important', HIGHLIGHT) +} From ee7f6079534de5fe6416b433295614b803c71adc Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Tue, 28 Oct 2025 15:43:09 +0000 Subject: [PATCH 04/38] Extract set CSS functions from child --- packages/child/index.js | 29 ++--------------------------- packages/child/page/css.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 27 deletions(-) create mode 100644 packages/child/page/css.js diff --git a/packages/child/index.js b/packages/child/index.js index 8592d5b32..27f048903 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -28,7 +28,6 @@ import { MUTATION_OBSERVER, NO_CHANGE, NONE, - NULL, NUMBER, OBJECT, OFFSET, @@ -110,6 +109,7 @@ import createPerformanceObserver, { } from './observers/perf' import createResizeObserver from './observers/resize' import createVisibilityObserver from './observers/visibility' +import { setBodyStyle, setMargin } from './page/css' import stopInfiniteResizingOfIframe from './page/stop-infinite-resizing' import { readFunction, readNumber, readString } from './read' @@ -232,7 +232,7 @@ function iframeResizerChild() { checkAndSetupTags, bothDirections ? id : checkBlockingCSS, - setMargin, + () => setMargin({ bodyMarginStr, bodyMargin }), () => setBodyStyle('background', bodyBackground), () => setBodyStyle('padding', bodyPadding), @@ -417,13 +417,6 @@ See https://iframe-resizer.com/api/child for more details.`, bothDirections = calculateWidth === calculateHeight } - function setBodyStyle(attr, value) { - if (undefined === value || value === '' || value === NULL) return - - document.body.style.setProperty(attr, value) - info(`Set body ${attr}: %c${value}`, HIGHLIGHT) - } - function applySelector(name, attribute, selector) { if (selector === '') return @@ -440,24 +433,6 @@ See https://iframe-resizer.com/api/child for more details.`, applySelector('ignoreSelector', IGNORE_ATTR, ignoreSelector) } - function checkCSS(attr, value) { - if (value.includes('-')) { - warn(`Negative CSS value ignored for ${attr}`) - value = '' - } - - return value - } - - function setMargin() { - // If called via V1 script, convert bodyMargin from int to str - if (undefined === bodyMarginStr) { - bodyMarginStr = `${bodyMargin}px` - } - - setBodyStyle('margin', checkCSS('margin', bodyMarginStr)) - } - function manageTriggerEvent(options) { const listener = { add(eventName) { diff --git a/packages/child/page/css.js b/packages/child/page/css.js new file mode 100644 index 000000000..5a644bae3 --- /dev/null +++ b/packages/child/page/css.js @@ -0,0 +1,29 @@ +import { HIGHLIGHT } from 'auto-console-group' + +import { NULL } from '../../common/consts' +import { info, warn } from '../console' + +export function checkCSS(attr, value) { + if (value.includes('-')) { + warn(`Negative CSS value ignored for ${attr}`) + value = '' + } + + return value +} + +export function setBodyStyle(attr, value) { + if (undefined === value || value === '' || value === NULL) return + + document.body.style.setProperty(attr, value) + info(`Set body ${attr}: %c${value}`, HIGHLIGHT) +} + +export function setMargin({ bodyMarginStr, bodyMargin }) { + // If called via V1 script, convert bodyMargin from int to str + if (undefined === bodyMarginStr) { + bodyMarginStr = `${bodyMargin}px` + } + + setBodyStyle('margin', checkCSS('margin', bodyMarginStr)) +} From 93e57dfbbb3987a42704a595798fb99290d9d6a8 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Tue, 28 Oct 2025 16:53:44 +0000 Subject: [PATCH 05/38] Extract applySelectors() and tidy file layout --- .../blocking-css.js} | 4 +- packages/child/check/ready.js | 2 +- packages/child/index.js | 99 ++++++++++++------- packages/child/page/apply-selectors.js | 22 +++++ packages/child/{ => page}/listeners.js | 2 +- packages/child/{ => read}/from-string.js | 0 packages/child/{ => read}/from-string.test.js | 2 +- packages/child/{ => read}/read.js | 2 +- packages/child/{ => read}/read.test.js | 0 packages/child/values/settings.js | 1 + packages/child/values/state.js | 1 + 11 files changed, 96 insertions(+), 39 deletions(-) rename packages/child/{check-blocking-css.js => check/blocking-css.js} (96%) create mode 100644 packages/child/page/apply-selectors.js rename packages/child/{ => page}/listeners.js (94%) rename packages/child/{ => read}/from-string.js (100%) rename packages/child/{ => read}/from-string.test.js (95%) rename packages/child/{ => read}/read.js (84%) rename packages/child/{ => read}/read.test.js (100%) create mode 100644 packages/child/values/settings.js create mode 100644 packages/child/values/state.js diff --git a/packages/child/check-blocking-css.js b/packages/child/check/blocking-css.js similarity index 96% rename from packages/child/check-blocking-css.js rename to packages/child/check/blocking-css.js index c1f521683..f09f759a6 100644 --- a/packages/child/check-blocking-css.js +++ b/packages/child/check/blocking-css.js @@ -1,5 +1,5 @@ -import { AUTO } from '../common/consts' -import { advise, log } from './console' +import { AUTO } from '../../common/consts' +import { advise, log } from '../console' const nodes = () => [document.documentElement, document.body] const properties = ['min-height', 'min-width', 'max-height', 'max-width'] diff --git a/packages/child/check/ready.js b/packages/child/check/ready.js index 38edd0e51..2b1701b99 100644 --- a/packages/child/check/ready.js +++ b/packages/child/check/ready.js @@ -1,6 +1,6 @@ import { READY_STATE_CHANGE } from '../../common/consts' import { isolateUserCode } from '../../common/utils' -import { addEventListener } from '../listeners' +import { addEventListener } from '../page/listeners' const COMPLETE = 'complete' let readyChecked = false diff --git a/packages/child/index.js b/packages/child/index.js index 27f048903..86d569a20 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -72,10 +72,10 @@ import { round, typeAssert, } from '../common/utils' +import checkBlockingCSS from './check/blocking-css' import checkQuirksMode from './check/quirks-mode' import checkReadyYet from './check/ready' import checkVersion from './check/version' -import checkBlockingCSS from './check-blocking-css' import { advise, assert, @@ -95,12 +95,6 @@ import { vInfo, warn, } from './console' -import { getBoolean, getNumber } from './from-string' -import { - addEventListener, - removeEventListener, - tearDownList, -} from './listeners' import createMutationObserver from './observers/mutation' import createOverflowObserver from './observers/overflow' import createPerformanceObserver, { @@ -109,9 +103,17 @@ import createPerformanceObserver, { } from './observers/perf' import createResizeObserver from './observers/resize' import createVisibilityObserver from './observers/visibility' +import createApplySelectors from './page/apply-selectors' import { setBodyStyle, setMargin } from './page/css' +import { + addEventListener, + removeEventListener, + tearDownList, +} from './page/listeners' import stopInfiniteResizingOfIframe from './page/stop-infinite-resizing' -import { readFunction, readNumber, readString } from './read' +import { getBoolean, getNumber } from './read/from-string' +import { readFunction, readNumber, readString } from './read/read' +import settings from './values/settings' function iframeResizerChild() { const customCalcMethods = { @@ -141,12 +143,12 @@ function iframeResizerChild() { const heightCalcModeDefault = AUTO const widthCalcModeDefault = SCROLL + let applySelectors = id let autoResize = true let bodyBackground = '' let bodyMargin = 0 let bodyMarginStr = '' let bodyPadding = '' - let bothDirections = false let calculateHeight = true let calculateWidth = false let firstRun = true @@ -210,18 +212,31 @@ function iframeResizerChild() { }) } + const checkBoth = ({ calculateWidth, calculateHeight }) => + calculateWidth === calculateHeight + + function map2settings(data) { + for (const [key, value] of Object.entries(data)) { + settings[key] = value + } + } + function init(data) { - readDataFromParent(data) + map2settings(readDataFromParent(data)) setConsoleOptions({ id: parentId, enabled: logging, expand: logExpand }) consoleEvent('initReceived') log(`Initialising iframe v${VERSION} ${window.location.href}`) - readDataFromPage() + map2settings(readDataFromPage()) + + const { bodyBackground, bodyPadding, version } = settings + const bothDirections = checkBoth(settings) + + applySelectors = createApplySelectors(settings) const setup = [ () => checkVersion(version), - checkBoth, checkMode, checkIgnoredElements, checkCrossDomain, @@ -232,7 +247,7 @@ function iframeResizerChild() { checkAndSetupTags, bothDirections ? id : checkBlockingCSS, - () => setMargin({ bodyMarginStr, bodyMargin }), + () => setMargin(settings), () => setBodyStyle('background', bodyBackground), () => setBodyStyle('padding', bodyPadding), @@ -347,6 +362,30 @@ function iframeResizerChild() { mode = getNumber(data[21]) ?? mode // sizeSelector = data[22] || sizeSelector logExpand = getBoolean(data[23]) ?? logExpand + + return { + parentId, + bodyMargin, + calculateWidth, + logging, + autoResize, + bodyMarginStr, + heightCalcMode, + bodyBackground, + bodyPadding, + tolerance, + inPageLinks, + resizeFrom, + widthCalcMode, + mouseEvents, + offsetHeight, + offsetWidth, + calculateHeight, + key, + version, + mode, + logExpand, + } } function readDataFromPage() { @@ -400,39 +439,33 @@ See https://iframe-resizer.com/api/child for more details.`, return calcMode } - if (mode === 1) return + if (mode === 1) return {} const data = window.iframeResizer || window.iFrameResizer - if (typeof data !== OBJECT) return + if (typeof data !== OBJECT) return {} readData(data) heightCalcMode = setupCustomCalcMethods(heightCalcMode, HEIGHT) widthCalcMode = setupCustomCalcMethods(widthCalcMode, WIDTH) info(`Set targetOrigin for parent: %c${targetOriginDefault}`, HIGHLIGHT) - } - function checkBoth() { - bothDirections = calculateWidth === calculateHeight - } - - function applySelector(name, attribute, selector) { - if (selector === '') return - - log(`${name}: %c${selector}`, HIGHLIGHT) - - for (const el of document.querySelectorAll(selector)) { - log(`Applying ${attribute} to:`, el) - el.toggleAttribute(attribute, true) + return { + onBeforeResize, + onMessage, + onReady, + offsetHeight, + offsetWidth, + key2, + ignoreSelector, + sizeSelector, + targetOriginDefault, + heightCalcMode, + widthCalcMode, } } - function applySelectors() { - applySelector('sizeSelector', SIZE_ATTR, sizeSelector) - applySelector('ignoreSelector', IGNORE_ATTR, ignoreSelector) - } - function manageTriggerEvent(options) { const listener = { add(eventName) { diff --git a/packages/child/page/apply-selectors.js b/packages/child/page/apply-selectors.js new file mode 100644 index 000000000..61e1d608d --- /dev/null +++ b/packages/child/page/apply-selectors.js @@ -0,0 +1,22 @@ +import { HIGHLIGHT } from 'auto-console-group' + +import { IGNORE_ATTR, SIZE_ATTR } from '../../common/consts' +import { log } from '../console' + +export const applySelector = (name, attribute, selector) => () => { + if (selector === '') return + + log(`${name}: %c${selector}`, HIGHLIGHT) + + for (const el of document.querySelectorAll(selector)) { + log(`Applying ${attribute} to:`, el) + el.toggleAttribute(attribute, true) + } +} + +export default function ({ sizeSelector, ignoreSelector }) { + return () => { + applySelector('sizeSelector', SIZE_ATTR, sizeSelector) + applySelector('ignoreSelector', IGNORE_ATTR, ignoreSelector) + } +} diff --git a/packages/child/listeners.js b/packages/child/page/listeners.js similarity index 94% rename from packages/child/listeners.js rename to packages/child/page/listeners.js index 949c45759..2b5250ab6 100644 --- a/packages/child/listeners.js +++ b/packages/child/page/listeners.js @@ -1,6 +1,6 @@ import { HIGHLIGHT } from 'auto-console-group' -import { log } from './console' +import { log } from '../console' export const tearDownList = [] diff --git a/packages/child/from-string.js b/packages/child/read/from-string.js similarity index 100% rename from packages/child/from-string.js rename to packages/child/read/from-string.js diff --git a/packages/child/from-string.test.js b/packages/child/read/from-string.test.js similarity index 95% rename from packages/child/from-string.test.js rename to packages/child/read/from-string.test.js index 553f98cd3..9e78b7865 100644 --- a/packages/child/from-string.test.js +++ b/packages/child/read/from-string.test.js @@ -1,4 +1,4 @@ -import { FALSE } from '../common/consts' +import { FALSE } from '../../common/consts' import { getBoolean, getNumber } from './from-string' describe('from-string utility functions', () => { diff --git a/packages/child/read.js b/packages/child/read/read.js similarity index 84% rename from packages/child/read.js rename to packages/child/read/read.js index 8cbba7312..61f20ca23 100644 --- a/packages/child/read.js +++ b/packages/child/read/read.js @@ -1,4 +1,4 @@ -import { BOOLEAN, FUNCTION, NUMBER, STRING } from '../common/consts' +import { BOOLEAN, FUNCTION, NUMBER, STRING } from '../../common/consts' const read = (type) => (data, key) => { if (!(key in data)) return diff --git a/packages/child/read.test.js b/packages/child/read/read.test.js similarity index 100% rename from packages/child/read.test.js rename to packages/child/read/read.test.js diff --git a/packages/child/values/settings.js b/packages/child/values/settings.js new file mode 100644 index 000000000..b1c6ea436 --- /dev/null +++ b/packages/child/values/settings.js @@ -0,0 +1 @@ +export default {} diff --git a/packages/child/values/state.js b/packages/child/values/state.js new file mode 100644 index 000000000..b1c6ea436 --- /dev/null +++ b/packages/child/values/state.js @@ -0,0 +1 @@ +export default {} From 62a6b3fbfe857c085d6a84a91a986fbca29221c4 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Wed, 29 Oct 2025 14:27:40 +0000 Subject: [PATCH 06/38] Extract settings and read functions --- packages/child/check/version.js | 2 +- packages/child/index.js | 316 +++++++----------------- packages/child/read/from-page.js | 89 +++++++ packages/child/read/from-parent.js | 33 +++ packages/child/read/from-string.js | 7 - packages/child/read/from-string.test.js | 37 --- packages/child/values/settings.js | 34 ++- packages/common/consts.js | 3 + 8 files changed, 252 insertions(+), 269 deletions(-) create mode 100644 packages/child/read/from-page.js create mode 100644 packages/child/read/from-parent.js delete mode 100644 packages/child/read/from-string.js delete mode 100644 packages/child/read/from-string.test.js diff --git a/packages/child/check/version.js b/packages/child/check/version.js index fc6485100..7d39dd128 100644 --- a/packages/child/check/version.js +++ b/packages/child/check/version.js @@ -1,7 +1,7 @@ import { FALSE, VERSION } from '../../common/consts' import { advise } from '../console' -export default function checkVersion(version) { +export default function checkVersion({ version }) { if (!version || version === '' || version === FALSE) { advise( `Legacy version detected on parent page diff --git a/packages/child/index.js b/packages/child/index.js index 86d569a20..b611f51f9 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -1,17 +1,16 @@ import { BOLD, FOREGROUND, HIGHLIGHT, ITALIC, NORMAL } from 'auto-console-group' import { - AUTO, AUTO_RESIZE, BASE, BEFORE_UNLOAD, BOOLEAN, - CHILD, CHILD_READY_MESSAGE, CLOSE, ENABLE, FUNCTION, HEIGHT, + HEIGHT_CALC_MODE_DEFAULT, HEIGHT_EDGE, IGNORE_ATTR, IGNORE_DISABLE_RESIZE, @@ -29,9 +28,6 @@ import { NO_CHANGE, NONE, NUMBER, - OBJECT, - OFFSET, - OFFSET_SIZE, OVERFLOW_ATTR, OVERFLOW_OBSERVER, PAGE_HIDE, @@ -42,7 +38,6 @@ import { PARENT_RESIZE_REQUEST, READY_STATE_CHANGE, RESIZE_OBSERVER, - SCROLL, SCROLL_BY, SCROLL_TO, SCROLL_TO_OFFSET, @@ -56,9 +51,10 @@ import { VERSION, VISIBILITY_OBSERVER, WIDTH, + WIDTH_CALC_MODE_DEFAULT, WIDTH_EDGE, } from '../common/consts' -import setMode, { getKey, getModeData, getModeLabel } from '../common/mode' +import setMode, { getModeData, getModeLabel } from '../common/mode' import { capitalizeFirstLetter, getElementName, @@ -82,7 +78,6 @@ import { debug, deprecateMethod, deprecateMethodReplace, - deprecateOption, endAutoGroup, error, errorBoundary, @@ -111,8 +106,8 @@ import { tearDownList, } from './page/listeners' import stopInfiniteResizingOfIframe from './page/stop-infinite-resizing' -import { getBoolean, getNumber } from './read/from-string' -import { readFunction, readNumber, readString } from './read/read' +import readDataFromPage from './read/from-page' +import readDataFromParent from './read/from-parent' import settings from './values/settings' function iframeResizerChild() { @@ -126,7 +121,7 @@ function iframeResizerChild() { return getWidth.auto() }, } - const deprecatedResizeMethods = { + const DEPRECATED_RESIZE_METHODS = { bodyOffset: 1, bodyScroll: 1, offset: 1, @@ -138,67 +133,53 @@ function iframeResizerChild() { grow: 1, lowestElement: 1, } - const eventCancelTimer = 128 + const EVENT_CANCEL_TIMER = 128 const eventHandlersByName = {} - const heightCalcModeDefault = AUTO - const widthCalcModeDefault = SCROLL let applySelectors = id - let autoResize = true - let bodyBackground = '' - let bodyMargin = 0 - let bodyMarginStr = '' - let bodyPadding = '' - let calculateHeight = true - let calculateWidth = false let firstRun = true let hasIgnored = false let hasOverflow = false let hasOverflowUpdated = true let hasTags = false let height = 1 - let heightCalcMode = heightCalcModeDefault // only applies if not provided by host page (V1 compatibility) - let ignoreSelector = '' let initLock = true let inPageLinks = {} let isHidden = false - let logExpand = false - let logging = false - let key - let key2 - let mode = 0 - let mouseEvents = false - let offsetHeight = 0 - let offsetWidth = 0 let origin let overflowedNodeSet = new Set() let overflowObserver - let parentId = '' - let resizeFrom = CHILD let resizeObserver let sameOrigin = false - let sizeSelector = '' let taggedElements = [] let target = window.parent - let targetOriginDefault = '*' let timerActive let totalTime - let tolerance = 0 let triggerLocked = false - let version let width = 1 - let widthCalcMode = widthCalcModeDefault let win = window - let onBeforeResize - let onMessage = () => { - warn('onMessage function not defined') - } - let onReady = () => {} let onPageInfo = null let onParentInfo = null + function setupCustomCalcMethods(calcMode, calcFunc) { + if (typeof calcMode === FUNCTION) { + advise( + `Deprecated Option(${calcFunc}CalculationMethod) + + The use of ${calcFunc}CalculationMethod as a function is deprecated and will be removed in a future version of iframe-resizer. Please use the new onBeforeResize event handler instead. + + See https://iframe-resizer.com/api/child for more details.`, + ) + customCalcMethods[calcFunc] = calcMode + calcMode = 'custom' + } + + return calcMode + } + function isolate(funcs) { + const { mode } = settings funcs.forEach((func) => { try { func() @@ -217,27 +198,30 @@ function iframeResizerChild() { function map2settings(data) { for (const [key, value] of Object.entries(data)) { - settings[key] = value + if (value) settings[key] = value } } - function init(data) { - map2settings(readDataFromParent(data)) - + function startLogging({ logExpand, logging, parentId }) { setConsoleOptions({ id: parentId, enabled: logging, expand: logExpand }) consoleEvent('initReceived') log(`Initialising iframe v${VERSION} ${window.location.href}`) + } - map2settings(readDataFromPage()) + function init(data) { + map2settings(readDataFromParent(data)) + startLogging(settings) + map2settings(readDataFromPage(setupCustomCalcMethods)) + // debug({ ...settings }) - const { bodyBackground, bodyPadding, version } = settings + const { bodyBackground, bodyPadding, inPageLinks, mode, onReady } = settings const bothDirections = checkBoth(settings) applySelectors = createApplySelectors(settings) const setup = [ - () => checkVersion(version), - checkMode, + () => checkVersion(settings), + () => checkMode(settings), checkIgnoredElements, checkCrossDomain, checkHeightMode, @@ -257,9 +241,9 @@ function iframeResizerChild() { applySelectors, attachObservers, - setupInPageLinks, + () => setupInPageLinks(inPageLinks), setupEventListeners, - setupMouseEvents, + () => setupMouseEvents(settings), setupOnPageHide, setupPublicMethods, ] @@ -331,141 +315,13 @@ function iframeResizerChild() { function checkCrossDomain() { try { - sameOrigin = mode === 1 || 'iframeParentListener' in window.parent + sameOrigin = + settings.mode === 1 || 'iframeParentListener' in window.parent } catch (error) { log('Cross domain iframe detected') } } - // eslint-disable-next-line sonarjs/cognitive-complexity - function readDataFromParent(data) { - parentId = data[0] ?? parentId - bodyMargin = getNumber(data[1]) ?? bodyMargin // For V1 compatibility - calculateWidth = getBoolean(data[2]) ?? calculateWidth - logging = getBoolean(data[3]) ?? logging - // data[4] no longer used (was intervalTimer) - autoResize = getBoolean(data[6]) ?? autoResize - bodyMarginStr = data[7] ?? bodyMarginStr - heightCalcMode = data[8] ?? heightCalcMode - bodyBackground = data[9] ?? bodyBackground - bodyPadding = data[10] ?? bodyPadding - tolerance = getNumber(data[11]) ?? tolerance - inPageLinks.enable = getBoolean(data[12]) ?? false - resizeFrom = data[13] ?? resizeFrom - widthCalcMode = data[14] ?? widthCalcMode - mouseEvents = getBoolean(data[15]) ?? mouseEvents - offsetHeight = getNumber(data[16]) ?? offsetHeight - offsetWidth = getNumber(data[17]) ?? offsetWidth - calculateHeight = getBoolean(data[18]) ?? calculateHeight - key = data[19] ?? key - version = data[20] ?? version - mode = getNumber(data[21]) ?? mode - // sizeSelector = data[22] || sizeSelector - logExpand = getBoolean(data[23]) ?? logExpand - - return { - parentId, - bodyMargin, - calculateWidth, - logging, - autoResize, - bodyMarginStr, - heightCalcMode, - bodyBackground, - bodyPadding, - tolerance, - inPageLinks, - resizeFrom, - widthCalcMode, - mouseEvents, - offsetHeight, - offsetWidth, - calculateHeight, - key, - version, - mode, - logExpand, - } - } - - function readDataFromPage() { - // eslint-disable-next-line sonarjs/cognitive-complexity - function readData(data) { - log(`Reading data from page:`, Object.keys(data)) - - onBeforeResize = readFunction(data, 'onBeforeResize') ?? onBeforeResize - onMessage = readFunction(data, 'onMessage') ?? onMessage - onReady = readFunction(data, 'onReady') ?? onReady - - if (typeof data?.offset === NUMBER) { - deprecateOption(OFFSET, OFFSET_SIZE) - if (calculateHeight) - offsetHeight = readNumber(data, OFFSET) ?? offsetHeight - if (calculateWidth) - offsetWidth = readNumber(data, OFFSET) ?? offsetWidth - } - - if (typeof data?.offsetSize === NUMBER) { - if (calculateHeight) - offsetHeight = readNumber(data, OFFSET_SIZE) ?? offsetHeight - if (calculateWidth) - offsetWidth = readNumber(data, OFFSET_SIZE) ?? offsetWidth - } - - key2 = readString(data, getKey(0)) ?? key2 - ignoreSelector = readString(data, 'ignoreSelector') ?? ignoreSelector - sizeSelector = readString(data, 'sizeSelector') ?? sizeSelector - targetOriginDefault = - readString(data, 'targetOrigin') ?? targetOriginDefault - - // String or Function - heightCalcMode = data?.heightCalculationMethod || heightCalcMode - widthCalcMode = data?.widthCalculationMethod || widthCalcMode - } - - function setupCustomCalcMethods(calcMode, calcFunc) { - if (typeof calcMode === FUNCTION) { - advise( - `Deprecated Option(${calcFunc}CalculationMethod) - -The use of ${calcFunc}CalculationMethod as a function is deprecated and will be removed in a future version of iframe-resizer. Please use the new onBeforeResize event handler instead. - -See https://iframe-resizer.com/api/child for more details.`, - ) - customCalcMethods[calcFunc] = calcMode - calcMode = 'custom' - } - - return calcMode - } - - if (mode === 1) return {} - - const data = window.iframeResizer || window.iFrameResizer - - if (typeof data !== OBJECT) return {} - - readData(data) - heightCalcMode = setupCustomCalcMethods(heightCalcMode, HEIGHT) - widthCalcMode = setupCustomCalcMethods(widthCalcMode, WIDTH) - - info(`Set targetOrigin for parent: %c${targetOriginDefault}`, HIGHLIGHT) - - return { - onBeforeResize, - onMessage, - onReady, - offsetHeight, - offsetWidth, - key2, - ignoreSelector, - sizeSelector, - targetOriginDefault, - heightCalcMode, - widthCalcMode, - } - } - function manageTriggerEvent(options) { const listener = { add(eventName) { @@ -533,8 +389,8 @@ The data-iframe-height and data-iframe-width attributes have been de calcMode = calcModeDefault } - if (calcMode in deprecatedResizeMethods) { - const actionMsg = version + if (calcMode in DEPRECATED_RESIZE_METHODS) { + const actionMsg = settings.version ? 'remove this option.' : `set this option to 'auto' when using an older version of iframe-resizer on the parent page. This can be done on the child page by adding the following code: @@ -558,22 +414,27 @@ This version of iframe-resizer can auto detect the most suitable ${label} } function checkHeightMode() { - heightCalcMode = checkCalcMode( - heightCalcMode, - heightCalcModeDefault, + settings.heightCalcMode = checkCalcMode( + settings.heightCalcMode, + HEIGHT_CALC_MODE_DEFAULT, getHeight, ) } function checkWidthMode() { - widthCalcMode = checkCalcMode(widthCalcMode, widthCalcModeDefault, getWidth) + settings.widthCalcMode = checkCalcMode( + settings.widthCalcMode, + WIDTH_CALC_MODE_DEFAULT, + getWidth, + ) } - function checkMode() { + function checkMode({ key, key2, mode, version }) { const oMode = mode const pMode = setMode({ key }) const cMode = setMode({ key: key2 }) mode = Math.max(pMode, cMode) + settings.mode = mode if (mode < 0) { mode = Math.min(pMode, cMode) purge() @@ -589,7 +450,7 @@ This version of iframe-resizer can auto detect the most suitable ${label} } function setupEventListeners() { - if (autoResize !== true) { + if (settings.autoResize !== true) { log('Auto Resize disabled') } @@ -607,7 +468,7 @@ This version of iframe-resizer can auto detect the most suitable ${label} document.body.append(clearFix) } - function setupInPageLinks() { + function setupInPageLinks(enabled) { const getPagePosition = () => ({ x: document.documentElement.scrollLeft, y: document.documentElement.scrollTop, @@ -679,7 +540,7 @@ This version of iframe-resizer can auto detect the most suitable ${label} function initCheck() { // Check if page loaded with location hash after init resize - setTimeout(checkLocationHash, eventCancelTimer) + setTimeout(checkLocationHash, EVENT_CANCEL_TIMER) } function enableInPageLinks() { @@ -689,9 +550,9 @@ This version of iframe-resizer can auto detect the most suitable ${label} initCheck() } - const { enable } = inPageLinks + const { mode } = settings - if (enable) { + if (enabled) { if (mode === 1) { advise(getModeData(5)) } else { @@ -707,7 +568,7 @@ This version of iframe-resizer can auto detect the most suitable ${label} } } - function setupMouseEvents() { + function setupMouseEvents({ mouseEvents }) { if (mouseEvents !== true) return function sendMouse(e) { @@ -724,13 +585,13 @@ This version of iframe-resizer can auto detect the most suitable ${label} } function setupPublicMethods() { - if (mode === 1) return + if (settings.mode === 1) return win.parentIframe = Object.freeze({ autoResize: (enable) => { typeAssert(enable, BOOLEAN, 'parentIframe.autoResize(enable) enable') + const { autoResize, calculateHeight, calculateWidth } = settings - // if (calculateWidth === calculateHeight) { if (calculateWidth === false && calculateHeight === false) { consoleEvent(ENABLE) advise( @@ -740,22 +601,22 @@ This version of iframe-resizer can auto detect the most suitable ${label} } if (enable === true && autoResize === false) { - autoResize = true + settings.autoResize = true queueMicrotask(() => sendSize(ENABLE, 'Auto Resize enabled')) } else if (enable === false && autoResize === true) { - autoResize = false + settings.autoResize = false } - sendMessage(0, 0, AUTO_RESIZE, JSON.stringify(autoResize)) + sendMessage(0, 0, AUTO_RESIZE, JSON.stringify(settings.autoResize)) - return autoResize + return settings.autoResize }, close() { sendMessage(0, 0, CLOSE) }, - getId: () => parentId, + getId: () => settings.parentId, getOrigin: () => { consoleEvent('getOrigin') @@ -817,8 +678,8 @@ This version of iframe-resizer can auto detect the most suitable ${label} NUMBER, 'parentIframe.setOffsetSize(offset) offset', ) - offsetHeight = newOffset - offsetWidth = newOffset + settings.offsetHeight = newOffset + settings.offsetWidth = newOffset sendSize(SET_OFFSET_SIZE, `parentIframe.setOffsetSize(${newOffset})`) }, @@ -851,12 +712,12 @@ This version of iframe-resizer can auto detect the most suitable ${label} }, setHeightCalculationMethod(heightCalculationMethod) { - heightCalcMode = heightCalculationMethod + settings.heightCalcMode = heightCalculationMethod checkHeightMode() }, setWidthCalculationMethod(widthCalculationMethod) { - widthCalcMode = widthCalculationMethod + settings.widthCalcMode = widthCalculationMethod checkWidthMode() }, @@ -868,7 +729,7 @@ This version of iframe-resizer can auto detect the most suitable ${label} ) log(`Set targetOrigin: %c${targetOrigin}`, HIGHLIGHT) - targetOriginDefault = targetOrigin + settings.targetOrigin = targetOrigin }, resize(customHeight, customWidth) { @@ -972,7 +833,7 @@ This version of iframe-resizer can auto detect the most suitable ${label} function createOverflowObservers(nodeList) { const overflowObserverOptions = { root: document.documentElement, - side: calculateHeight ? HEIGHT_EDGE : WIDTH_EDGE, + side: settings.calculateHeight ? HEIGHT_EDGE : WIDTH_EDGE, } overflowObserver = createOverflowObserver( @@ -1079,6 +940,7 @@ This version of iframe-resizer can auto detect the most suitable ${label} performance.mark(PREF_START) const Side = capitalizeFirstLetter(side) + const { logging } = settings let elVal = MIN_SIZE let maxEl = document.documentElement @@ -1261,8 +1123,8 @@ This version of iframe-resizer can auto detect the most suitable ${label} const getHeight = { label: HEIGHT, - enabled: () => calculateHeight, - getOffset: () => offsetHeight, + enabled: () => settings.calculateHeight, + getOffset: () => settings.offsetHeight, auto: () => getAutoSize(getHeight), bodyOffset: getBodyOffset, bodyScroll: () => document.body.scrollHeight, @@ -1284,8 +1146,8 @@ This version of iframe-resizer can auto detect the most suitable ${label} const getWidth = { label: WIDTH, - enabled: () => calculateWidth, - getOffset: () => offsetWidth, + enabled: () => settings.calculateWidth, + getOffset: () => settings.offsetWidth, auto: () => getAutoSize(getWidth), bodyScroll: () => document.body.scrollWidth, bodyOffset: () => document.body.offsetWidth, @@ -1305,10 +1167,10 @@ This version of iframe-resizer can auto detect the most suitable ${label} taggedElement: () => getMaxElement(WIDTH_EDGE), } - const checkTolerance = (a, b) => !(Math.abs(a - b) <= tolerance) + const checkTolerance = (a, b) => !(Math.abs(a - b) <= settings.tolerance) function callOnBeforeResize(newSize) { - const returnedSize = onBeforeResize(newSize) + const returnedSize = settings.onBeforeResize(newSize) if (returnedSize === undefined) { throw new TypeError( @@ -1333,7 +1195,7 @@ This version of iframe-resizer can auto detect the most suitable ${label} function getNewSize(direction, mode) { const calculatedSize = direction[mode]() const newSize = - direction.enabled() && onBeforeResize !== undefined + direction.enabled() && settings.onBeforeResize !== undefined ? callOnBeforeResize(calculatedSize) : calculatedSize @@ -1352,6 +1214,9 @@ This version of iframe-resizer can auto detect the most suitable ${label} customWidth, msg, ) { + const { calculateHeight, calculateWidth, heightCalcMode, widthCalcMode } = + settings + const isSizeChangeDetected = () => (calculateHeight && checkTolerance(height, newHeight)) || (calculateWidth && checkTolerance(width, newWidth)) @@ -1403,6 +1268,7 @@ This version of iframe-resizer can auto detect the most suitable ${label} const sendSize = errorBoundary( (triggerEvent, triggerEventDesc, customHeight, customWidth, msg) => { consoleEvent(triggerEvent) + const { autoResize } = settings switch (true) { case isHidden === true: { @@ -1470,6 +1336,9 @@ This version of iframe-resizer can auto detect the most suitable ${label} } function triggerReset(triggerEvent) { + const { heightCalcMode, widthCalcMode } = settings + + log(`Reset trigger event: %c${triggerEvent}`, HIGHLIGHT) height = getHeight[heightCalcMode]() width = getWidth[widthCalcMode]() @@ -1477,22 +1346,22 @@ This version of iframe-resizer can auto detect the most suitable ${label} } function resetIframe(triggerEventDesc) { - const hcm = heightCalcMode - heightCalcMode = heightCalcModeDefault + const hcm = settings.heightCalcMode + settings.heightCalcMode = HEIGHT_CALC_MODE_DEFAULT log(`Reset trigger event: %c${triggerEventDesc}`, HIGHLIGHT) lockTrigger() triggerReset('reset') - heightCalcMode = hcm + settings.heightCalcMode = hcm } function dispatchMessage(height, width, triggerEvent, msg, targetOrigin) { - if (mode < -1) return + if (settings.mode < -1) return function setTargetOrigin() { if (undefined === targetOrigin) { - targetOrigin = targetOriginDefault + targetOrigin = settings.targetOrigin return } @@ -1507,6 +1376,7 @@ This version of iframe-resizer can auto detect the most suitable ${label} } function dispatchToParent() { + const { mode, parentId } = settings const size = `${height}:${width}` const message = `${parentId}:${size}:${triggerEvent}${undefined === msg ? '' : `:${msg}`}` @@ -1567,7 +1437,7 @@ This version of iframe-resizer can auto detect the most suitable ${label} setTimeout(() => { initLock = false - }, eventCancelTimer) + }, EVENT_CANCEL_TIMER) }, reset() { @@ -1618,7 +1488,7 @@ This version of iframe-resizer can auto detect the most suitable ${label} const msgBody = getData() log(`onMessage called from parent:%c`, HIGHLIGHT, parseFrozen(msgBody)) // eslint-disable-next-line sonarjs/no-extra-arguments - isolateUserCode(onMessage, parse(msgBody)) + isolateUserCode(settings.onMessage, parse(msgBody)) }, } diff --git a/packages/child/read/from-page.js b/packages/child/read/from-page.js new file mode 100644 index 000000000..6a757bd2d --- /dev/null +++ b/packages/child/read/from-page.js @@ -0,0 +1,89 @@ +import { HIGHLIGHT } from 'auto-console-group' + +import { + BOOLEAN, + FUNCTION, + HEIGHT, + NUMBER, + OBJECT, + OFFSET, + OFFSET_SIZE, + STRING, + WIDTH, +} from '../../common/consts' +import { getKey } from '../../common/mode' +import { deprecateOption, info, log } from '../console' +// import { setupCustomCalcMethods } from '../methods/custom-calcs' +import settings from '../values/settings' + +const read = (type) => (data, key) => { + if (!(key in data)) return + // eslint-disable-next-line valid-typeof, consistent-return + if (typeof data[key] === type) return data[key] + + throw new TypeError(`${key} is not a ${type}.`) +} + +export const readFunction = read(FUNCTION) +export const readBoolean = read(BOOLEAN) +export const readNumber = read(NUMBER) +export const readString = read(STRING) + +function readData(data, setupCustomCalcMethods) { + const { calculateHeight, calculateWidth } = settings + let offsetHeight + let offsetWidth + + log(`Reading data from page:`, Object.keys(data)) + + if (typeof data?.offset === NUMBER) { + deprecateOption(OFFSET, OFFSET_SIZE) + if (calculateHeight) offsetHeight = readNumber(data, OFFSET) + if (calculateWidth) offsetWidth = readNumber(data, OFFSET) + } + + if (typeof data?.offsetSize === NUMBER) { + if (calculateHeight) offsetHeight = readNumber(data, OFFSET_SIZE) + if (calculateWidth) offsetWidth = readNumber(data, OFFSET_SIZE) + } + + return { + offsetHeight, + offsetWidth, + key2: readString(data, getKey(0)), + ignoreSelector: readString(data, 'ignoreSelector'), + sizeSelector: readString(data, 'sizeSelector'), + + targetOrigin: readString(data, 'targetOrigin'), + + heightCalcMode: setupCustomCalcMethods( + data?.heightCalculationMethod, + HEIGHT, + ), + widthCalcMode: setupCustomCalcMethods(data?.widthCalculationMethod, WIDTH), + + onBeforeResize: readFunction(data, 'onBeforeResize'), + onMessage: readFunction(data, 'onMessage'), + onReady: readFunction(data, 'onReady'), + } +} + +export default function readDataFromPage(setupCustomCalcMethods) { + const { mode, targetOrigin } = settings + if (mode === 1) return {} + + const data = window.iframeResizer || window.iFrameResizer + + if (typeof data !== OBJECT) { + return {} + } + + const localSettings = readData(data, setupCustomCalcMethods) + + info( + `Set targetOrigin for parent: %c${localSettings.targetOrigin || targetOrigin}`, + HIGHLIGHT, + ) + + return localSettings +} diff --git a/packages/child/read/from-parent.js b/packages/child/read/from-parent.js new file mode 100644 index 000000000..8485e60de --- /dev/null +++ b/packages/child/read/from-parent.js @@ -0,0 +1,33 @@ +const strBool = (str) => str === 'true' + +const castDefined = (cast) => (data) => + undefined === data ? undefined : cast(data) + +const getBoolean = castDefined(strBool) +const getNumber = castDefined(Number) + +export default (data) => ({ + parentId: data[0], + bodyMargin: getNumber(data[1]), + calculateWidth: getBoolean(data[2]), + logging: getBoolean(data[3]), + // data[4] no longer used (was intervalTimer) + autoResize: getBoolean(data[6]), + bodyMarginStr: data[7], + heightCalcMode: data[8], + bodyBackground: data[9], + bodyPadding: data[10], + tolerance: getNumber(data[11]), + inPageLinks: getBoolean(data[12]), + // data[13] no longer used (was resizeFrom) + widthCalcMode: data[14], + mouseEvents: getBoolean(data[15]), + offsetHeight: getNumber(data[16]), + offsetWidth: getNumber(data[17]), + calculateHeight: getBoolean(data[18]), + key: data[19], + version: data[20], + mode: getNumber(data[21]), + // sizeSelector: data[22] // Now only available via page settings + logExpand: getBoolean(data[23]), +}) diff --git a/packages/child/read/from-string.js b/packages/child/read/from-string.js deleted file mode 100644 index d910e554b..000000000 --- a/packages/child/read/from-string.js +++ /dev/null @@ -1,7 +0,0 @@ -const strBool = (str) => str === 'true' - -const castDefined = (cast) => (data) => - undefined === data ? undefined : cast(data) - -export const getBoolean = castDefined(strBool) -export const getNumber = castDefined(Number) diff --git a/packages/child/read/from-string.test.js b/packages/child/read/from-string.test.js deleted file mode 100644 index 9e78b7865..000000000 --- a/packages/child/read/from-string.test.js +++ /dev/null @@ -1,37 +0,0 @@ -import { FALSE } from '../../common/consts' -import { getBoolean, getNumber } from './from-string' - -describe('from-string utility functions', () => { - describe('getBoolean', () => { - test('should return true for the string "true"', () => { - expect(getBoolean('true')).toBe(true) - }) - - test('should return false for the string "false"', () => { - expect(getBoolean(FALSE)).toBe(false) - }) - - test('should return undefined for undefined input', () => { - expect(getBoolean()).toBeUndefined() - }) - - test('should return false for non-boolean strings', () => { - expect(getBoolean('random')).toBe(false) - }) - }) - - describe('getNumber', () => { - test('should return a number for valid numeric strings', () => { - expect(getNumber('42')).toBe(42) - expect(getNumber('3.14')).toBe(3.14) - }) - - test('should return NaN for non-numeric strings', () => { - expect(getNumber('random')).toBeNaN() - }) - - test('should return undefined for undefined input', () => { - expect(getNumber()).toBeUndefined() - }) - }) -}) diff --git a/packages/child/values/settings.js b/packages/child/values/settings.js index b1c6ea436..4fdbc3cb7 100644 --- a/packages/child/values/settings.js +++ b/packages/child/values/settings.js @@ -1 +1,33 @@ -export default {} +import { + HEIGHT_CALC_MODE_DEFAULT, + WIDTH_CALC_MODE_DEFAULT, +} from '../../common/consts' +import { warn } from '../console' + +export default { + bodyMargin: 0, // For V1 compatibility + calculateWidth: false, + logging: false, + autoResize: true, + bodyMarginStr: '', + heightCalcMode: HEIGHT_CALC_MODE_DEFAULT, + bodyBackground: '', + bodyPadding: '', + tolerance: 0, + inPageLinks: false, + widthCalcMode: WIDTH_CALC_MODE_DEFAULT, + mouseEvents: false, + offsetHeight: 0, + offsetWidth: 0, + calculateHeight: true, + mode: 0, + logExpand: false, + ignoreSelector: '', + sizeSelector: '', + targetOrigin: '*', + onBeforeResize: undefined, + onMessage: () => { + warn('onMessage function not defined') + }, + onReady: () => {}, +} diff --git a/packages/common/consts.js b/packages/common/consts.js index dded1e5af..da4c91925 100644 --- a/packages/common/consts.js +++ b/packages/common/consts.js @@ -120,6 +120,9 @@ export const INIT_EVENTS = Object.freeze({ export const EXPAND = 'expanded' export const COLLAPSE = 'collapsed' +export const HEIGHT_CALC_MODE_DEFAULT = AUTO +export const WIDTH_CALC_MODE_DEFAULT = SCROLL + export const LOG_OPTIONS = Object.freeze({ [EXPAND]: 1, [COLLAPSE]: 1, From d9996e813f8f71aa0c8400b3181a30e5f4d84f60 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Thu, 30 Oct 2025 10:48:12 +0000 Subject: [PATCH 07/38] Extract checks from child --- packages/child/check/calculation-mode.js | 57 +++++++++++++++ packages/child/check/deprecated-attributes.js | 22 ++++++ packages/child/index.js | 71 +------------------ packages/child/read/read.js | 14 ---- packages/child/read/read.test.js | 49 ------------- 5 files changed, 82 insertions(+), 131 deletions(-) create mode 100644 packages/child/check/calculation-mode.js create mode 100644 packages/child/check/deprecated-attributes.js delete mode 100644 packages/child/read/read.js delete mode 100644 packages/child/read/read.test.js diff --git a/packages/child/check/calculation-mode.js b/packages/child/check/calculation-mode.js new file mode 100644 index 000000000..01be3c3d6 --- /dev/null +++ b/packages/child/check/calculation-mode.js @@ -0,0 +1,57 @@ +import { HIGHLIGHT } from 'auto-console-group' + +import { advise, log, warn } from '../console' +import settings from '../values/settings' + +const DEPRECATED_RESIZE_METHODS = { + bodyOffset: 1, + bodyScroll: 1, + offset: 1, + documentElementOffset: 1, + documentElementScroll: 1, + boundingClientRect: 1, + max: 1, + min: 1, + grow: 1, + lowestElement: 1, +} + +const olderVersions = ( + label, +) => `set this option to 'auto' when using an older version of iframe-resizer on the parent page. This can be done on the child page by adding the following code: + +window.iframeResizer = { +license: 'xxxx', +${label}CalculationMethod: AUTO, +} +` + +function showDeprecationWarning(label, calcMode) { + const actionMsg = settings.version + ? 'remove this option.' + : olderVersions(label) + + advise( + `Deprecated ${label}CalculationMethod (${calcMode}) + +This version of iframe-resizer can auto detect the most suitable ${label} calculation method. It is recommended that you ${actionMsg} +`, + ) +} + +export default function checkCalcMode(calcMode, calcModeDefault, modes) { + const { label } = modes + + if (calcModeDefault !== calcMode) { + if (!(calcMode in modes)) { + warn(`${calcMode} is not a valid option for ${label}CalculationMethod.`) + calcMode = calcModeDefault + } + + if (calcMode in DEPRECATED_RESIZE_METHODS) + showDeprecationWarning(label, calcMode) + } + + log(`Set ${label} calculation method: %c${calcMode}`, HIGHLIGHT) + return calcMode +} diff --git a/packages/child/check/deprecated-attributes.js b/packages/child/check/deprecated-attributes.js new file mode 100644 index 000000000..7748191d4 --- /dev/null +++ b/packages/child/check/deprecated-attributes.js @@ -0,0 +1,22 @@ +import { SIZE_ATTR } from '../../common/consts' +import { advise } from '../console' + +const DEPRECATED = `Deprecated Attributes + +The data-iframe-height and data-iframe-width attributes have been deprecated and replaced with the single data-iframe-size attribute. Use of the old attributes will be removed in a future version of iframe-resizer.` + +export default function checkDeprecatedAttrs() { + let found = false + + const checkAttrs = (attr) => + document.querySelectorAll(`[${attr}]`).forEach((el) => { + el.toggleAttribute(SIZE_ATTR, true) + el.removeAttribute(attr) + found = true + }) + + checkAttrs('data-iframe-height') + checkAttrs('data-iframe-width') + + if (found) advise(DEPRECATED) +} diff --git a/packages/child/index.js b/packages/child/index.js index b611f51f9..5235a6abe 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -61,7 +61,6 @@ import { id, invoke, isDef, - // isElement, isolateUserCode, lower, once, @@ -69,6 +68,8 @@ import { typeAssert, } from '../common/utils' import checkBlockingCSS from './check/blocking-css' +import checkCalcMode from './check/calculation-mode' +import checkDeprecatedAttrs from './check/deprecated-attributes' import checkQuirksMode from './check/quirks-mode' import checkReadyYet from './check/ready' import checkVersion from './check/version' @@ -121,18 +122,7 @@ function iframeResizerChild() { return getWidth.auto() }, } - const DEPRECATED_RESIZE_METHODS = { - bodyOffset: 1, - bodyScroll: 1, - offset: 1, - documentElementOffset: 1, - documentElementScroll: 1, - boundingClientRect: 1, - max: 1, - min: 1, - grow: 1, - lowestElement: 1, - } + const EVENT_CANCEL_TIMER = 128 const eventHandlersByName = {} @@ -358,61 +348,6 @@ function iframeResizerChild() { }) } - function checkDeprecatedAttrs() { - let found = false - - const checkAttrs = (attr) => - document.querySelectorAll(`[${attr}]`).forEach((el) => { - found = true - el.removeAttribute(attr) - el.toggleAttribute(SIZE_ATTR, true) - }) - - checkAttrs('data-iframe-height') - checkAttrs('data-iframe-width') - - if (found) { - advise( - `Deprecated Attributes - -The data-iframe-height and data-iframe-width attributes have been deprecated and replaced with the single data-iframe-size attribute. Use of the old attributes will be removed in a future version of iframe-resizer.`, - ) - } - } - - function checkCalcMode(calcMode, calcModeDefault, modes) { - const { label } = modes - - if (calcModeDefault !== calcMode) { - if (!(calcMode in modes)) { - warn(`${calcMode} is not a valid option for ${label}CalculationMethod.`) - calcMode = calcModeDefault - } - - if (calcMode in DEPRECATED_RESIZE_METHODS) { - const actionMsg = settings.version - ? 'remove this option.' - : `set this option to 'auto' when using an older version of iframe-resizer on the parent page. This can be done on the child page by adding the following code: - -window.iframeResizer = { - license: 'xxxx', - ${label}CalculationMethod: AUTO, -} -` - - advise( - `Deprecated ${label}CalculationMethod (${calcMode}) - -This version of iframe-resizer can auto detect the most suitable ${label} calculation method. It is recommended that you ${actionMsg} -`, - ) - } - } - - log(`Set ${label} calculation method: %c${calcMode}`, HIGHLIGHT) - return calcMode - } - function checkHeightMode() { settings.heightCalcMode = checkCalcMode( settings.heightCalcMode, diff --git a/packages/child/read/read.js b/packages/child/read/read.js deleted file mode 100644 index 61f20ca23..000000000 --- a/packages/child/read/read.js +++ /dev/null @@ -1,14 +0,0 @@ -import { BOOLEAN, FUNCTION, NUMBER, STRING } from '../../common/consts' - -const read = (type) => (data, key) => { - if (!(key in data)) return - // eslint-disable-next-line valid-typeof, consistent-return - if (typeof data[key] === type) return data[key] - - throw new TypeError(`${key} is not a ${type}.`) -} - -export const readFunction = read(FUNCTION) -export const readBoolean = read(BOOLEAN) -export const readNumber = read(NUMBER) -export const readString = read(STRING) diff --git a/packages/child/read/read.test.js b/packages/child/read/read.test.js deleted file mode 100644 index 87417ed68..000000000 --- a/packages/child/read/read.test.js +++ /dev/null @@ -1,49 +0,0 @@ -import { readBoolean, readFunction, readNumber, readString } from './read' - -describe('read utility functions', () => { - const mockData = { - funcKey: () => {}, - boolKey: true, - numKey: 42, - strKey: 'test', - } - - test('readFunction should return the function if the key exists and is a function', () => { - expect(readFunction(mockData, 'funcKey')).toBe(mockData.funcKey) - }) - - test('readFunction should throw a TypeError if the key is not a function', () => { - expect(() => readFunction(mockData, 'boolKey')).toThrow(TypeError) - }) - - test('readBoolean should return the boolean if the key exists and is a boolean', () => { - expect(readBoolean(mockData, 'boolKey')).toBe(true) - }) - - test('readBoolean should throw a TypeError if the key is not a boolean', () => { - expect(() => readBoolean(mockData, 'numKey')).toThrow(TypeError) - }) - - test('readNumber should return the number if the key exists and is a number', () => { - expect(readNumber(mockData, 'numKey')).toBe(42) - }) - - test('readNumber should throw a TypeError if the key is not a number', () => { - expect(() => readNumber(mockData, 'strKey')).toThrow(TypeError) - }) - - test('readString should return the string if the key exists and is a string', () => { - expect(readString(mockData, 'strKey')).toBe('test') - }) - - test('readString should throw a TypeError if the key is not a string', () => { - expect(() => readString(mockData, 'funcKey')).toThrow(TypeError) - }) - - test('read functions should return undefined if the key does not exist', () => { - expect(readFunction(mockData, 'missingKey')).toBeUndefined() - expect(readBoolean(mockData, 'missingKey')).toBeUndefined() - expect(readNumber(mockData, 'missingKey')).toBeUndefined() - expect(readString(mockData, 'missingKey')).toBeUndefined() - }) -}) From a940d80cb7af313b2bc5bedace9859787ab2e3e5 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Thu, 30 Oct 2025 11:00:56 +0000 Subject: [PATCH 08/38] Extract more checks --- packages/child/check/mode.js | 25 +++++++++++++++++++++++++ packages/child/index.js | 25 ++----------------------- 2 files changed, 27 insertions(+), 23 deletions(-) create mode 100644 packages/child/check/mode.js diff --git a/packages/child/check/mode.js b/packages/child/check/mode.js new file mode 100644 index 000000000..bb17753a0 --- /dev/null +++ b/packages/child/check/mode.js @@ -0,0 +1,25 @@ +import { VERSION } from '../../common/consts' +import setMode, { getModeData, getModeLabel } from '../../common/mode' +import { isDef } from '../../common/utils' +import { advise, purge, vInfo } from '../console' +import settings from '../values/settings' + +export default function ({ key, key2, mode, version }) { + const oMode = mode + const pMode = setMode({ key }) + const cMode = setMode({ key: key2 }) + // eslint-disable-next-line no-multi-assign + settings.mode = mode = Math.max(pMode, cMode) + if (mode < 0) { + mode = Math.min(pMode, cMode) + purge() + advise(`${getModeData(mode + 2)}${getModeData(2)}`) + if (isDef(version)) + throw getModeData(mode + 2).replace(/<\/?[a-z][^>]*>|<\/>/gi, '') + } else if (!isDef(version) || (oMode > -1 && mode > oMode)) { + if (sessionStorage.getItem('ifr') !== VERSION) + vInfo(`v${VERSION} (${getModeLabel(mode)})`, mode) + if (mode < 2) advise(getModeData(3)) + sessionStorage.setItem('ifr', VERSION) + } +} diff --git a/packages/child/index.js b/packages/child/index.js index 5235a6abe..3e057f812 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -54,13 +54,12 @@ import { WIDTH_CALC_MODE_DEFAULT, WIDTH_EDGE, } from '../common/consts' -import setMode, { getModeData, getModeLabel } from '../common/mode' +import { getModeData } from '../common/mode' import { capitalizeFirstLetter, getElementName, id, invoke, - isDef, isolateUserCode, lower, once, @@ -70,6 +69,7 @@ import { import checkBlockingCSS from './check/blocking-css' import checkCalcMode from './check/calculation-mode' import checkDeprecatedAttrs from './check/deprecated-attributes' +import checkMode from './check/mode' import checkQuirksMode from './check/quirks-mode' import checkReadyYet from './check/ready' import checkVersion from './check/version' @@ -88,7 +88,6 @@ import { log, purge, setConsoleOptions, - vInfo, warn, } from './console' import createMutationObserver from './observers/mutation' @@ -364,26 +363,6 @@ function iframeResizerChild() { ) } - function checkMode({ key, key2, mode, version }) { - const oMode = mode - const pMode = setMode({ key }) - const cMode = setMode({ key: key2 }) - mode = Math.max(pMode, cMode) - settings.mode = mode - if (mode < 0) { - mode = Math.min(pMode, cMode) - purge() - advise(`${getModeData(mode + 2)}${getModeData(2)}`) - if (isDef(version)) - throw getModeData(mode + 2).replace(/<\/?[a-z][^>]*>|<\/>/gi, '') - } else if (!isDef(version) || (oMode > -1 && mode > oMode)) { - if (sessionStorage.getItem('ifr') !== VERSION) - vInfo(`v${VERSION} (${getModeLabel(mode)})`, mode) - if (mode < 2) advise(getModeData(3)) - sessionStorage.setItem('ifr', VERSION) - } - } - function setupEventListeners() { if (settings.autoResize !== true) { log('Auto Resize disabled') From f734959115f88294b44c49b90bf0734010e80673 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Thu, 30 Oct 2025 11:35:24 +0000 Subject: [PATCH 09/38] Extract more checks --- packages/child/check/mode.js | 5 +++-- packages/child/index.js | 6 +++--- packages/common/mode.js | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/child/check/mode.js b/packages/child/check/mode.js index bb17753a0..a275872b5 100644 --- a/packages/child/check/mode.js +++ b/packages/child/check/mode.js @@ -1,15 +1,16 @@ import { VERSION } from '../../common/consts' import setMode, { getModeData, getModeLabel } from '../../common/mode' import { isDef } from '../../common/utils' -import { advise, purge, vInfo } from '../console' +import { advise, log, purge, vInfo } from '../console' import settings from '../values/settings' export default function ({ key, key2, mode, version }) { const oMode = mode const pMode = setMode({ key }) - const cMode = setMode({ key: key2 }) + const cMode = setMode({ key2 }) // eslint-disable-next-line no-multi-assign settings.mode = mode = Math.max(pMode, cMode) + log('Final mode set to:', getModeLabel(mode)) if (mode < 0) { mode = Math.min(pMode, cMode) purge() diff --git a/packages/child/index.js b/packages/child/index.js index 3e057f812..499d09ff1 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -203,7 +203,7 @@ function iframeResizerChild() { map2settings(readDataFromPage(setupCustomCalcMethods)) // debug({ ...settings }) - const { bodyBackground, bodyPadding, inPageLinks, mode, onReady } = settings + const { bodyBackground, bodyPadding, inPageLinks, onReady } = settings const bothDirections = checkBoth(settings) applySelectors = createApplySelectors(settings) @@ -240,7 +240,7 @@ function iframeResizerChild() { isolate(setup) checkReadyYet(once(onReady)) - log('Initialization complete') + log('Initialization complete', settings) endAutoGroup() sendSize( @@ -248,7 +248,7 @@ function iframeResizerChild() { 'Init message from host page', undefined, undefined, - `${VERSION}:${mode}`, + `${VERSION}:${settings.mode}`, ) sendTitle() diff --git a/packages/common/mode.js b/packages/common/mode.js index 6814ef013..cf6c38d54 100644 --- a/packages/common/mode.js +++ b/packages/common/mode.js @@ -20,7 +20,7 @@ const l = (l) => { (l <= 'Z' ? 90 : 122) >= (l = l.codePointAt(0) + 19) ? l : l - 26, ), ), - x = ['spjluzl', 'rlf', 'clyzpvu'], + x = ['spjluzl', 'rlf', 'clyzpvu', 'rlf2'], y = [ 'Puchspk Spjluzl Rlf', 'Tpzzpun Spjluzl Rlf', @@ -44,7 +44,7 @@ export const getModeData = (l) => p(y[l]) export const getModeLabel = (l) => p(z[l]) export const getKey = (l) => p(x[l]) export default (y) => { - const z = y[p(x[0])] || y[p(x[1])] || y[p(x[2])] + const z = y[p(x[0])] || y[p(x[1])] || y[p(x[2])] || y[p(x[3])] if (!z) return -1 const u = z.split('-') let v = (function (y = '') { From 51341d58a524f8f2cc0747e46f36e17b804d4f3e Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Thu, 30 Oct 2025 11:45:46 +0000 Subject: [PATCH 10/38] Extract check xDomain --- packages/child/check/cross-domain.js | 12 ++++++++++++ packages/child/index.js | 14 ++++---------- packages/child/values/state.js | 4 +++- 3 files changed, 19 insertions(+), 11 deletions(-) create mode 100644 packages/child/check/cross-domain.js diff --git a/packages/child/check/cross-domain.js b/packages/child/check/cross-domain.js new file mode 100644 index 000000000..48ba7c2e0 --- /dev/null +++ b/packages/child/check/cross-domain.js @@ -0,0 +1,12 @@ +import { log } from '../console' +import settings from '../values/settings' +import state from '../values/state' + +export default function checkCrossDomain() { + try { + state.sameOrigin = + settings.mode === 1 || 'iframeParentListener' in window.parent + } catch (error) { + log('Cross domain iframe detected') + } +} diff --git a/packages/child/index.js b/packages/child/index.js index 499d09ff1..ff16bc5b6 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -68,6 +68,7 @@ import { } from '../common/utils' import checkBlockingCSS from './check/blocking-css' import checkCalcMode from './check/calculation-mode' +import checkCrossDomain from './check/cross-domain' import checkDeprecatedAttrs from './check/deprecated-attributes' import checkMode from './check/mode' import checkQuirksMode from './check/quirks-mode' @@ -109,6 +110,7 @@ import stopInfiniteResizingOfIframe from './page/stop-infinite-resizing' import readDataFromPage from './read/from-page' import readDataFromParent from './read/from-parent' import settings from './values/settings' +import state from './values/state' function iframeResizerChild() { const customCalcMethods = { @@ -139,7 +141,7 @@ function iframeResizerChild() { let overflowedNodeSet = new Set() let overflowObserver let resizeObserver - let sameOrigin = false + let taggedElements = [] let target = window.parent let timerActive @@ -302,15 +304,6 @@ function iframeResizerChild() { return hasIgnored } - function checkCrossDomain() { - try { - sameOrigin = - settings.mode === 1 || 'iframeParentListener' in window.parent - } catch (error) { - log('Cross domain iframe detected') - } - } - function manageTriggerEvent(options) { const listener = { add(eventName) { @@ -1291,6 +1284,7 @@ function iframeResizerChild() { function dispatchToParent() { const { mode, parentId } = settings + const { sameOrigin } = state const size = `${height}:${width}` const message = `${parentId}:${size}:${triggerEvent}${undefined === msg ? '' : `:${msg}`}` diff --git a/packages/child/values/state.js b/packages/child/values/state.js index b1c6ea436..03b6191a4 100644 --- a/packages/child/values/state.js +++ b/packages/child/values/state.js @@ -1 +1,3 @@ -export default {} +export default { + sameOrigin: false, +} From 59ad000627d97c0a67225dcb04ee7326349552bf Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Thu, 30 Oct 2025 15:39:19 +0000 Subject: [PATCH 11/38] extract sendMessage() --- packages/child/index.js | 96 +++++++--------------------------- packages/child/send/message.js | 73 ++++++++++++++++++++++++++ packages/child/values/state.js | 1 + 3 files changed, 93 insertions(+), 77 deletions(-) create mode 100644 packages/child/send/message.js diff --git a/packages/child/index.js b/packages/child/index.js index ff16bc5b6..21ffe1d04 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -1,4 +1,4 @@ -import { BOLD, FOREGROUND, HIGHLIGHT, ITALIC, NORMAL } from 'auto-console-group' +import { BOLD, FOREGROUND, HIGHLIGHT, NORMAL } from 'auto-console-group' import { AUTO_RESIZE, @@ -63,7 +63,6 @@ import { isolateUserCode, lower, once, - round, typeAssert, } from '../common/utils' import checkBlockingCSS from './check/blocking-css' @@ -109,6 +108,7 @@ import { import stopInfiniteResizingOfIframe from './page/stop-infinite-resizing' import readDataFromPage from './read/from-page' import readDataFromParent from './read/from-parent' +import sendMessage, { dispatch } from './send/message' import settings from './values/settings' import state from './values/state' @@ -143,9 +143,6 @@ function iframeResizerChild() { let resizeObserver let taggedElements = [] - let target = window.parent - let timerActive - let totalTime let triggerLocked = false let width = 1 let win = window @@ -1114,12 +1111,11 @@ function iframeResizerChild() { return newSize } - function sizeIframe( + function getContentSize( triggerEvent, triggerEventDesc, customHeight, customWidth, - msg, ) { const { calculateHeight, calculateWidth, heightCalcMode, widthCalcMode } = settings @@ -1146,8 +1142,7 @@ function iframeResizerChild() { width = newWidth // eslint-disable-next-line no-fallthrough case SET_OFFSET_SIZE: - dispatchMessage(height, width, triggerEvent, msg) - break + return { height, width } // the following case needs {} to prevent a compile error case OVERFLOW_OBSERVER: @@ -1164,11 +1159,10 @@ function iframeResizerChild() { info(NO_CHANGE) } - timerActive = false // Reset time for next resize + return null } let sendPending = false - const sendFailed = once(() => advise(getModeData(4))) let hiddenMessageShown = false let rafId @@ -1203,8 +1197,18 @@ function iframeResizerChild() { default: { hiddenMessageShown = false sendPending = true - totalTime = performance.now() - timerActive = true + state.totalTime = performance.now() + state.timerActive = true + + const newSize = getContentSize( + triggerEvent, + triggerEventDesc, + customHeight, + customWidth, + ) + + if (newSize) + dispatch(newSize.height, newSize.width, triggerEvent, msg) if (!rafId) rafId = requestAnimationFrame(() => { @@ -1214,13 +1218,7 @@ function iframeResizerChild() { debug(`Reset sendPending: %c${triggerEvent}`, HIGHLIGHT) }) - sizeIframe( - triggerEvent, - triggerEventDesc, - customHeight, - customWidth, - msg, - ) + state.timerActive = false // Reset time for next resize } } @@ -1263,62 +1261,6 @@ function iframeResizerChild() { settings.heightCalcMode = hcm } - function dispatchMessage(height, width, triggerEvent, msg, targetOrigin) { - if (settings.mode < -1) return - - function setTargetOrigin() { - if (undefined === targetOrigin) { - targetOrigin = settings.targetOrigin - return - } - - log(`Message targetOrigin: %c${targetOrigin}`, HIGHLIGHT) - } - - function displayTimeTaken() { - const timer = round(performance.now() - totalTime) - return triggerEvent === INIT - ? `Initialised iframe in %c${timer}ms` - : `Size calculated in %c${timer}ms` - } - - function dispatchToParent() { - const { mode, parentId } = settings - const { sameOrigin } = state - const size = `${height}:${width}` - const message = `${parentId}:${size}:${triggerEvent}${undefined === msg ? '' : `:${msg}`}` - - if (sameOrigin) - try { - window.parent.iframeParentListener(MESSAGE_ID + message) - } catch (error) { - if (mode === 1) sendFailed() - else throw error - return - } - else target.postMessage(MESSAGE_ID + message, targetOrigin) - - if (timerActive) log(displayTimeTaken(), HIGHLIGHT) - - info( - `Sending message to parent page via ${sameOrigin ? 'sameOrigin' : 'postMessage'}: %c%c${message}`, - ITALIC, - HIGHLIGHT, - ) - } - - setTargetOrigin() - dispatchToParent() - } - - const sendMessage = errorBoundary( - (height, width, triggerEvent, message, targetOrigin) => { - consoleEvent(triggerEvent) - dispatchMessage(height, width, triggerEvent, message, targetOrigin) - endAutoGroup() - }, - ) - function receiver(event) { consoleEvent('onMessage') const { freeze } = Object @@ -1336,7 +1278,7 @@ function iframeResizerChild() { const data = event.data.slice(MESSAGE_ID_LENGTH).split(SEPARATOR) - target = event.source + state.target = event.source origin = event.origin init(data) diff --git a/packages/child/send/message.js b/packages/child/send/message.js new file mode 100644 index 000000000..055f201c7 --- /dev/null +++ b/packages/child/send/message.js @@ -0,0 +1,73 @@ +import { HIGHLIGHT, ITALIC } from 'auto-console-group' + +import { INIT, MESSAGE_ID } from '../../common/consts' +import { getModeData } from '../../common/mode' +import { once, round } from '../../common/utils' +import { + advise, + endAutoGroup, + errorBoundary, + event as consoleEvent, + info, + log, +} from '../console' +import settings from '../values/settings' +import state from '../values/state' + +const sendFailed = once(() => advise(getModeData(4))) + +export function dispatch(height, width, triggerEvent, msg, targetOrigin) { + if (settings.mode < -1) return + + function setTargetOrigin() { + if (undefined === targetOrigin) { + targetOrigin = settings.targetOrigin + return + } + + log(`Message targetOrigin: %c${targetOrigin}`, HIGHLIGHT) + } + + function displayTimeTaken() { + const timer = round(performance.now() - state.totalTime) + return triggerEvent === INIT + ? `Initialised iframe in %c${timer}ms` + : `Size calculated in %c${timer}ms` + } + + function dispatchToParent() { + const { mode, parentId } = settings + const { sameOrigin, timerActive } = state + const size = `${height}:${width}` + const message = `${parentId}:${size}:${triggerEvent}${undefined === msg ? '' : `:${msg}`}` + + if (sameOrigin) + try { + window.parent.iframeParentListener(MESSAGE_ID + message) + } catch (error) { + if (mode === 1) sendFailed() + else throw error + return + } + else state.target.postMessage(MESSAGE_ID + message, targetOrigin) + + if (timerActive) log(displayTimeTaken(), HIGHLIGHT) + + info( + `Sending message to parent page via ${sameOrigin ? 'sameOrigin' : 'postMessage'}: %c%c${message}`, + ITALIC, + HIGHLIGHT, + ) + } + + setTargetOrigin() + dispatchToParent() +} + +export default errorBoundary( + (height, width, triggerEvent, message, targetOrigin) => { + consoleEvent(triggerEvent) + dispatch(height, width, triggerEvent, message, targetOrigin) + endAutoGroup() + }, +) diff --git a/packages/child/values/state.js b/packages/child/values/state.js index 03b6191a4..68a9bc3df 100644 --- a/packages/child/values/state.js +++ b/packages/child/values/state.js @@ -1,3 +1,4 @@ export default { sameOrigin: false, + target: window?.parent, } From e06cbc2760b57e04b3c5ee7bfe167c5334ae9fb4 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Fri, 31 Oct 2025 22:19:31 +0000 Subject: [PATCH 12/38] Extract ready() --- packages/child/index.js | 37 ++++------------------------------ packages/child/page/ready.js | 33 ++++++++++++++++++++++++++++++ packages/child/values/state.js | 1 + 3 files changed, 38 insertions(+), 33 deletions(-) create mode 100644 packages/child/page/ready.js diff --git a/packages/child/index.js b/packages/child/index.js index 21ffe1d04..e59083d2a 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -5,7 +5,6 @@ import { BASE, BEFORE_UNLOAD, BOOLEAN, - CHILD_READY_MESSAGE, CLOSE, ENABLE, FUNCTION, @@ -105,6 +104,7 @@ import { removeEventListener, tearDownList, } from './page/listeners' +import ready from './page/ready' import stopInfiniteResizingOfIframe from './page/stop-infinite-resizing' import readDataFromPage from './read/from-page' import readDataFromParent from './read/from-parent' @@ -128,7 +128,6 @@ function iframeResizerChild() { const eventHandlersByName = {} let applySelectors = id - let firstRun = true let hasIgnored = false let hasOverflow = false let hasOverflowUpdated = true @@ -948,7 +947,7 @@ function iframeResizerChild() { break case !hasOverflow && - firstRun && + state.firstRun && prevBoundingSize[dimension] === 0 && prevScrollSize[dimension] === 0: info(`Initial page size values: ${sizes}`, ...BOUNDING_FORMAT) @@ -1283,7 +1282,7 @@ function iframeResizerChild() { init(data) - firstRun = false + state.firstRun = false setTimeout(() => { initLock = false @@ -1374,7 +1373,7 @@ function iframeResizerChild() { } function processMessage() { - if (firstRun === false) { + if (state.firstRun === false) { callFromParent() return } @@ -1398,34 +1397,6 @@ function iframeResizerChild() { const received = errorBoundary(receiver) - // Normally the parent kicks things off when it detects the iframe has loaded. - // If this script is async-loaded, then tell parent page to retry init. - let sent = false - const sendReady = (target) => - target.postMessage( - CHILD_READY_MESSAGE, - window?.iframeResizer?.targetOrigin || '*', - ) - - function ready() { - if (document.readyState === 'loading' || !firstRun || sent) return - - const { parent, top } = window - - consoleEvent('ready') - log( - 'Sending%c ready%c to parent from', - HIGHLIGHT, - FOREGROUND, - window.location.href, - ) - - sendReady(parent) - if (parent !== top) sendReady(top) - - sent = true - } - if ('iframeChildListener' in window) { warn('Already setup') } else { diff --git a/packages/child/page/ready.js b/packages/child/page/ready.js new file mode 100644 index 000000000..222330762 --- /dev/null +++ b/packages/child/page/ready.js @@ -0,0 +1,33 @@ +import { FOREGROUND, HIGHLIGHT } from 'auto-console-group' + +import { CHILD_READY_MESSAGE } from '../../common/consts' +import { event as consoleEvent, log } from '../console' +import state from '../values/state' + +// Normally the parent kicks things off when it detects the iframe has loaded. +// If this script is async-loaded, then tell parent page to retry init. +let sent = false +const sendReady = (target) => + target.postMessage( + CHILD_READY_MESSAGE, + window?.iframeResizer?.targetOrigin || '*', + ) + +export default function ready() { + if (document.readyState === 'loading' || !state.firstRun || sent) return + + const { parent, top } = window + + consoleEvent('ready') + log( + 'Sending%c ready%c to parent from', + HIGHLIGHT, + FOREGROUND, + window.location.href, + ) + + sendReady(parent) + if (parent !== top) sendReady(top) + + sent = true +} diff --git a/packages/child/values/state.js b/packages/child/values/state.js index 68a9bc3df..14b79c4d4 100644 --- a/packages/child/values/state.js +++ b/packages/child/values/state.js @@ -1,4 +1,5 @@ export default { + firstRun: true, sameOrigin: false, target: window?.parent, } From 9acbadc72cf3080ff6c463b42717e9447e0bcbb4 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Fri, 31 Oct 2025 22:34:07 +0000 Subject: [PATCH 13/38] Extract mouse events --- packages/child/check/ready.js | 2 +- packages/child/{page => events}/listeners.js | 0 packages/child/events/mouse.js | 21 +++++++++ packages/child/{page => events}/ready.js | 0 packages/child/index.js | 48 ++++---------------- packages/child/page/clear-fix.js | 10 ++++ 6 files changed, 41 insertions(+), 40 deletions(-) rename packages/child/{page => events}/listeners.js (100%) create mode 100644 packages/child/events/mouse.js rename packages/child/{page => events}/ready.js (100%) create mode 100644 packages/child/page/clear-fix.js diff --git a/packages/child/check/ready.js b/packages/child/check/ready.js index 2b1701b99..8c6239a1c 100644 --- a/packages/child/check/ready.js +++ b/packages/child/check/ready.js @@ -1,6 +1,6 @@ import { READY_STATE_CHANGE } from '../../common/consts' import { isolateUserCode } from '../../common/utils' -import { addEventListener } from '../page/listeners' +import { addEventListener } from '../events/listeners' const COMPLETE = 'complete' let readyChecked = false diff --git a/packages/child/page/listeners.js b/packages/child/events/listeners.js similarity index 100% rename from packages/child/page/listeners.js rename to packages/child/events/listeners.js diff --git a/packages/child/events/mouse.js b/packages/child/events/mouse.js new file mode 100644 index 000000000..459496f30 --- /dev/null +++ b/packages/child/events/mouse.js @@ -0,0 +1,21 @@ +import { HIGHLIGHT } from 'auto-console-group' + +import { MOUSE_ENTER, MOUSE_LEAVE } from '../../common/consts' +import { log } from '../console' +import sendMessage from '../send/message' +import { addEventListener } from './listeners' + +const sendMouse = (evt) => + sendMessage(0, 0, evt.type, `${evt.screenY}:${evt.screenX}`) + +function addMouseListener(evt, name) { + log(`Add event listener: %c${name}`, HIGHLIGHT) + addEventListener(window.document, evt, sendMouse) +} + +export default function setupMouseEvents({ mouseEvents }) { + if (mouseEvents !== true) return + + addMouseListener(MOUSE_ENTER, 'Mouse Enter') + addMouseListener(MOUSE_LEAVE, 'Mouse Leave') +} diff --git a/packages/child/page/ready.js b/packages/child/events/ready.js similarity index 100% rename from packages/child/page/ready.js rename to packages/child/events/ready.js diff --git a/packages/child/index.js b/packages/child/index.js index e59083d2a..cde5b7153 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -21,8 +21,6 @@ import { MESSAGE_ID, MESSAGE_ID_LENGTH, MIN_SIZE, - MOUSE_ENTER, - MOUSE_LEAVE, MUTATION_OBSERVER, NO_CHANGE, NONE, @@ -89,6 +87,13 @@ import { setConsoleOptions, warn, } from './console' +import { + addEventListener, + removeEventListener, + tearDownList, +} from './events/listeners' +import setupMouseEvents from './events/mouse' +import ready from './events/ready' import createMutationObserver from './observers/mutation' import createOverflowObserver from './observers/overflow' import createPerformanceObserver, { @@ -98,13 +103,8 @@ import createPerformanceObserver, { import createResizeObserver from './observers/resize' import createVisibilityObserver from './observers/visibility' import createApplySelectors from './page/apply-selectors' +import injectClearFixIntoBodyElement from './page/clear-fix' import { setBodyStyle, setMargin } from './page/css' -import { - addEventListener, - removeEventListener, - tearDownList, -} from './page/listeners' -import ready from './page/ready' import stopInfiniteResizingOfIframe from './page/stop-infinite-resizing' import readDataFromPage from './read/from-page' import readDataFromParent from './read/from-parent' @@ -303,12 +303,9 @@ function iframeResizerChild() { function manageTriggerEvent(options) { const listener = { add(eventName) { - function handleEvent() { - sendSize(options.eventName, options.eventType) - } + const handleEvent = () => sendSize(options.eventName, options.eventType) eventHandlersByName[eventName] = handleEvent - addEventListener(window, eventName, handleEvent, { passive: true }) }, remove(eventName) { @@ -360,17 +357,6 @@ function iframeResizerChild() { manageEventListeners('add') } - function injectClearFixIntoBodyElement() { - const clearFix = document.createElement('div') - - clearFix.style.clear = 'both' - // Guard against the following having been globally redefined in CSS. - clearFix.style.display = 'block' - clearFix.style.height = '0' - - document.body.append(clearFix) - } - function setupInPageLinks(enabled) { const getPagePosition = () => ({ x: document.documentElement.scrollLeft, @@ -471,22 +457,6 @@ function iframeResizerChild() { } } - function setupMouseEvents({ mouseEvents }) { - if (mouseEvents !== true) return - - function sendMouse(e) { - sendMessage(0, 0, e.type, `${e.screenY}:${e.screenX}`) - } - - function addMouseListener(evt, name) { - log(`Add event listener: %c${name}`, HIGHLIGHT) - addEventListener(window.document, evt, sendMouse) - } - - addMouseListener(MOUSE_ENTER, 'Mouse Enter') - addMouseListener(MOUSE_LEAVE, 'Mouse Leave') - } - function setupPublicMethods() { if (settings.mode === 1) return diff --git a/packages/child/page/clear-fix.js b/packages/child/page/clear-fix.js new file mode 100644 index 000000000..0b5c14738 --- /dev/null +++ b/packages/child/page/clear-fix.js @@ -0,0 +1,10 @@ +export default function injectClearFixIntoBodyElement() { + const clearFix = document.createElement('div') + + clearFix.style.clear = 'both' + // Guard against the following having been globally redefined in CSS. + clearFix.style.display = 'block' + clearFix.style.height = '0' + + document.body.append(clearFix) +} From bb464707ab1c44aafb5eb9d58c4583181c19fa0e Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Fri, 31 Oct 2025 22:46:08 +0000 Subject: [PATCH 14/38] extract sendTitle --- packages/child/index.js | 8 +------- packages/child/send/title.js | 9 +++++++++ 2 files changed, 10 insertions(+), 7 deletions(-) create mode 100644 packages/child/send/title.js diff --git a/packages/child/index.js b/packages/child/index.js index cde5b7153..dd5a74f1e 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -43,7 +43,6 @@ import { SIZE_ATTR, SIZE_CHANGE_DETECTED, STRING, - TITLE, UNDEFINED, VERSION, VISIBILITY_OBSERVER, @@ -109,6 +108,7 @@ import stopInfiniteResizingOfIframe from './page/stop-infinite-resizing' import readDataFromPage from './read/from-page' import readDataFromParent from './read/from-parent' import sendMessage, { dispatch } from './send/message' +import sendTitle from './send/title' import settings from './values/settings' import state from './values/state' @@ -271,12 +271,6 @@ function iframeResizerChild() { log(`Tagged elements found: %c${hasTags}`, HIGHLIGHT) } - function sendTitle() { - if (document.title && document.title !== '') { - sendMessage(0, 0, TITLE, document.title) - } - } - function warnIgnored(ignoredElements) { const s = ignoredElements.length === 1 ? '' : 's' diff --git a/packages/child/send/title.js b/packages/child/send/title.js new file mode 100644 index 000000000..9513969cf --- /dev/null +++ b/packages/child/send/title.js @@ -0,0 +1,9 @@ +import { TITLE } from '../../common/consts' +import sendMessage from './message' + +export default function sendTitle() { + const { title } = document + if (title && title !== '') { + sendMessage(0, 0, TITLE, title) + } +} From 65429ec57e5f4f327b85a6360795ba6afa030771 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Sat, 1 Nov 2025 13:26:12 +0000 Subject: [PATCH 15/38] tidy --- packages/child/send/title.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/child/send/title.js b/packages/child/send/title.js index 9513969cf..8113e27e1 100644 --- a/packages/child/send/title.js +++ b/packages/child/send/title.js @@ -3,7 +3,5 @@ import sendMessage from './message' export default function sendTitle() { const { title } = document - if (title && title !== '') { - sendMessage(0, 0, TITLE, title) - } + if (title && title !== '') sendMessage(0, 0, TITLE, title) } From 9a60d0c870a8e0f09c51e42e8139c3a120bb22a3 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Sat, 1 Nov 2025 13:53:30 +0000 Subject: [PATCH 16/38] Extract size calc functions --- packages/child/index.js | 302 ++--------------------------- packages/child/read/from-page.js | 34 ++-- packages/child/size/all.js | 13 ++ packages/child/size/auto.js | 144 ++++++++++++++ packages/child/size/body-offset.js | 13 ++ packages/child/size/custom.js | 30 +++ packages/child/size/get-height.js | 35 ++++ packages/child/size/get-width.js | 34 ++++ packages/child/size/index.js | 2 + packages/child/size/max-element.js | 64 ++++++ packages/child/values/state.js | 6 + 11 files changed, 377 insertions(+), 300 deletions(-) create mode 100644 packages/child/size/all.js create mode 100644 packages/child/size/auto.js create mode 100644 packages/child/size/body-offset.js create mode 100644 packages/child/size/custom.js create mode 100644 packages/child/size/get-height.js create mode 100644 packages/child/size/get-width.js create mode 100644 packages/child/size/index.js create mode 100644 packages/child/size/max-element.js diff --git a/packages/child/index.js b/packages/child/index.js index dd5a74f1e..ddd92496a 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -8,12 +8,10 @@ import { CLOSE, ENABLE, FUNCTION, - HEIGHT, HEIGHT_CALC_MODE_DEFAULT, HEIGHT_EDGE, IGNORE_ATTR, IGNORE_DISABLE_RESIZE, - IGNORE_TAGS, IN_PAGE_LINK, INIT, MANUAL_RESIZE_REQUEST, @@ -46,13 +44,11 @@ import { UNDEFINED, VERSION, VISIBILITY_OBSERVER, - WIDTH, WIDTH_CALC_MODE_DEFAULT, WIDTH_EDGE, } from '../common/consts' import { getModeData } from '../common/mode' import { - capitalizeFirstLetter, getElementName, id, invoke, @@ -95,10 +91,7 @@ import setupMouseEvents from './events/mouse' import ready from './events/ready' import createMutationObserver from './observers/mutation' import createOverflowObserver from './observers/overflow' -import createPerformanceObserver, { - PREF_END, - PREF_START, -} from './observers/perf' +import createPerformanceObserver from './observers/perf' import createResizeObserver from './observers/resize' import createVisibilityObserver from './observers/visibility' import createApplySelectors from './page/apply-selectors' @@ -109,62 +102,31 @@ import readDataFromPage from './read/from-page' import readDataFromParent from './read/from-parent' import sendMessage, { dispatch } from './send/message' import sendTitle from './send/title' +import { getHeight, getWidth } from './size' +import { getAllElements } from './size/all' import settings from './values/settings' import state from './values/state' function iframeResizerChild() { - const customCalcMethods = { - height: () => { - warn('Custom height calculation function not defined') - return getHeight.auto() - }, - width: () => { - warn('Custom width calculation function not defined') - return getWidth.auto() - }, - } - const EVENT_CANCEL_TIMER = 128 const eventHandlersByName = {} let applySelectors = id let hasIgnored = false - let hasOverflow = false - let hasOverflowUpdated = true - let hasTags = false let height = 1 let initLock = true let inPageLinks = {} let isHidden = false let origin - let overflowedNodeSet = new Set() let overflowObserver let resizeObserver - let taggedElements = [] - let triggerLocked = false let width = 1 let win = window let onPageInfo = null let onParentInfo = null - function setupCustomCalcMethods(calcMode, calcFunc) { - if (typeof calcMode === FUNCTION) { - advise( - `Deprecated Option(${calcFunc}CalculationMethod) - - The use of ${calcFunc}CalculationMethod as a function is deprecated and will be removed in a future version of iframe-resizer. Please use the new onBeforeResize event handler instead. - - See https://iframe-resizer.com/api/child for more details.`, - ) - customCalcMethods[calcFunc] = calcMode - calcMode = 'custom' - } - - return calcMode - } - function isolate(funcs) { const { mode } = settings funcs.forEach((func) => { @@ -198,7 +160,7 @@ function iframeResizerChild() { function init(data) { map2settings(readDataFromParent(data)) startLogging(settings) - map2settings(readDataFromPage(setupCustomCalcMethods)) + map2settings(readDataFromPage()) // debug({ ...settings }) const { bodyBackground, bodyPadding, inPageLinks, onReady } = settings @@ -266,9 +228,9 @@ function iframeResizerChild() { addEventListener(window, lower(PAGE_HIDE), onPageHide) function checkAndSetupTags() { - taggedElements = document.querySelectorAll(`[${SIZE_ATTR}]`) - hasTags = taggedElements.length > 0 - log(`Tagged elements found: %c${hasTags}`, HIGHLIGHT) + state.taggedElements = document.querySelectorAll(`[${SIZE_ATTR}]`) + state.hasTags = state.taggedElements.length > 0 + log(`Tagged elements found: %c${state.hasTags}`, HIGHLIGHT) } function warnIgnored(ignoredElements) { @@ -663,22 +625,25 @@ function iframeResizerChild() { function checkOverflow() { const allOverflowedNodes = document.querySelectorAll(`[${OVERFLOW_ATTR}]`) - overflowedNodeSet = filterIgnoredElements(allOverflowedNodes) + state.overflowedNodeSet = filterIgnoredElements(allOverflowedNodes) - hasOverflow = overflowedNodeSet.size > 0 + state.hasOverflow = state.overflowedNodeSet.size > 0 // Not supported in Safari 16 (or esLint!!!) // eslint-disable-next-line no-use-extend-native/no-use-extend-native if (typeof Set.prototype.symmetricDifference === FUNCTION) - hasOverflowUpdated = - overflowedNodeSet.symmetricDifference(prevOverflowedNodeSet).size > 0 + state.hasOverflowUpdated = + state.overflowedNodeSet.symmetricDifference(prevOverflowedNodeSet) + .size > 0 - prevOverflowedNodeSet = overflowedNodeSet + prevOverflowedNodeSet = state.overflowedNodeSet } function overflowObserved() { checkOverflow() + const { hasOverflowUpdated, hasOverflow, overflowedNodeSet } = state + switch (true) { case !hasOverflowUpdated: return @@ -803,237 +768,6 @@ function iframeResizerChild() { pushDisconnectsOnToTearDown(observers) } - function getMaxElement(side) { - performance.mark(PREF_START) - - const Side = capitalizeFirstLetter(side) - const { logging } = settings - - let elVal = MIN_SIZE - let maxEl = document.documentElement - let maxVal = hasTags - ? 0 - : document.documentElement.getBoundingClientRect().bottom - - const targetElements = hasTags - ? taggedElements - : hasOverflow - ? Array.from(overflowedNodeSet) - : getAllElements(document.documentElement) // Width resizing may need to check all elements - - for (const element of targetElements) { - elVal = - element.getBoundingClientRect()[side] + - parseFloat(getComputedStyle(element).getPropertyValue(`margin-${side}`)) - - if (elVal > maxVal) { - maxVal = elVal - maxEl = element - } - } - - info(`${Side} position calculated from:`, maxEl) - info(`Checked %c${targetElements.length}%c elements`, HIGHLIGHT, FOREGROUND) - - performance.mark(PREF_END, { - detail: { - hasTags, - len: targetElements.length, - logging, - Side, - }, - }) - - return maxVal - } - - const getAllMeasurements = (dimension) => [ - dimension.bodyOffset(), - dimension.bodyScroll(), - dimension.documentElementOffset(), - dimension.documentElementScroll(), - dimension.boundingClientRect(), - ] - - const addNot = (tagName) => `:not(${tagName})` - const selector = `* ${Array.from(IGNORE_TAGS).map(addNot).join('')}` - const getAllElements = (node) => node.querySelectorAll(selector) - - function getOffsetSize(getDimension) { - const offset = getDimension.getOffset() - - if (offset !== 0) { - info(`Page offsetSize: %c${offset}px`, HIGHLIGHT) - } - - return offset - } - - const prevScrollSize = { - height: 0, - width: 0, - } - - const prevBoundingSize = { - height: 0, - width: 0, - } - - const getAdjustedScroll = (getDimension) => - getDimension.documentElementScroll() + Math.max(0, getDimension.getOffset()) - - const BOUNDING_FORMAT = [HIGHLIGHT, FOREGROUND, HIGHLIGHT] - - function getAutoSize(getDimension) { - function returnBoundingClientRect() { - prevBoundingSize[dimension] = boundingSize - prevScrollSize[dimension] = scrollSize - return Math.max(boundingSize, MIN_SIZE) - } - - const isHeight = getDimension === getHeight - const dimension = getDimension.label - const boundingSize = getDimension.boundingClientRect() - const ceilBoundingSize = Math.ceil(boundingSize) - const floorBoundingSize = Math.floor(boundingSize) - const scrollSize = getAdjustedScroll(getDimension) - const sizes = `HTML: %c${boundingSize}px %cPage: %c${scrollSize}px` - - let calculatedSize = MIN_SIZE - - switch (true) { - case !getDimension.enabled(): - return Math.max(scrollSize, MIN_SIZE) - - case hasTags: - info(`Found element with data-iframe-size attribute`) - calculatedSize = getDimension.taggedElement() - break - - case !hasOverflow && - state.firstRun && - prevBoundingSize[dimension] === 0 && - prevScrollSize[dimension] === 0: - info(`Initial page size values: ${sizes}`, ...BOUNDING_FORMAT) - calculatedSize = returnBoundingClientRect() - break - - case triggerLocked && - boundingSize === prevBoundingSize[dimension] && - scrollSize === prevScrollSize[dimension]: - info(`Size unchanged: ${sizes}`, ...BOUNDING_FORMAT) - calculatedSize = Math.max(boundingSize, scrollSize) - break - - case boundingSize === 0 && scrollSize !== 0: - info(`Page is hidden: ${sizes}`, ...BOUNDING_FORMAT) - calculatedSize = scrollSize - break - - case !hasOverflow && - boundingSize !== prevBoundingSize[dimension] && - scrollSize <= prevScrollSize[dimension]: - info(`New size: ${sizes} `, ...BOUNDING_FORMAT) - info( - `Previous size: %c${prevBoundingSize[dimension]}px`, - HIGHLIGHT, - ) - calculatedSize = returnBoundingClientRect() - break - - case !isHeight: - calculatedSize = getDimension.taggedElement() - break - - case !hasOverflow && boundingSize < prevBoundingSize[dimension]: - info(` size decreased: ${sizes}`, ...BOUNDING_FORMAT) - calculatedSize = returnBoundingClientRect() - break - - case scrollSize === floorBoundingSize || scrollSize === ceilBoundingSize: - info(` size equals page size: ${sizes}`, ...BOUNDING_FORMAT) - calculatedSize = returnBoundingClientRect() - break - - case boundingSize > scrollSize: - info(`Page size < size: ${sizes}`, ...BOUNDING_FORMAT) - calculatedSize = returnBoundingClientRect() - break - - case hasOverflow: - info(`Found elements possibly overflowing `) - calculatedSize = getDimension.taggedElement() - break - - default: - info(`Using size: ${sizes}`, ...BOUNDING_FORMAT) - calculatedSize = returnBoundingClientRect() - } - - info(`Content ${dimension}: %c${calculatedSize}px`, HIGHLIGHT) - - calculatedSize += getOffsetSize(getDimension) - - return Math.max(calculatedSize, MIN_SIZE) - } - - const getBodyOffset = () => { - const { body } = document - const style = getComputedStyle(body) - - return ( - body.offsetHeight + - parseInt(style.marginTop, BASE) + - parseInt(style.marginBottom, BASE) - ) - } - - const getHeight = { - label: HEIGHT, - enabled: () => settings.calculateHeight, - getOffset: () => settings.offsetHeight, - auto: () => getAutoSize(getHeight), - bodyOffset: getBodyOffset, - bodyScroll: () => document.body.scrollHeight, - offset: () => getHeight.bodyOffset(), // Backwards compatibility - custom: () => customCalcMethods.height(), - documentElementOffset: () => document.documentElement.offsetHeight, - documentElementScroll: () => document.documentElement.scrollHeight, - boundingClientRect: () => - Math.max( - document.documentElement.getBoundingClientRect().bottom, - document.body.getBoundingClientRect().bottom, - ), - max: () => Math.max(...getAllMeasurements(getHeight)), - min: () => Math.min(...getAllMeasurements(getHeight)), - grow: () => getHeight.max(), - lowestElement: () => getMaxElement(HEIGHT_EDGE), - taggedElement: () => getMaxElement(HEIGHT_EDGE), - } - - const getWidth = { - label: WIDTH, - enabled: () => settings.calculateWidth, - getOffset: () => settings.offsetWidth, - auto: () => getAutoSize(getWidth), - bodyScroll: () => document.body.scrollWidth, - bodyOffset: () => document.body.offsetWidth, - custom: () => customCalcMethods.width(), - documentElementScroll: () => document.documentElement.scrollWidth, - documentElementOffset: () => document.documentElement.offsetWidth, - boundingClientRect: () => - Math.max( - document.documentElement.getBoundingClientRect().right, - document.body.getBoundingClientRect().right, - ), - max: () => Math.max(...getAllMeasurements(getWidth)), - min: () => Math.min(...getAllMeasurements(getWidth)), - rightMostElement: () => getMaxElement(WIDTH_EDGE), - scroll: () => - Math.max(getWidth.bodyScroll(), getWidth.documentElementScroll()), - taggedElement: () => getMaxElement(WIDTH_EDGE), - } - const checkTolerance = (a, b) => !(Math.abs(a - b) <= settings.tolerance) function callOnBeforeResize(newSize) { @@ -1190,15 +924,15 @@ function iframeResizerChild() { ) function lockTrigger() { - if (triggerLocked) { + if (state.triggerLocked) { log('TriggerLock blocked calculation') return } - triggerLocked = true + state.triggerLocked = true debug('Trigger event lock on') requestAnimationFrame(() => { - triggerLocked = false + state.triggerLocked = false debug('Trigger event lock off') }) } diff --git a/packages/child/read/from-page.js b/packages/child/read/from-page.js index 6a757bd2d..b2eed191f 100644 --- a/packages/child/read/from-page.js +++ b/packages/child/read/from-page.js @@ -1,7 +1,6 @@ import { HIGHLIGHT } from 'auto-console-group' import { - BOOLEAN, FUNCTION, HEIGHT, NUMBER, @@ -13,7 +12,7 @@ import { } from '../../common/consts' import { getKey } from '../../common/mode' import { deprecateOption, info, log } from '../console' -// import { setupCustomCalcMethods } from '../methods/custom-calcs' +import setupCustomCalcMethod from '../size/custom' import settings from '../values/settings' const read = (type) => (data, key) => { @@ -25,17 +24,17 @@ const read = (type) => (data, key) => { } export const readFunction = read(FUNCTION) -export const readBoolean = read(BOOLEAN) export const readNumber = read(NUMBER) export const readString = read(STRING) -function readData(data, setupCustomCalcMethods) { +const isObject = (obj) => + obj !== null && typeof obj === OBJECT && !Array.isArray(obj) + +function readOffsetSize(data) { const { calculateHeight, calculateWidth } = settings let offsetHeight let offsetWidth - log(`Reading data from page:`, Object.keys(data)) - if (typeof data?.offset === NUMBER) { deprecateOption(OFFSET, OFFSET_SIZE) if (calculateHeight) offsetHeight = readNumber(data, OFFSET) @@ -47,20 +46,27 @@ function readData(data, setupCustomCalcMethods) { if (calculateWidth) offsetWidth = readNumber(data, OFFSET_SIZE) } + return { offsetHeight, offsetWidth } +} + +function readData(data) { + log(`Reading data from page:`, Object.keys(data)) + + const { offsetHeight, offsetWidth } = readOffsetSize(data) + return { offsetHeight, offsetWidth, - key2: readString(data, getKey(0)), ignoreSelector: readString(data, 'ignoreSelector'), + key2: readString(data, getKey(0)), sizeSelector: readString(data, 'sizeSelector'), - targetOrigin: readString(data, 'targetOrigin'), - heightCalcMode: setupCustomCalcMethods( + heightCalcMode: setupCustomCalcMethod( data?.heightCalculationMethod, HEIGHT, ), - widthCalcMode: setupCustomCalcMethods(data?.widthCalculationMethod, WIDTH), + widthCalcMode: setupCustomCalcMethod(data?.widthCalculationMethod, WIDTH), onBeforeResize: readFunction(data, 'onBeforeResize'), onMessage: readFunction(data, 'onMessage'), @@ -68,17 +74,13 @@ function readData(data, setupCustomCalcMethods) { } } -export default function readDataFromPage(setupCustomCalcMethods) { +export default function readDataFromPage() { const { mode, targetOrigin } = settings if (mode === 1) return {} const data = window.iframeResizer || window.iFrameResizer - if (typeof data !== OBJECT) { - return {} - } - - const localSettings = readData(data, setupCustomCalcMethods) + const localSettings = isObject(data) ? readData(data) : {} info( `Set targetOrigin for parent: %c${localSettings.targetOrigin || targetOrigin}`, diff --git a/packages/child/size/all.js b/packages/child/size/all.js new file mode 100644 index 000000000..9432621e3 --- /dev/null +++ b/packages/child/size/all.js @@ -0,0 +1,13 @@ +import { IGNORE_TAGS } from '../../common/consts' + +const addNot = (tagName) => `:not(${tagName})` +const selector = `* ${Array.from(IGNORE_TAGS).map(addNot).join('')}` +export const getAllElements = (node) => node.querySelectorAll(selector) + +export const getAllMeasurements = (dimension) => [ + dimension.bodyOffset(), + dimension.bodyScroll(), + dimension.documentElementOffset(), + dimension.documentElementScroll(), + dimension.boundingClientRect(), +] diff --git a/packages/child/size/auto.js b/packages/child/size/auto.js new file mode 100644 index 000000000..69fcb74b9 --- /dev/null +++ b/packages/child/size/auto.js @@ -0,0 +1,144 @@ +import { FOREGROUND, HIGHLIGHT } from 'auto-console-group' + +import { HEIGHT, MIN_SIZE } from '../../common/consts' +import { info } from '../console' +import state from '../values/state' + +const prevScrollSize = { + height: 0, + width: 0, +} + +const prevBoundingSize = { + height: 0, + width: 0, +} + +function getBoundingClientRect(dimension, boundingSize, scrollSize) { + prevBoundingSize[dimension] = boundingSize + prevScrollSize[dimension] = scrollSize + return Math.max(boundingSize, MIN_SIZE) +} + +function getOffset(getDimension) { + const offset = getDimension.getOffset() + if (offset !== 0) info(`Page offsetSize: %c${offset}px`, HIGHLIGHT) + return offset +} + +const getAdjustedScroll = (getDimension) => + getDimension.documentElementScroll() + Math.max(0, getDimension.getOffset()) + +const BOUNDING_FORMAT = [HIGHLIGHT, FOREGROUND, HIGHLIGHT] + +export default function getAutoSize(getDimension) { + const { hasOverflow, hasTags, triggerLocked } = state + const dimension = getDimension.label + const isHeight = dimension === HEIGHT + const boundingSize = getDimension.boundingClientRect() + const ceilBoundingSize = Math.ceil(boundingSize) + const floorBoundingSize = Math.floor(boundingSize) + const scrollSize = getAdjustedScroll(getDimension) + const sizes = `HTML: %c${boundingSize}px %cPage: %c${scrollSize}px` + + let calculatedSize = MIN_SIZE + + switch (true) { + case !getDimension.enabled(): + return Math.max(scrollSize, MIN_SIZE) + + case hasTags: + info(`Found element with data-iframe-size attribute`) + calculatedSize = getDimension.taggedElement() + break + + case !hasOverflow && + state.firstRun && + prevBoundingSize[dimension] === 0 && + prevScrollSize[dimension] === 0: + info(`Initial page size values: ${sizes}`, ...BOUNDING_FORMAT) + calculatedSize = getBoundingClientRect( + dimension, + boundingSize, + scrollSize, + ) + break + + case triggerLocked && + boundingSize === prevBoundingSize[dimension] && + scrollSize === prevScrollSize[dimension]: + info(`Size unchanged: ${sizes}`, ...BOUNDING_FORMAT) + calculatedSize = Math.max(boundingSize, scrollSize) + break + + case boundingSize === 0 && scrollSize !== 0: + info(`Page is hidden: ${sizes}`, ...BOUNDING_FORMAT) + calculatedSize = scrollSize + break + + case !hasOverflow && + boundingSize !== prevBoundingSize[dimension] && + scrollSize <= prevScrollSize[dimension]: + info(`New size: ${sizes} `, ...BOUNDING_FORMAT) + info( + `Previous size: %c${prevBoundingSize[dimension]}px`, + HIGHLIGHT, + ) + calculatedSize = getBoundingClientRect( + dimension, + boundingSize, + scrollSize, + ) + break + + case !isHeight: + calculatedSize = getDimension.taggedElement() + break + + case !hasOverflow && boundingSize < prevBoundingSize[dimension]: + info(` size decreased: ${sizes}`, ...BOUNDING_FORMAT) + calculatedSize = getBoundingClientRect( + dimension, + boundingSize, + scrollSize, + ) + break + + case scrollSize === floorBoundingSize || scrollSize === ceilBoundingSize: + info(` size equals page size: ${sizes}`, ...BOUNDING_FORMAT) + calculatedSize = getBoundingClientRect( + dimension, + boundingSize, + scrollSize, + ) + break + + case boundingSize > scrollSize: + info(`Page size < size: ${sizes}`, ...BOUNDING_FORMAT) + calculatedSize = getBoundingClientRect( + dimension, + boundingSize, + scrollSize, + ) + break + + case hasOverflow: + info(`Found elements possibly overflowing `) + calculatedSize = getDimension.taggedElement() + break + + default: + info(`Using size: ${sizes}`, ...BOUNDING_FORMAT) + calculatedSize = getBoundingClientRect( + dimension, + boundingSize, + scrollSize, + ) + } + + info(`Content ${dimension}: %c${calculatedSize}px`, HIGHLIGHT) + + calculatedSize += getOffset(getDimension) + + return Math.max(calculatedSize, MIN_SIZE) +} diff --git a/packages/child/size/body-offset.js b/packages/child/size/body-offset.js new file mode 100644 index 000000000..c4babb7ef --- /dev/null +++ b/packages/child/size/body-offset.js @@ -0,0 +1,13 @@ +import { BASE } from '../../common/consts' + +// getBodyOffset +export default () => { + const { body } = document + const style = getComputedStyle(body) + + return ( + body.offsetHeight + + parseInt(style.marginTop, BASE) + + parseInt(style.marginBottom, BASE) + ) +} diff --git a/packages/child/size/custom.js b/packages/child/size/custom.js new file mode 100644 index 000000000..1fbd80f3e --- /dev/null +++ b/packages/child/size/custom.js @@ -0,0 +1,30 @@ +import { FUNCTION, HEIGHT } from '../../common/consts' +import { advise } from '../console' +import { getHeight, getWidth } from './index' + +const CUSTOM = 'custom' + +const deprecated = ( + calcFunc, +) => `Deprecated Option(${calcFunc}CalculationMethod) + +The use of ${calcFunc}CalculationMethod as a function is deprecated and will be removed in a future version of iframe-resizer. Please use the new onBeforeResize event handler instead. + +See https://iframe-resizer.com/api/child for more details. +` + +export default function setupCustomCalcMethod(calcMode, calcFunc) { + if (typeof calcMode === FUNCTION) { + advise(deprecated(calcFunc)) + + if (calcFunc === HEIGHT) { + getHeight.custom = calcMode + } else { + getWidth.custom = calcMode + } + + calcMode = CUSTOM + } + + return calcMode +} diff --git a/packages/child/size/get-height.js b/packages/child/size/get-height.js new file mode 100644 index 000000000..3d652e598 --- /dev/null +++ b/packages/child/size/get-height.js @@ -0,0 +1,35 @@ +import { HEIGHT, HEIGHT_EDGE } from '../../common/consts' +import { warn } from '../console' +import settings from '../values/settings' +import { getAllMeasurements } from './all' +import getAutoSize from './auto' +import getBodyOffset from './body-offset' +import getMaxElement from './max-element' + +const getHeight = { + label: HEIGHT, + enabled: () => settings.calculateHeight, + getOffset: () => settings.offsetHeight, + auto: () => getAutoSize(getHeight), + bodyOffset: getBodyOffset, + bodyScroll: () => document.body.scrollHeight, + offset: () => getHeight.bodyOffset(), // Backwards compatibility + documentElementOffset: () => document.documentElement.offsetHeight, + documentElementScroll: () => document.documentElement.scrollHeight, + boundingClientRect: () => + Math.max( + document.documentElement.getBoundingClientRect().bottom, + document.body.getBoundingClientRect().bottom, + ), + max: () => Math.max(...getAllMeasurements(getHeight)), + min: () => Math.min(...getAllMeasurements(getHeight)), + grow: () => getHeight.max(), + lowestElement: () => getMaxElement(HEIGHT_EDGE), + taggedElement: () => getMaxElement(HEIGHT_EDGE), + custom: () => { + warn('Custom height calculation function not defined') + return getHeight.auto() + }, +} + +export default getHeight diff --git a/packages/child/size/get-width.js b/packages/child/size/get-width.js new file mode 100644 index 000000000..72f715e35 --- /dev/null +++ b/packages/child/size/get-width.js @@ -0,0 +1,34 @@ +import { WIDTH, WIDTH_EDGE } from '../../common/consts' +import { warn } from '../console' +import settings from '../values/settings' +import { getAllMeasurements } from './all' +import getAutoSize from './auto' +import getMaxElement from './max-element' + +const getWidth = { + label: WIDTH, + enabled: () => settings.calculateWidth, + getOffset: () => settings.offsetWidth, + auto: () => getAutoSize(getWidth), + bodyScroll: () => document.body.scrollWidth, + bodyOffset: () => document.body.offsetWidth, + documentElementScroll: () => document.documentElement.scrollWidth, + documentElementOffset: () => document.documentElement.offsetWidth, + boundingClientRect: () => + Math.max( + document.documentElement.getBoundingClientRect().right, + document.body.getBoundingClientRect().right, + ), + max: () => Math.max(...getAllMeasurements(getWidth)), + min: () => Math.min(...getAllMeasurements(getWidth)), + rightMostElement: () => getMaxElement(WIDTH_EDGE), + scroll: () => + Math.max(getWidth.bodyScroll(), getWidth.documentElementScroll()), + taggedElement: () => getMaxElement(WIDTH_EDGE), + custom: () => { + warn('Custom width calculation function not defined') + return getWidth.auto() + }, +} + +export default getWidth diff --git a/packages/child/size/index.js b/packages/child/size/index.js new file mode 100644 index 000000000..f5ee4ca42 --- /dev/null +++ b/packages/child/size/index.js @@ -0,0 +1,2 @@ +export { default as getHeight } from './get-height' +export { default as getWidth } from './get-width' diff --git a/packages/child/size/max-element.js b/packages/child/size/max-element.js new file mode 100644 index 000000000..d919a1961 --- /dev/null +++ b/packages/child/size/max-element.js @@ -0,0 +1,64 @@ +import { FOREGROUND, HIGHLIGHT } from 'auto-console-group' + +import { MIN_SIZE } from '../../common/consts' +import { capitalizeFirstLetter } from '../../common/utils' +import { info } from '../console' +import { PREF_END, PREF_START } from '../observers/perf' +import settings from '../values/settings' +import state from '../values/state' +import { getAllElements } from './all' + +function getTaggedElements() { + const { hasOverflow, hasTags, overflowedNodeSet, taggedElements } = state + + return hasTags + ? taggedElements + : hasOverflow + ? Array.from(overflowedNodeSet) + : getAllElements(document.documentElement) // Width resizing may need to check all elements +} + +function findMaxElement(targetElements, side) { + let elVal = MIN_SIZE + let maxEl = document.documentElement + let maxVal = state.hasTags + ? 0 + : document.documentElement.getBoundingClientRect().bottom + + for (const element of targetElements) { + elVal = + element.getBoundingClientRect()[side] + + parseFloat(getComputedStyle(element).getPropertyValue(`margin-${side}`)) + + if (elVal > maxVal) { + maxVal = elVal + maxEl = element + } + } + return { maxEl, maxVal } +} + +export default function getMaxElement(side) { + performance.mark(PREF_START) + + const Side = capitalizeFirstLetter(side) + const { logging } = settings + const { hasTags } = state + + const targetElements = getTaggedElements() + const { maxEl, maxVal } = findMaxElement(targetElements, side) + + info(`${Side} position calculated from:`, maxEl) + info(`Checked %c${targetElements.length}%c elements`, HIGHLIGHT, FOREGROUND) + + performance.mark(PREF_END, { + detail: { + hasTags, + len: targetElements.length, + logging, + Side, + }, + }) + + return maxVal +} diff --git a/packages/child/values/state.js b/packages/child/values/state.js index 14b79c4d4..cc756fa40 100644 --- a/packages/child/values/state.js +++ b/packages/child/values/state.js @@ -1,5 +1,11 @@ export default { firstRun: true, + hasTags: false, + hasOverflow: false, + hasOverflowUpdated: true, + overflowedNodeSet: new Set(), sameOrigin: false, + taggedElements: [], target: window?.parent, + triggerLocked: false, } From ed56d0eb79aff63a723bd03608c9dc605e7f5348 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Sat, 1 Nov 2025 14:31:08 +0000 Subject: [PATCH 17/38] Tidy checks --- packages/child/check/quirks-mode.js | 13 +++++++------ packages/child/check/version.js | 23 ++++++++++------------- packages/child/index.js | 1 - 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/packages/child/check/quirks-mode.js b/packages/child/check/quirks-mode.js index 925fd1e97..2038e8ecd 100644 --- a/packages/child/check/quirks-mode.js +++ b/packages/child/check/quirks-mode.js @@ -1,14 +1,15 @@ import { advise } from '../console' -export default function checkQuirksMode() { - if (document.compatMode !== 'BackCompat') return +const QUIRKS_MODE = 'BackCompat' - advise( - `Quirks Mode Detected +const DEPRECATED = `Quirks Mode Detected This iframe is running in the browser's legacy Quirks Mode, this may cause issues with the correct operation of iframe-resizer. It is recommended that you switch to the modern Standards Mode. For more information see https://iframe-resizer.com/quirks-mode. -`, - ) +` + +export default function checkQuirksMode() { + if (document.compatMode !== QUIRKS_MODE) return + advise(DEPRECATED) } diff --git a/packages/child/check/version.js b/packages/child/check/version.js index 7d39dd128..14f5f1d40 100644 --- a/packages/child/check/version.js +++ b/packages/child/check/version.js @@ -1,27 +1,24 @@ import { FALSE, VERSION } from '../../common/consts' import { advise } from '../console' -export default function checkVersion({ version }) { - if (!version || version === '' || version === FALSE) { - advise( - `Legacy version detected on parent page +const LEGACY = `Legacy version detected on parent page Detected legacy version of parent page script. It is recommended to update the parent page to use @iframe-resizer/parent. See https://iframe-resizer.com/setup/ for more details. -`, - ) - return - } +` - if (version !== VERSION) { - advise( - `Version mismatch +const mismatch = (version) => `Version mismatch The parent and child pages are running different versions of iframe resizer. Parent page: ${version} - Child page: ${VERSION}. -`, - ) +` + +export default function checkVersion({ version }) { + if (!version || version === '' || version === FALSE) { + advise(LEGACY) + } else if (version !== VERSION) { + advise(mismatch(version)) } } diff --git a/packages/child/index.js b/packages/child/index.js index ddd92496a..9b648910d 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -120,7 +120,6 @@ function iframeResizerChild() { let origin let overflowObserver let resizeObserver - let width = 1 let win = window From 915fa86f3af095ca68ba430ae86897eab8e03c0d Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Sat, 1 Nov 2025 17:57:17 +0000 Subject: [PATCH 18/38] Tidy --- packages/child/read/from-page.js | 1 - packages/child/size/max-element.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/child/read/from-page.js b/packages/child/read/from-page.js index b2eed191f..74983eeb4 100644 --- a/packages/child/read/from-page.js +++ b/packages/child/read/from-page.js @@ -79,7 +79,6 @@ export default function readDataFromPage() { if (mode === 1) return {} const data = window.iframeResizer || window.iFrameResizer - const localSettings = isObject(data) ? readData(data) : {} info( diff --git a/packages/child/size/max-element.js b/packages/child/size/max-element.js index d919a1961..2c5e3831b 100644 --- a/packages/child/size/max-element.js +++ b/packages/child/size/max-element.js @@ -8,7 +8,7 @@ import settings from '../values/settings' import state from '../values/state' import { getAllElements } from './all' -function getTaggedElements() { +function getSelectedElements() { const { hasOverflow, hasTags, overflowedNodeSet, taggedElements } = state return hasTags @@ -45,7 +45,7 @@ export default function getMaxElement(side) { const { logging } = settings const { hasTags } = state - const targetElements = getTaggedElements() + const targetElements = getSelectedElements() const { maxEl, maxVal } = findMaxElement(targetElements, side) info(`${Side} position calculated from:`, maxEl) From 32e9c701b9f50715ca14461a984b26db7941231b Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Sat, 1 Nov 2025 18:27:03 +0000 Subject: [PATCH 19/38] Tidy --- packages/child/size/custom.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/child/size/custom.js b/packages/child/size/custom.js index 1fbd80f3e..0d368bc6d 100644 --- a/packages/child/size/custom.js +++ b/packages/child/size/custom.js @@ -14,17 +14,15 @@ See https://iframe-resizer.com/api/child for more details. ` export default function setupCustomCalcMethod(calcMode, calcFunc) { - if (typeof calcMode === FUNCTION) { - advise(deprecated(calcFunc)) + if (typeof calcMode !== FUNCTION) return calcMode - if (calcFunc === HEIGHT) { - getHeight.custom = calcMode - } else { - getWidth.custom = calcMode - } + advise(deprecated(calcFunc)) - calcMode = CUSTOM + if (calcFunc === HEIGHT) { + getHeight.custom = calcMode + } else { + getWidth.custom = calcMode } - return calcMode + return CUSTOM } From a75819ba4b6cd0137efa49a7200b2a1995c0f195 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Sat, 1 Nov 2025 18:27:07 +0000 Subject: [PATCH 20/38] Extract sendSize() & getContentSize() --- packages/child/check/tolerance.js | 3 + packages/child/index.js | 167 +------------------------ packages/child/send/dispatch.js | 72 +++++++++++ packages/child/send/message.js | 67 +--------- packages/child/send/size.js | 87 +++++++++++++ packages/child/size/change-detected.js | 13 ++ packages/child/size/content.js | 63 ++++++++++ packages/child/size/get-new.js | 38 ++++++ packages/child/values/state.js | 1 + 9 files changed, 282 insertions(+), 229 deletions(-) create mode 100644 packages/child/check/tolerance.js create mode 100644 packages/child/send/dispatch.js create mode 100644 packages/child/send/size.js create mode 100644 packages/child/size/change-detected.js create mode 100644 packages/child/size/content.js create mode 100644 packages/child/size/get-new.js diff --git a/packages/child/check/tolerance.js b/packages/child/check/tolerance.js new file mode 100644 index 000000000..9ec8c4c9b --- /dev/null +++ b/packages/child/check/tolerance.js @@ -0,0 +1,3 @@ +import settings from '../values/settings' + +export default (a, b) => !(Math.abs(a - b) <= settings.tolerance) diff --git a/packages/child/index.js b/packages/child/index.js index 9b648910d..e8ba523de 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -11,16 +11,13 @@ import { HEIGHT_CALC_MODE_DEFAULT, HEIGHT_EDGE, IGNORE_ATTR, - IGNORE_DISABLE_RESIZE, IN_PAGE_LINK, INIT, MANUAL_RESIZE_REQUEST, MESSAGE, MESSAGE_ID, MESSAGE_ID_LENGTH, - MIN_SIZE, MUTATION_OBSERVER, - NO_CHANGE, NONE, NUMBER, OVERFLOW_ATTR, @@ -39,7 +36,6 @@ import { SEPARATOR, SET_OFFSET_SIZE, SIZE_ATTR, - SIZE_CHANGE_DETECTED, STRING, UNDEFINED, VERSION, @@ -67,7 +63,6 @@ import checkReadyYet from './check/ready' import checkVersion from './check/version' import { advise, - assert, debug, deprecateMethod, deprecateMethodReplace, @@ -78,7 +73,6 @@ import { info, label, log, - purge, setConsoleOptions, warn, } from './console' @@ -100,7 +94,8 @@ import { setBodyStyle, setMargin } from './page/css' import stopInfiniteResizingOfIframe from './page/stop-infinite-resizing' import readDataFromPage from './read/from-page' import readDataFromParent from './read/from-parent' -import sendMessage, { dispatch } from './send/message' +import sendMessage from './send/message' +import sendSize from './send/size' import sendTitle from './send/title' import { getHeight, getWidth } from './size' import { getAllElements } from './size/all' @@ -116,7 +111,6 @@ function iframeResizerChild() { let height = 1 let initLock = true let inPageLinks = {} - let isHidden = false let origin let overflowObserver let resizeObserver @@ -691,7 +685,7 @@ function iframeResizerChild() { function visibilityChange(isVisible) { log(`Visible: %c${isVisible}`, HIGHLIGHT) - isHidden = !isVisible + state.isHidden = !isVisible sendSize(VISIBILITY_OBSERVER, 'Visibility changed') } @@ -767,161 +761,6 @@ function iframeResizerChild() { pushDisconnectsOnToTearDown(observers) } - const checkTolerance = (a, b) => !(Math.abs(a - b) <= settings.tolerance) - - function callOnBeforeResize(newSize) { - const returnedSize = settings.onBeforeResize(newSize) - - if (returnedSize === undefined) { - throw new TypeError( - 'No value returned from onBeforeResize(), expected a numeric value', - ) - } - - if (Number.isNaN(returnedSize)) - throw new TypeError( - `Invalid value returned from onBeforeResize(): ${returnedSize}, expected Number`, - ) - - if (returnedSize < MIN_SIZE) { - throw new RangeError( - `Out of range value returned from onBeforeResize(): ${returnedSize}, must be at least ${MIN_SIZE}`, - ) - } - - return returnedSize - } - - function getNewSize(direction, mode) { - const calculatedSize = direction[mode]() - const newSize = - direction.enabled() && settings.onBeforeResize !== undefined - ? callOnBeforeResize(calculatedSize) - : calculatedSize - - assert( - newSize >= MIN_SIZE, - `New iframe ${direction.label} is too small: ${newSize}, must be at least ${MIN_SIZE}`, - ) - - return newSize - } - - function getContentSize( - triggerEvent, - triggerEventDesc, - customHeight, - customWidth, - ) { - const { calculateHeight, calculateWidth, heightCalcMode, widthCalcMode } = - settings - - const isSizeChangeDetected = () => - (calculateHeight && checkTolerance(height, newHeight)) || - (calculateWidth && checkTolerance(width, newWidth)) - - const newHeight = customHeight ?? getNewSize(getHeight, heightCalcMode) - const newWidth = customWidth ?? getNewSize(getWidth, widthCalcMode) - - const updateEvent = isSizeChangeDetected() - ? SIZE_CHANGE_DETECTED - : triggerEvent - - log(`Resize event: %c${triggerEventDesc}`, HIGHLIGHT) - - switch (updateEvent) { - case INIT: - case ENABLE: - case SIZE_CHANGE_DETECTED: - // lockTrigger() - height = newHeight - width = newWidth - // eslint-disable-next-line no-fallthrough - case SET_OFFSET_SIZE: - return { height, width } - - // the following case needs {} to prevent a compile error - case OVERFLOW_OBSERVER: - case MUTATION_OBSERVER: - case RESIZE_OBSERVER: - case VISIBILITY_OBSERVER: { - log(NO_CHANGE) - purge() - break - } - - default: - purge() - info(NO_CHANGE) - } - - return null - } - - let sendPending = false - let hiddenMessageShown = false - let rafId - - const sendSize = errorBoundary( - (triggerEvent, triggerEventDesc, customHeight, customWidth, msg) => { - consoleEvent(triggerEvent) - const { autoResize } = settings - - switch (true) { - case isHidden === true: { - if (hiddenMessageShown === true) break - log('Iframe hidden - Ignored resize request') - hiddenMessageShown = true - sendPending = false - cancelAnimationFrame(rafId) - break - } - - // Ignore overflowObserver here, as more efficient than using - // mutationObserver to detect OVERFLOW_ATTR changes - case sendPending === true && triggerEvent !== OVERFLOW_OBSERVER: { - purge() - log('Resize already pending - Ignored resize request') - break // only update once per frame - } - - case !autoResize && !(triggerEvent in IGNORE_DISABLE_RESIZE): { - info('Resizing disabled') - break - } - - default: { - hiddenMessageShown = false - sendPending = true - state.totalTime = performance.now() - state.timerActive = true - - const newSize = getContentSize( - triggerEvent, - triggerEventDesc, - customHeight, - customWidth, - ) - - if (newSize) - dispatch(newSize.height, newSize.width, triggerEvent, msg) - - if (!rafId) - rafId = requestAnimationFrame(() => { - sendPending = false - rafId = null - consoleEvent('requestAnimationFrame') - debug(`Reset sendPending: %c${triggerEvent}`, HIGHLIGHT) - }) - - state.timerActive = false // Reset time for next resize - } - } - - endAutoGroup() - }, - ) - function lockTrigger() { if (state.triggerLocked) { log('TriggerLock blocked calculation') diff --git a/packages/child/send/dispatch.js b/packages/child/send/dispatch.js new file mode 100644 index 000000000..e227812d5 --- /dev/null +++ b/packages/child/send/dispatch.js @@ -0,0 +1,72 @@ +import { HIGHLIGHT, ITALIC } from 'auto-console-group' + +import { INIT, MESSAGE_ID } from '../../common/consts' +import { getModeData } from '../../common/mode' +import { once, round } from '../../common/utils' +import { advise, info, log } from '../console' +import settings from '../values/settings' +import state from '../values/state' + +const sendFailed = once(() => advise(getModeData(4))) + +export function displayTimeTaken(triggerEvent) { + if (!state.timerActive) return + + const timer = round(performance.now() - state.totalTime) + const timeTaken = + triggerEvent === INIT + ? `Initialised iframe in %c${timer}ms` + : `Size calculated in %c${timer}ms` + + log(timeTaken, HIGHLIGHT) +} + +export function setTargetOrigin(targetOrigin) { + if (undefined === targetOrigin) targetOrigin = settings.targetOrigin + else log(`Message targetOrigin: %c${targetOrigin}`, HIGHLIGHT) + return targetOrigin +} + +export function dispatchToParent(message, targetOrigin) { + const { mode } = settings + const { sameOrigin, target } = state + + if (sameOrigin) + try { + window.parent.iframeParentListener(MESSAGE_ID + message) + } catch (error) { + if (mode === 1) sendFailed() + else throw error + return false + } + else target.postMessage(MESSAGE_ID + message, setTargetOrigin(targetOrigin)) + + return true +} + +export default function dispatch( + height, + width, + triggerEvent, + msg, + targetOrigin, +) { + const { parentId } = settings + const { sameOrigin } = state + const size = `${height}:${width}` + const message = `${parentId}:${size}:${triggerEvent}${undefined === msg ? '' : `:${msg}`}` + + if (settings.mode < -1) return + + const success = dispatchToParent(message, targetOrigin) + + if (!success) return + + displayTimeTaken(triggerEvent) + + info( + `Sending message to parent page via ${sameOrigin ? 'sameOrigin' : 'postMessage'}: %c%c${message}`, + ITALIC, + HIGHLIGHT, + ) +} diff --git a/packages/child/send/message.js b/packages/child/send/message.js index 055f201c7..5eadfea05 100644 --- a/packages/child/send/message.js +++ b/packages/child/send/message.js @@ -1,68 +1,5 @@ -import { HIGHLIGHT, ITALIC } from 'auto-console-group' - -import { INIT, MESSAGE_ID } from '../../common/consts' -import { getModeData } from '../../common/mode' -import { once, round } from '../../common/utils' -import { - advise, - endAutoGroup, - errorBoundary, - event as consoleEvent, - info, - log, -} from '../console' -import settings from '../values/settings' -import state from '../values/state' - -const sendFailed = once(() => advise(getModeData(4))) - -export function dispatch(height, width, triggerEvent, msg, targetOrigin) { - if (settings.mode < -1) return - - function setTargetOrigin() { - if (undefined === targetOrigin) { - targetOrigin = settings.targetOrigin - return - } - - log(`Message targetOrigin: %c${targetOrigin}`, HIGHLIGHT) - } - - function displayTimeTaken() { - const timer = round(performance.now() - state.totalTime) - return triggerEvent === INIT - ? `Initialised iframe in %c${timer}ms` - : `Size calculated in %c${timer}ms` - } - - function dispatchToParent() { - const { mode, parentId } = settings - const { sameOrigin, timerActive } = state - const size = `${height}:${width}` - const message = `${parentId}:${size}:${triggerEvent}${undefined === msg ? '' : `:${msg}`}` - - if (sameOrigin) - try { - window.parent.iframeParentListener(MESSAGE_ID + message) - } catch (error) { - if (mode === 1) sendFailed() - else throw error - return - } - else state.target.postMessage(MESSAGE_ID + message, targetOrigin) - - if (timerActive) log(displayTimeTaken(), HIGHLIGHT) - - info( - `Sending message to parent page via ${sameOrigin ? 'sameOrigin' : 'postMessage'}: %c%c${message}`, - ITALIC, - HIGHLIGHT, - ) - } - - setTargetOrigin() - dispatchToParent() -} +import { endAutoGroup, errorBoundary, event as consoleEvent } from '../console' +import dispatch from './dispatch' export default errorBoundary( (height, width, triggerEvent, message, targetOrigin) => { diff --git a/packages/child/send/size.js b/packages/child/send/size.js new file mode 100644 index 000000000..c863f1c5f --- /dev/null +++ b/packages/child/send/size.js @@ -0,0 +1,87 @@ +import { HIGHLIGHT } from 'auto-console-group' + +import { IGNORE_DISABLE_RESIZE, OVERFLOW_OBSERVER } from '../../common/consts' +import { + debug, + endAutoGroup, + errorBoundary, + event as consoleEvent, + info, + log, + purge, +} from '../console' +import getContentSize from '../size/content' +import settings from '../values/settings' +import state from '../values/state' +import dispatch from './dispatch' + +let sendPending = false +let hiddenMessageShown = false +let rafId + +function sendSize( + triggerEvent, + triggerEventDesc, + customHeight, + customWidth, + msg, +) { + const { autoResize } = settings + const { isHidden } = state + + consoleEvent(triggerEvent) + + switch (true) { + case isHidden === true: { + if (hiddenMessageShown === true) break + log('Iframe hidden - Ignored resize request') + hiddenMessageShown = true + sendPending = false + cancelAnimationFrame(rafId) + break + } + + // Ignore overflowObserver here, as more efficient than using + // mutationObserver to detect OVERFLOW_ATTR changes + case sendPending === true && triggerEvent !== OVERFLOW_OBSERVER: { + purge() + log('Resize already pending - Ignored resize request') + break // only update once per frame + } + + case !autoResize && !(triggerEvent in IGNORE_DISABLE_RESIZE): { + info('Resizing disabled') + break + } + + default: { + hiddenMessageShown = false + sendPending = true + state.totalTime = performance.now() + state.timerActive = true + + const newSize = getContentSize( + triggerEvent, + triggerEventDesc, + customHeight, + customWidth, + ) + + if (newSize) dispatch(newSize.height, newSize.width, triggerEvent, msg) + + if (!rafId) + rafId = requestAnimationFrame(() => { + sendPending = false + rafId = null + consoleEvent('requestAnimationFrame') + debug(`Reset sendPending: %c${triggerEvent}`, HIGHLIGHT) + }) + + state.timerActive = false // Reset time for next resize + } + } + + endAutoGroup() +} + +export default errorBoundary(sendSize) diff --git a/packages/child/size/change-detected.js b/packages/child/size/change-detected.js new file mode 100644 index 000000000..5cb25558f --- /dev/null +++ b/packages/child/size/change-detected.js @@ -0,0 +1,13 @@ +import checkTolerance from '../check/tolerance' +import settings from '../values/settings' +import state from '../values/state' + +export default function isSizeChangeDetected(newHeight, newWidth) { + const { calculateHeight, calculateWidth } = settings + const { height, width } = state + + return ( + (calculateHeight && checkTolerance(height, newHeight)) || + (calculateWidth && checkTolerance(width, newWidth)) + ) +} diff --git a/packages/child/size/content.js b/packages/child/size/content.js new file mode 100644 index 000000000..12e7c1b70 --- /dev/null +++ b/packages/child/size/content.js @@ -0,0 +1,63 @@ +import { HIGHLIGHT } from 'auto-console-group' + +import { + ENABLE, + INIT, + MUTATION_OBSERVER, + NO_CHANGE, + OVERFLOW_OBSERVER, + RESIZE_OBSERVER, + SET_OFFSET_SIZE, + SIZE_CHANGE_DETECTED, + VISIBILITY_OBSERVER, +} from '../../common/consts' +import { info, log, purge } from '../console' +import settings from '../values/settings' +import state from '../values/state' +import isSizeChangeDetected from './change-detected' +import { getNewHeight, getNewWidth } from './get-new' + +export default function getContentSize( + triggerEvent, + triggerEventDesc, + customHeight, + customWidth, +) { + const { heightCalcMode, widthCalcMode } = settings + + const newHeight = customHeight ?? getNewHeight(heightCalcMode) + const newWidth = customWidth ?? getNewWidth(widthCalcMode) + + const updateEvent = isSizeChangeDetected(newHeight, newWidth) + ? SIZE_CHANGE_DETECTED + : triggerEvent + + log(`Resize event: %c${triggerEventDesc}`, HIGHLIGHT) + + switch (updateEvent) { + case INIT: + case ENABLE: + case SIZE_CHANGE_DETECTED: + state.height = newHeight + state.width = newWidth + // eslint-disable-next-line no-fallthrough + case SET_OFFSET_SIZE: + return state + + // the following case needs {} to prevent a compile error on Next.js + case OVERFLOW_OBSERVER: + case MUTATION_OBSERVER: + case RESIZE_OBSERVER: + case VISIBILITY_OBSERVER: { + log(NO_CHANGE) + purge() + break + } + + default: + purge() + info(NO_CHANGE) + } + + return null +} diff --git a/packages/child/size/get-new.js b/packages/child/size/get-new.js new file mode 100644 index 000000000..2a1b64909 --- /dev/null +++ b/packages/child/size/get-new.js @@ -0,0 +1,38 @@ +import { MIN_SIZE } from '../../common/consts' +import settings from '../values/settings' +import getHeight from './get-height' +import getWidth from './get-width' + +function callOnBeforeResize(newSize) { + const returnedSize = settings.onBeforeResize(newSize) + + if (returnedSize === undefined) { + throw new TypeError( + 'No value returned from onBeforeResize(), expected a numeric value', + ) + } + + if (Number.isNaN(returnedSize)) + throw new TypeError( + `Invalid value returned from onBeforeResize(): ${returnedSize}, expected Number`, + ) + + if (returnedSize < MIN_SIZE) { + throw new RangeError( + `Out of range value returned from onBeforeResize(): ${returnedSize}, must be at least ${MIN_SIZE}`, + ) + } + + return returnedSize +} + +const createGetNewSize = (direction) => (mode) => { + const calculatedSize = direction[mode]() + + return direction.enabled() && settings.onBeforeResize !== undefined + ? callOnBeforeResize(calculatedSize) + : calculatedSize +} + +export const getNewHeight = createGetNewSize(getHeight) +export const getNewWidth = createGetNewSize(getWidth) diff --git a/packages/child/values/state.js b/packages/child/values/state.js index cc756fa40..c3388e30d 100644 --- a/packages/child/values/state.js +++ b/packages/child/values/state.js @@ -3,6 +3,7 @@ export default { hasTags: false, hasOverflow: false, hasOverflowUpdated: true, + isHidden: false, overflowedNodeSet: new Set(), sameOrigin: false, taggedElements: [], From d1558405155b1e3bb4e4a2957279c387e3aef001 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Sat, 1 Nov 2025 18:40:40 +0000 Subject: [PATCH 21/38] Extract state values --- packages/child/index.js | 20 ++++++++------------ packages/child/values/state.js | 1 + 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/packages/child/index.js b/packages/child/index.js index e8ba523de..9af94c8a0 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -104,17 +104,11 @@ import state from './values/state' function iframeResizerChild() { const EVENT_CANCEL_TIMER = 128 - const eventHandlersByName = {} let applySelectors = id - let hasIgnored = false - let height = 1 - let initLock = true let inPageLinks = {} - let origin let overflowObserver let resizeObserver - let width = 1 let win = window let onPageInfo = null @@ -241,7 +235,7 @@ function iframeResizerChild() { let ignoredElementsCount = 0 function checkIgnoredElements() { const ignoredElements = document.querySelectorAll(`*[${IGNORE_ATTR}]`) - hasIgnored = ignoredElements.length > 0 + const hasIgnored = ignoredElements.length > 0 if (hasIgnored && ignoredElements.length !== ignoredElementsCount) { warnIgnored(ignoredElements) ignoredElementsCount = ignoredElements.length @@ -249,6 +243,7 @@ function iframeResizerChild() { return hasIgnored } + const eventHandlersByName = {} function manageTriggerEvent(options) { const listener = { add(eventName) { @@ -443,10 +438,10 @@ function iframeResizerChild() { getOrigin: () => { consoleEvent('getOrigin') deprecateMethod('getOrigin()', 'getParentOrigin()') - return origin + return state.origin }, - getParentOrigin: () => origin, + getParentOrigin: () => state.origin, getPageInfo(callback) { if (typeof callback === FUNCTION) { @@ -779,8 +774,8 @@ function iframeResizerChild() { const { heightCalcMode, widthCalcMode } = settings log(`Reset trigger event: %c${triggerEvent}`, HIGHLIGHT) - height = getHeight[heightCalcMode]() - width = getWidth[widthCalcMode]() + const height = getHeight[heightCalcMode]() + const width = getWidth[widthCalcMode]() sendMessage(height, width, triggerEvent) } @@ -796,6 +791,7 @@ function iframeResizerChild() { settings.heightCalcMode = hcm } + let initLock = true function receiver(event) { consoleEvent('onMessage') const { freeze } = Object @@ -814,7 +810,7 @@ function iframeResizerChild() { const data = event.data.slice(MESSAGE_ID_LENGTH).split(SEPARATOR) state.target = event.source - origin = event.origin + state.origin = event.origin init(data) diff --git a/packages/child/values/state.js b/packages/child/values/state.js index c3388e30d..b698aaeb7 100644 --- a/packages/child/values/state.js +++ b/packages/child/values/state.js @@ -4,6 +4,7 @@ export default { hasOverflow: false, hasOverflowUpdated: true, isHidden: false, + origin: undefined, overflowedNodeSet: new Set(), sameOrigin: false, taggedElements: [], From 6d9ec4972727781308687b43d7fecbc1620f4e5b Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Sat, 1 Nov 2025 19:15:11 +0000 Subject: [PATCH 22/38] Extract ignored and overflow checks --- packages/child/check/ignored-elements.js | 30 +++++++++ packages/child/check/overflow.js | 46 ++++++++++++++ packages/child/index.js | 78 ++---------------------- packages/child/values/state.js | 1 - 4 files changed, 81 insertions(+), 74 deletions(-) create mode 100644 packages/child/check/ignored-elements.js create mode 100644 packages/child/check/overflow.js diff --git a/packages/child/check/ignored-elements.js b/packages/child/check/ignored-elements.js new file mode 100644 index 000000000..ea1b04ad8 --- /dev/null +++ b/packages/child/check/ignored-elements.js @@ -0,0 +1,30 @@ +import { BOLD, NORMAL } from 'auto-console-group' + +import { IGNORE_ATTR } from '../../common/consts' +import { warn } from '../console' + +let ignoredElementsCount = 0 + +function warnIgnored(ignoredElements) { + const s = ignoredElements.length === 1 ? '' : 's' + + warn( + `%c[${IGNORE_ATTR}]%c found on %c${ignoredElements.length}%c element${s}`, + BOLD, + NORMAL, + BOLD, + NORMAL, + ) +} + +export default function checkIgnoredElements() { + const ignoredElements = document.querySelectorAll(`*[${IGNORE_ATTR}]`) + const hasIgnored = ignoredElements.length > 0 + + if (hasIgnored && ignoredElements.length !== ignoredElementsCount) { + warnIgnored(ignoredElements) + ignoredElementsCount = ignoredElements.length + } + + return hasIgnored +} diff --git a/packages/child/check/overflow.js b/packages/child/check/overflow.js new file mode 100644 index 000000000..1db40a02e --- /dev/null +++ b/packages/child/check/overflow.js @@ -0,0 +1,46 @@ +import { FUNCTION, IGNORE_ATTR, OVERFLOW_ATTR } from '../../common/consts' +import { endAutoGroup, event as consoleEvent, info } from '../console' +import state from '../values/state' + +let prevOverflowedNodeSet = new Set() + +export function filterIgnoredElements(nodeList) { + const filteredNodeSet = new Set() + const ignoredNodeSet = new Set() + + for (const node of nodeList) { + if (node.closest(`[${IGNORE_ATTR}]`)) { + ignoredNodeSet.add(node) + } else { + filteredNodeSet.add(node) + } + } + + if (ignoredNodeSet.size > 0) { + queueMicrotask(() => { + consoleEvent('overflowIgnored') + info(`Ignoring elements with [data-iframe-ignore] > *:\n`, ignoredNodeSet) + endAutoGroup() + }) + } + + return filteredNodeSet +} + +export default function checkOverflow() { + const allOverflowedNodes = document.querySelectorAll(`[${OVERFLOW_ATTR}]`) + const overflowedNodeSet = filterIgnoredElements(allOverflowedNodes) + let hasOverflowUpdated = false + + // Not supported in Safari 16 (or esLint!!!) + // eslint-disable-next-line no-use-extend-native/no-use-extend-native + if (typeof Set.prototype.symmetricDifference === FUNCTION) + hasOverflowUpdated = + overflowedNodeSet.symmetricDifference(prevOverflowedNodeSet).size > 0 + + prevOverflowedNodeSet = overflowedNodeSet + state.overflowedNodeSet = overflowedNodeSet + state.hasOverflow = overflowedNodeSet.size > 0 + + return { hasOverflowUpdated, overflowedNodeSet } +} diff --git a/packages/child/index.js b/packages/child/index.js index 9af94c8a0..a2b5c5078 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -1,4 +1,4 @@ -import { BOLD, FOREGROUND, HIGHLIGHT, NORMAL } from 'auto-console-group' +import { FOREGROUND, HIGHLIGHT } from 'auto-console-group' import { AUTO_RESIZE, @@ -10,7 +10,6 @@ import { FUNCTION, HEIGHT_CALC_MODE_DEFAULT, HEIGHT_EDGE, - IGNORE_ATTR, IN_PAGE_LINK, INIT, MANUAL_RESIZE_REQUEST, @@ -20,7 +19,6 @@ import { MUTATION_OBSERVER, NONE, NUMBER, - OVERFLOW_ATTR, OVERFLOW_OBSERVER, PAGE_HIDE, PAGE_INFO, @@ -57,7 +55,9 @@ import checkBlockingCSS from './check/blocking-css' import checkCalcMode from './check/calculation-mode' import checkCrossDomain from './check/cross-domain' import checkDeprecatedAttrs from './check/deprecated-attributes' +import checkIgnoredElements from './check/ignored-elements' import checkMode from './check/mode' +import checkOverflow from './check/overflow' import checkQuirksMode from './check/quirks-mode' import checkReadyYet from './check/ready' import checkVersion from './check/version' @@ -220,29 +220,6 @@ function iframeResizerChild() { log(`Tagged elements found: %c${state.hasTags}`, HIGHLIGHT) } - function warnIgnored(ignoredElements) { - const s = ignoredElements.length === 1 ? '' : 's' - - warn( - `%c[${IGNORE_ATTR}]%c found on %c${ignoredElements.length}%c element${s}`, - BOLD, - NORMAL, - BOLD, - NORMAL, - ) - } - - let ignoredElementsCount = 0 - function checkIgnoredElements() { - const ignoredElements = document.querySelectorAll(`*[${IGNORE_ATTR}]`) - const hasIgnored = ignoredElements.length > 0 - if (hasIgnored && ignoredElements.length !== ignoredElementsCount) { - warnIgnored(ignoredElements) - ignoredElementsCount = ignoredElements.length - } - return hasIgnored - } - const eventHandlersByName = {} function manageTriggerEvent(options) { const listener = { @@ -583,54 +560,9 @@ function iframeResizerChild() { win.parentIFrame = win.parentIframe } - function filterIgnoredElements(nodeList) { - const filteredNodeSet = new Set() - const ignoredNodeSet = new Set() - - for (const node of nodeList) { - if (node.closest(`[${IGNORE_ATTR}]`)) { - ignoredNodeSet.add(node) - } else { - filteredNodeSet.add(node) - } - } - - if (ignoredNodeSet.size > 0) { - queueMicrotask(() => { - consoleEvent('overflowIgnored') - info( - `Ignoring elements with [data-iframe-ignore] > *:\n`, - ignoredNodeSet, - ) - endAutoGroup() - }) - } - - return filteredNodeSet - } - - let prevOverflowedNodeSet = new Set() - function checkOverflow() { - const allOverflowedNodes = document.querySelectorAll(`[${OVERFLOW_ATTR}]`) - - state.overflowedNodeSet = filterIgnoredElements(allOverflowedNodes) - - state.hasOverflow = state.overflowedNodeSet.size > 0 - - // Not supported in Safari 16 (or esLint!!!) - // eslint-disable-next-line no-use-extend-native/no-use-extend-native - if (typeof Set.prototype.symmetricDifference === FUNCTION) - state.hasOverflowUpdated = - state.overflowedNodeSet.symmetricDifference(prevOverflowedNodeSet) - .size > 0 - - prevOverflowedNodeSet = state.overflowedNodeSet - } - function overflowObserved() { - checkOverflow() - - const { hasOverflowUpdated, hasOverflow, overflowedNodeSet } = state + const { hasOverflow } = state + const { hasOverflowUpdated, overflowedNodeSet } = checkOverflow() switch (true) { case !hasOverflowUpdated: diff --git a/packages/child/values/state.js b/packages/child/values/state.js index b698aaeb7..df0418242 100644 --- a/packages/child/values/state.js +++ b/packages/child/values/state.js @@ -2,7 +2,6 @@ export default { firstRun: true, hasTags: false, hasOverflow: false, - hasOverflowUpdated: true, isHidden: false, origin: undefined, overflowedNodeSet: new Set(), From f3b4c23a9433ee8c9aa602e04303113c2f2b74ed Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Sat, 1 Nov 2025 19:21:07 +0000 Subject: [PATCH 23/38] Extract onPageHIde --- packages/child/events/page-hide.js | 18 ++++++++++++++++++ packages/child/index.js | 18 +----------------- 2 files changed, 19 insertions(+), 17 deletions(-) create mode 100644 packages/child/events/page-hide.js diff --git a/packages/child/events/page-hide.js b/packages/child/events/page-hide.js new file mode 100644 index 000000000..13b1c4195 --- /dev/null +++ b/packages/child/events/page-hide.js @@ -0,0 +1,18 @@ +import { BEFORE_UNLOAD, PAGE_HIDE } from '../../common/consts' +import { invoke, lower } from '../../common/utils' +import { event as consoleEvent, info } from '../console' +import sendMessage from '../send/message' +import { addEventListener, tearDownList } from './listeners' + +const resetNoResponseTimer = () => sendMessage(0, 0, BEFORE_UNLOAD) + +function onPageHide({ persisted }) { + if (!persisted) resetNoResponseTimer() + consoleEvent(PAGE_HIDE) + info('Page persisted:', persisted) + if (persisted) return + tearDownList.forEach(invoke) +} + +// setupOnPageHide +export default () => addEventListener(window, lower(PAGE_HIDE), onPageHide) diff --git a/packages/child/index.js b/packages/child/index.js index a2b5c5078..24a0ad0f6 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -3,7 +3,6 @@ import { FOREGROUND, HIGHLIGHT } from 'auto-console-group' import { AUTO_RESIZE, BASE, - BEFORE_UNLOAD, BOOLEAN, CLOSE, ENABLE, @@ -20,7 +19,6 @@ import { NONE, NUMBER, OVERFLOW_OBSERVER, - PAGE_HIDE, PAGE_INFO, PAGE_INFO_STOP, PARENT_INFO, @@ -45,9 +43,7 @@ import { getModeData } from '../common/mode' import { getElementName, id, - invoke, isolateUserCode, - lower, once, typeAssert, } from '../common/utils' @@ -82,6 +78,7 @@ import { tearDownList, } from './events/listeners' import setupMouseEvents from './events/mouse' +import setupOnPageHide from './events/page-hide' import ready from './events/ready' import createMutationObserver from './observers/mutation' import createOverflowObserver from './observers/overflow' @@ -201,19 +198,6 @@ function iframeResizerChild() { sendTitle() } - const resetNoResponseTimer = () => sendMessage(0, 0, BEFORE_UNLOAD) - - function onPageHide({ persisted }) { - if (!persisted) resetNoResponseTimer() - consoleEvent(PAGE_HIDE) - info('Page persisted:', persisted) - if (persisted) return - tearDownList.forEach(invoke) - } - - const setupOnPageHide = () => - addEventListener(window, lower(PAGE_HIDE), onPageHide) - function checkAndSetupTags() { state.taggedElements = document.querySelectorAll(`[${SIZE_ATTR}]`) state.hasTags = state.taggedElements.length > 0 From e81d6e0c18aa9e3204b7647d688206efdb9d4173 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Sun, 2 Nov 2025 13:36:48 +0000 Subject: [PATCH 24/38] Extract check H/W mode --- packages/child/check/calculation-mode.js | 23 ++++++++++++++++++++++- packages/child/index.js | 19 +------------------ 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/packages/child/check/calculation-mode.js b/packages/child/check/calculation-mode.js index 01be3c3d6..8224b6d97 100644 --- a/packages/child/check/calculation-mode.js +++ b/packages/child/check/calculation-mode.js @@ -1,6 +1,11 @@ import { HIGHLIGHT } from 'auto-console-group' +import { + HEIGHT_CALC_MODE_DEFAULT, + WIDTH_CALC_MODE_DEFAULT, +} from '../../common/consts' import { advise, log, warn } from '../console' +import { getHeight, getWidth } from '../size' import settings from '../values/settings' const DEPRECATED_RESIZE_METHODS = { @@ -39,7 +44,7 @@ This version of iframe-resizer can auto detect the most suitable ${label} ) } -export default function checkCalcMode(calcMode, calcModeDefault, modes) { +export function checkCalcMode(calcMode, calcModeDefault, modes) { const { label } = modes if (calcModeDefault !== calcMode) { @@ -55,3 +60,19 @@ export default function checkCalcMode(calcMode, calcModeDefault, modes) { log(`Set ${label} calculation method: %c${calcMode}`, HIGHLIGHT) return calcMode } + +export function checkHeightMode() { + settings.heightCalcMode = checkCalcMode( + settings.heightCalcMode, + HEIGHT_CALC_MODE_DEFAULT, + getHeight, + ) +} + +export function checkWidthMode() { + settings.widthCalcMode = checkCalcMode( + settings.widthCalcMode, + WIDTH_CALC_MODE_DEFAULT, + getWidth, + ) +} diff --git a/packages/child/index.js b/packages/child/index.js index 24a0ad0f6..80b7127ef 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -36,7 +36,6 @@ import { UNDEFINED, VERSION, VISIBILITY_OBSERVER, - WIDTH_CALC_MODE_DEFAULT, WIDTH_EDGE, } from '../common/consts' import { getModeData } from '../common/mode' @@ -48,7 +47,7 @@ import { typeAssert, } from '../common/utils' import checkBlockingCSS from './check/blocking-css' -import checkCalcMode from './check/calculation-mode' +import { checkHeightMode, checkWidthMode } from './check/calculation-mode' import checkCrossDomain from './check/cross-domain' import checkDeprecatedAttrs from './check/deprecated-attributes' import checkIgnoredElements from './check/ignored-elements' @@ -238,22 +237,6 @@ function iframeResizerChild() { }) } - function checkHeightMode() { - settings.heightCalcMode = checkCalcMode( - settings.heightCalcMode, - HEIGHT_CALC_MODE_DEFAULT, - getHeight, - ) - } - - function checkWidthMode() { - settings.widthCalcMode = checkCalcMode( - settings.widthCalcMode, - WIDTH_CALC_MODE_DEFAULT, - getWidth, - ) - } - function setupEventListeners() { if (settings.autoResize !== true) { log('Auto Resize disabled') From 6f01758c527bf5fa369d89b18249fc2af60a38a5 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Mon, 3 Nov 2025 13:31:02 +0000 Subject: [PATCH 25/38] Extract user methods from child --- packages/child/index.js | 260 ++------------------------------ packages/child/methods/index.js | 214 ++++++++++++++++++++++++++ packages/child/page/reset.js | 42 ++++++ packages/child/values/state.js | 3 + 4 files changed, 268 insertions(+), 251 deletions(-) create mode 100644 packages/child/methods/index.js create mode 100644 packages/child/page/reset.js diff --git a/packages/child/index.js b/packages/child/index.js index 80b7127ef..8d719a55a 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -1,51 +1,30 @@ import { FOREGROUND, HIGHLIGHT } from 'auto-console-group' import { - AUTO_RESIZE, BASE, - BOOLEAN, - CLOSE, - ENABLE, - FUNCTION, - HEIGHT_CALC_MODE_DEFAULT, HEIGHT_EDGE, IN_PAGE_LINK, INIT, - MANUAL_RESIZE_REQUEST, MESSAGE, MESSAGE_ID, MESSAGE_ID_LENGTH, MUTATION_OBSERVER, - NONE, - NUMBER, OVERFLOW_OBSERVER, PAGE_INFO, - PAGE_INFO_STOP, PARENT_INFO, - PARENT_INFO_STOP, PARENT_RESIZE_REQUEST, READY_STATE_CHANGE, RESIZE_OBSERVER, - SCROLL_BY, - SCROLL_TO, SCROLL_TO_OFFSET, SEPARATOR, - SET_OFFSET_SIZE, SIZE_ATTR, - STRING, UNDEFINED, VERSION, VISIBILITY_OBSERVER, WIDTH_EDGE, } from '../common/consts' import { getModeData } from '../common/mode' -import { - getElementName, - id, - isolateUserCode, - once, - typeAssert, -} from '../common/utils' +import { getElementName, id, isolateUserCode, once } from '../common/utils' import checkBlockingCSS from './check/blocking-css' import { checkHeightMode, checkWidthMode } from './check/calculation-mode' import checkCrossDomain from './check/cross-domain' @@ -58,9 +37,6 @@ import checkReadyYet from './check/ready' import checkVersion from './check/version' import { advise, - debug, - deprecateMethod, - deprecateMethodReplace, endAutoGroup, error, errorBoundary, @@ -79,6 +55,7 @@ import { import setupMouseEvents from './events/mouse' import setupOnPageHide from './events/page-hide' import ready from './events/ready' +import setupPublicMethods from './methods' import createMutationObserver from './observers/mutation' import createOverflowObserver from './observers/overflow' import createPerformanceObserver from './observers/perf' @@ -87,13 +64,13 @@ import createVisibilityObserver from './observers/visibility' import createApplySelectors from './page/apply-selectors' import injectClearFixIntoBodyElement from './page/clear-fix' import { setBodyStyle, setMargin } from './page/css' +import { triggerReset } from './page/reset' import stopInfiniteResizingOfIframe from './page/stop-infinite-resizing' import readDataFromPage from './read/from-page' import readDataFromParent from './read/from-parent' import sendMessage from './send/message' import sendSize from './send/size' import sendTitle from './send/title' -import { getHeight, getWidth } from './size' import { getAllElements } from './size/all' import settings from './values/settings' import state from './values/state' @@ -102,14 +79,10 @@ function iframeResizerChild() { const EVENT_CANCEL_TIMER = 128 let applySelectors = id - let inPageLinks = {} let overflowObserver let resizeObserver let win = window - let onPageInfo = null - let onParentInfo = null - function isolate(funcs) { const { mode } = settings funcs.forEach((func) => { @@ -177,7 +150,7 @@ function iframeResizerChild() { setupEventListeners, () => setupMouseEvents(settings), setupOnPageHide, - setupPublicMethods, + () => setupPublicMethods(win), ] isolate(setup) @@ -339,194 +312,12 @@ function iframeResizerChild() { log('In page linking not enabled') } - inPageLinks = { - ...inPageLinks, + state.inPageLinks = { + enabled, findTarget, } } - function setupPublicMethods() { - if (settings.mode === 1) return - - win.parentIframe = Object.freeze({ - autoResize: (enable) => { - typeAssert(enable, BOOLEAN, 'parentIframe.autoResize(enable) enable') - const { autoResize, calculateHeight, calculateWidth } = settings - - if (calculateWidth === false && calculateHeight === false) { - consoleEvent(ENABLE) - advise( - `Auto Resize can not be changed when direction is set to '${NONE}'.`, // or '${BOTH}' - ) - return false - } - - if (enable === true && autoResize === false) { - settings.autoResize = true - queueMicrotask(() => sendSize(ENABLE, 'Auto Resize enabled')) - } else if (enable === false && autoResize === true) { - settings.autoResize = false - } - - sendMessage(0, 0, AUTO_RESIZE, JSON.stringify(settings.autoResize)) - - return settings.autoResize - }, - - close() { - sendMessage(0, 0, CLOSE) - }, - - getId: () => settings.parentId, - - getOrigin: () => { - consoleEvent('getOrigin') - deprecateMethod('getOrigin()', 'getParentOrigin()') - return state.origin - }, - - getParentOrigin: () => state.origin, - - getPageInfo(callback) { - if (typeof callback === FUNCTION) { - onPageInfo = callback - sendMessage(0, 0, PAGE_INFO) - deprecateMethodReplace( - 'getPageInfo()', - 'getParentProps()', - 'See https://iframe-resizer.com/upgrade for details. ', - ) - return - } - - onPageInfo = null - sendMessage(0, 0, PAGE_INFO_STOP) - }, - - getParentProps(callback) { - typeAssert( - callback, - FUNCTION, - 'parentIframe.getParentProps(callback) callback', - ) - - onParentInfo = callback - sendMessage(0, 0, PARENT_INFO) - - return () => { - onParentInfo = null - sendMessage(0, 0, PARENT_INFO_STOP) - } - }, - - getParentProperties(callback) { - deprecateMethod('getParentProperties()', 'getParentProps()') - this.getParentProps(callback) - }, - - moveToAnchor(anchor) { - typeAssert(anchor, STRING, 'parentIframe.moveToAnchor(anchor) anchor') - inPageLinks.findTarget(anchor) - }, - - reset() { - resetIframe('parentIframe.reset') - }, - - setOffsetSize(newOffset) { - typeAssert( - newOffset, - NUMBER, - 'parentIframe.setOffsetSize(offset) offset', - ) - settings.offsetHeight = newOffset - settings.offsetWidth = newOffset - sendSize(SET_OFFSET_SIZE, `parentIframe.setOffsetSize(${newOffset})`) - }, - - scrollBy(x, y) { - typeAssert(x, NUMBER, 'parentIframe.scrollBy(x, y) x') - typeAssert(y, NUMBER, 'parentIframe.scrollBy(x, y) y') - sendMessage(y, x, SCROLL_BY) // X&Y reversed at sendMessage uses height/width - }, - - scrollTo(x, y) { - typeAssert(x, NUMBER, 'parentIframe.scrollTo(x, y) x') - typeAssert(y, NUMBER, 'parentIframe.scrollTo(x, y) y') - sendMessage(y, x, SCROLL_TO) // X&Y reversed at sendMessage uses height/width - }, - - scrollToOffset(x, y) { - typeAssert(x, NUMBER, 'parentIframe.scrollToOffset(x, y) x') - typeAssert(y, NUMBER, 'parentIframe.scrollToOffset(x, y) y') - sendMessage(y, x, SCROLL_TO_OFFSET) // X&Y reversed at sendMessage uses height/width - }, - - sendMessage(msg, targetOrigin) { - if (targetOrigin) - typeAssert( - targetOrigin, - STRING, - 'parentIframe.sendMessage(msg, targetOrigin) targetOrigin', - ) - sendMessage(0, 0, MESSAGE, JSON.stringify(msg), targetOrigin) - }, - - setHeightCalculationMethod(heightCalculationMethod) { - settings.heightCalcMode = heightCalculationMethod - checkHeightMode() - }, - - setWidthCalculationMethod(widthCalculationMethod) { - settings.widthCalcMode = widthCalculationMethod - checkWidthMode() - }, - - setTargetOrigin(targetOrigin) { - typeAssert( - targetOrigin, - STRING, - 'parentIframe.setTargetOrigin(targetOrigin) targetOrigin', - ) - - log(`Set targetOrigin: %c${targetOrigin}`, HIGHLIGHT) - settings.targetOrigin = targetOrigin - }, - - resize(customHeight, customWidth) { - if (customHeight !== undefined) - typeAssert( - customHeight, - NUMBER, - 'parentIframe.resize(customHeight, customWidth) customHeight', - ) - - if (customWidth !== undefined) - typeAssert( - customWidth, - NUMBER, - 'parentIframe.resize(customHeight, customWidth) customWidth', - ) - - const valString = `${customHeight || ''}${customWidth ? `,${customWidth}` : ''}` - - sendSize( - MANUAL_RESIZE_REQUEST, - `parentIframe.resize(${valString})`, - customHeight, - customWidth, - ) - }, - - size(customHeight, customWidth) { - deprecateMethod('size()', 'resize()') - this.resize(customHeight, customWidth) - }, - }) - - win.parentIFrame = win.parentIframe - } - function overflowObserved() { const { hasOverflow } = state const { hasOverflowUpdated, overflowedNodeSet } = checkOverflow() @@ -655,41 +446,6 @@ function iframeResizerChild() { pushDisconnectsOnToTearDown(observers) } - function lockTrigger() { - if (state.triggerLocked) { - log('TriggerLock blocked calculation') - return - } - state.triggerLocked = true - debug('Trigger event lock on') - - requestAnimationFrame(() => { - state.triggerLocked = false - debug('Trigger event lock off') - }) - } - - function triggerReset(triggerEvent) { - const { heightCalcMode, widthCalcMode } = settings - - log(`Reset trigger event: %c${triggerEvent}`, HIGHLIGHT) - const height = getHeight[heightCalcMode]() - const width = getWidth[widthCalcMode]() - - sendMessage(height, width, triggerEvent) - } - - function resetIframe(triggerEventDesc) { - const hcm = settings.heightCalcMode - settings.heightCalcMode = HEIGHT_CALC_MODE_DEFAULT - - log(`Reset trigger event: %c${triggerEventDesc}`, HIGHLIGHT) - lockTrigger() - triggerReset('reset') - - settings.heightCalcMode = hcm - } - let initLock = true function receiver(event) { consoleEvent('onMessage') @@ -737,7 +493,7 @@ function iframeResizerChild() { }, moveToAnchor() { - inPageLinks.findTarget(getData()) + state.inPageLinks.findTarget(getData()) }, inPageLink() { @@ -745,6 +501,7 @@ function iframeResizerChild() { }, // Backward compatibility pageInfo() { + const { onPageInfo } = state const msgBody = getData() log(`PageInfo received from parent:`, parseFrozen(msgBody)) if (onPageInfo) { @@ -755,6 +512,7 @@ function iframeResizerChild() { }, parentInfo() { + const { onParentInfo } = state const msgBody = parseFrozen(getData()) log(`ParentInfo received from parent:`, msgBody) if (onParentInfo) { diff --git a/packages/child/methods/index.js b/packages/child/methods/index.js new file mode 100644 index 000000000..372a44ae3 --- /dev/null +++ b/packages/child/methods/index.js @@ -0,0 +1,214 @@ +import { HIGHLIGHT } from 'auto-console-group' + +import { + AUTO_RESIZE, + BOOLEAN, + CLOSE, + ENABLE, + FUNCTION, + MANUAL_RESIZE_REQUEST, + MESSAGE, + NONE, + NUMBER, + PAGE_INFO, + PAGE_INFO_STOP, + PARENT_INFO, + PARENT_INFO_STOP, + SCROLL_BY, + SCROLL_TO, + SCROLL_TO_OFFSET, + SET_OFFSET_SIZE, + STRING, +} from '../../common/consts' +import { typeAssert } from '../../common/utils' +import { checkHeightMode, checkWidthMode } from '../check/calculation-mode' +import { + advise, + deprecateMethod, + deprecateMethodReplace, + event as consoleEvent, + log, +} from '../console' +import { resetIframe } from '../page/reset' +import sendMessage from '../send/message' +import sendSize from '../send/size' +import settings from '../values/settings' +import state from '../values/state' + +export default function setupPublicMethods(win) { + if (settings.mode === 1) return + + win.parentIframe = Object.freeze({ + autoResize: (enable) => { + typeAssert(enable, BOOLEAN, 'parentIframe.autoResize(enable) enable') + const { autoResize, calculateHeight, calculateWidth } = settings + + if (calculateWidth === false && calculateHeight === false) { + consoleEvent(ENABLE) + advise( + `Auto Resize can not be changed when direction is set to '${NONE}'.`, // or '${BOTH}' + ) + return false + } + + if (enable === true && autoResize === false) { + settings.autoResize = true + queueMicrotask(() => sendSize(ENABLE, 'Auto Resize enabled')) + } else if (enable === false && autoResize === true) { + settings.autoResize = false + } + + sendMessage(0, 0, AUTO_RESIZE, JSON.stringify(settings.autoResize)) + + return settings.autoResize + }, + + close() { + sendMessage(0, 0, CLOSE) + }, + + getId: () => settings.parentId, + + getOrigin: () => { + consoleEvent('getOrigin') + deprecateMethod('getOrigin()', 'getParentOrigin()') + return state.origin + }, + + getParentOrigin: () => state.origin, + + getPageInfo(callback) { + if (typeof callback === FUNCTION) { + state.onPageInfo = callback + sendMessage(0, 0, PAGE_INFO) + deprecateMethodReplace( + 'getPageInfo()', + 'getParentProps()', + 'See https://iframe-resizer.com/upgrade for details. ', + ) + return + } + + state.onPageInfo = null + sendMessage(0, 0, PAGE_INFO_STOP) + }, + + getParentProps(callback) { + typeAssert( + callback, + FUNCTION, + 'parentIframe.getParentProps(callback) callback', + ) + + state.onParentInfo = callback + sendMessage(0, 0, PARENT_INFO) + + return () => { + state.onParentInfo = null + sendMessage(0, 0, PARENT_INFO_STOP) + } + }, + + getParentProperties(callback) { + deprecateMethod('getParentProperties()', 'getParentProps()') + this.getParentProps(callback) + }, + + moveToAnchor(anchor) { + typeAssert(anchor, STRING, 'parentIframe.moveToAnchor(anchor) anchor') + state.inPageLinks.findTarget(anchor) + }, + + reset() { + resetIframe('parentIframe.reset') + }, + + setOffsetSize(newOffset) { + typeAssert(newOffset, NUMBER, 'parentIframe.setOffsetSize(offset) offset') + settings.offsetHeight = newOffset + settings.offsetWidth = newOffset + sendSize(SET_OFFSET_SIZE, `parentIframe.setOffsetSize(${newOffset})`) + }, + + scrollBy(x, y) { + typeAssert(x, NUMBER, 'parentIframe.scrollBy(x, y) x') + typeAssert(y, NUMBER, 'parentIframe.scrollBy(x, y) y') + sendMessage(y, x, SCROLL_BY) // X&Y reversed at sendMessage uses height/width + }, + + scrollTo(x, y) { + typeAssert(x, NUMBER, 'parentIframe.scrollTo(x, y) x') + typeAssert(y, NUMBER, 'parentIframe.scrollTo(x, y) y') + sendMessage(y, x, SCROLL_TO) // X&Y reversed at sendMessage uses height/width + }, + + scrollToOffset(x, y) { + typeAssert(x, NUMBER, 'parentIframe.scrollToOffset(x, y) x') + typeAssert(y, NUMBER, 'parentIframe.scrollToOffset(x, y) y') + sendMessage(y, x, SCROLL_TO_OFFSET) // X&Y reversed at sendMessage uses height/width + }, + + sendMessage(msg, targetOrigin) { + if (targetOrigin) + typeAssert( + targetOrigin, + STRING, + 'parentIframe.sendMessage(msg, targetOrigin) targetOrigin', + ) + sendMessage(0, 0, MESSAGE, JSON.stringify(msg), targetOrigin) + }, + + setHeightCalculationMethod(heightCalculationMethod) { + settings.heightCalcMode = heightCalculationMethod + checkHeightMode() + }, + + setWidthCalculationMethod(widthCalculationMethod) { + settings.widthCalcMode = widthCalculationMethod + checkWidthMode() + }, + + setTargetOrigin(targetOrigin) { + typeAssert( + targetOrigin, + STRING, + 'parentIframe.setTargetOrigin(targetOrigin) targetOrigin', + ) + + log(`Set targetOrigin: %c${targetOrigin}`, HIGHLIGHT) + settings.targetOrigin = targetOrigin + }, + + resize(customHeight, customWidth) { + if (customHeight !== undefined) + typeAssert( + customHeight, + NUMBER, + 'parentIframe.resize(customHeight, customWidth) customHeight', + ) + + if (customWidth !== undefined) + typeAssert( + customWidth, + NUMBER, + 'parentIframe.resize(customHeight, customWidth) customWidth', + ) + + const valString = `${customHeight || ''}${customWidth ? `,${customWidth}` : ''}` + + sendSize( + MANUAL_RESIZE_REQUEST, + `parentIframe.resize(${valString})`, + customHeight, + customWidth, + ) + }, + + size(customHeight, customWidth) { + deprecateMethod('size()', 'resize()') + this.resize(customHeight, customWidth) + }, + }) + + win.parentIFrame = win.parentIframe +} diff --git a/packages/child/page/reset.js b/packages/child/page/reset.js new file mode 100644 index 000000000..80b949b49 --- /dev/null +++ b/packages/child/page/reset.js @@ -0,0 +1,42 @@ +import { HIGHLIGHT } from 'auto-console-group' + +import { HEIGHT_CALC_MODE_DEFAULT } from '../../common/consts' +import { debug, log } from '../console' +import sendMessage from '../send/message' +import { getHeight, getWidth } from '../size' +import settings from '../values/settings' +import state from '../values/state' + +function lockTrigger() { + if (state.triggerLocked) { + log('TriggerLock blocked calculation') + return + } + + state.triggerLocked = true + debug('Trigger event lock on') + + requestAnimationFrame(() => { + state.triggerLocked = false + debug('Trigger event lock off') + }) +} + +export function triggerReset(triggerEvent) { + const { heightCalcMode, widthCalcMode } = settings + const height = getHeight[heightCalcMode]() + const width = getWidth[widthCalcMode]() + + log(`Reset trigger event: %c${triggerEvent}`, HIGHLIGHT) + sendMessage(height, width, triggerEvent) +} + +export function resetIframe(triggerEventDesc) { + const hcm = settings.heightCalcMode + + log(`Reset trigger event: %c${triggerEventDesc}`, HIGHLIGHT) + settings.heightCalcMode = HEIGHT_CALC_MODE_DEFAULT + lockTrigger() + triggerReset('reset') + settings.heightCalcMode = hcm +} diff --git a/packages/child/values/state.js b/packages/child/values/state.js index df0418242..f59650959 100644 --- a/packages/child/values/state.js +++ b/packages/child/values/state.js @@ -3,10 +3,13 @@ export default { hasTags: false, hasOverflow: false, isHidden: false, + inPageLinks: {}, origin: undefined, overflowedNodeSet: new Set(), sameOrigin: false, taggedElements: [], target: window?.parent, triggerLocked: false, + onPageInfo: null, + onParentInfo: null, } From 7ed7ea7d555810e21950ff0cc790599eeaaa218c Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Mon, 3 Nov 2025 14:07:25 +0000 Subject: [PATCH 26/38] Extract inPageLinks --- packages/child/check/mode.js | 8 ++- packages/child/index.js | 111 ++-------------------------------- packages/child/page/links.js | 112 +++++++++++++++++++++++++++++++++++ packages/common/consts.js | 2 + 4 files changed, 123 insertions(+), 110 deletions(-) create mode 100644 packages/child/page/links.js diff --git a/packages/child/check/mode.js b/packages/child/check/mode.js index a275872b5..0448cf4ab 100644 --- a/packages/child/check/mode.js +++ b/packages/child/check/mode.js @@ -1,8 +1,9 @@ import { VERSION } from '../../common/consts' import setMode, { getModeData, getModeLabel } from '../../common/mode' import { isDef } from '../../common/utils' -import { advise, log, purge, vInfo } from '../console' +import { advise, purge, vInfo } from '../console' import settings from '../values/settings' +import state from '../values/state' export default function ({ key, key2, mode, version }) { const oMode = mode @@ -10,13 +11,14 @@ export default function ({ key, key2, mode, version }) { const cMode = setMode({ key2 }) // eslint-disable-next-line no-multi-assign settings.mode = mode = Math.max(pMode, cMode) - log('Final mode set to:', getModeLabel(mode)) if (mode < 0) { mode = Math.min(pMode, cMode) purge() advise(`${getModeData(mode + 2)}${getModeData(2)}`) - if (isDef(version)) + if (isDef(version)) { + state.firstRun = false throw getModeData(mode + 2).replace(/<\/?[a-z][^>]*>|<\/>/gi, '') + } } else if (!isDef(version) || (oMode > -1 && mode > oMode)) { if (sessionStorage.getItem('ifr') !== VERSION) vInfo(`v${VERSION} (${getModeLabel(mode)})`, mode) diff --git a/packages/child/index.js b/packages/child/index.js index 8d719a55a..dc105ce2c 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -1,9 +1,8 @@ -import { FOREGROUND, HIGHLIGHT } from 'auto-console-group' +import { HIGHLIGHT } from 'auto-console-group' import { - BASE, + EVENT_CANCEL_TIMER, HEIGHT_EDGE, - IN_PAGE_LINK, INIT, MESSAGE, MESSAGE_ID, @@ -15,7 +14,6 @@ import { PARENT_RESIZE_REQUEST, READY_STATE_CHANGE, RESIZE_OBSERVER, - SCROLL_TO_OFFSET, SEPARATOR, SIZE_ATTR, UNDEFINED, @@ -23,7 +21,6 @@ import { VISIBILITY_OBSERVER, WIDTH_EDGE, } from '../common/consts' -import { getModeData } from '../common/mode' import { getElementName, id, isolateUserCode, once } from '../common/utils' import checkBlockingCSS from './check/blocking-css' import { checkHeightMode, checkWidthMode } from './check/calculation-mode' @@ -64,6 +61,7 @@ import createVisibilityObserver from './observers/visibility' import createApplySelectors from './page/apply-selectors' import injectClearFixIntoBodyElement from './page/clear-fix' import { setBodyStyle, setMargin } from './page/css' +import setupInPageLinks from './page/links' import { triggerReset } from './page/reset' import stopInfiniteResizingOfIframe from './page/stop-infinite-resizing' import readDataFromPage from './read/from-page' @@ -76,8 +74,6 @@ import settings from './values/settings' import state from './values/state' function iframeResizerChild() { - const EVENT_CANCEL_TIMER = 128 - let applySelectors = id let overflowObserver let resizeObserver @@ -114,6 +110,7 @@ function iframeResizerChild() { } function init(data) { + if (!state.firstRun) return map2settings(readDataFromParent(data)) startLogging(settings) map2settings(readDataFromPage()) @@ -218,106 +215,6 @@ function iframeResizerChild() { manageEventListeners('add') } - function setupInPageLinks(enabled) { - const getPagePosition = () => ({ - x: document.documentElement.scrollLeft, - y: document.documentElement.scrollTop, - }) - - function getElementPosition(el) { - const elPosition = el.getBoundingClientRect() - const pagePosition = getPagePosition() - - return { - x: parseInt(elPosition.left, BASE) + parseInt(pagePosition.x, BASE), - y: parseInt(elPosition.top, BASE) + parseInt(pagePosition.y, BASE), - } - } - - function findTarget(location) { - function jumpToTarget(target) { - const jumpPosition = getElementPosition(target) - - log( - `Moving to in page link (%c#${hash}%c) at x: %c${jumpPosition.x}%c y: %c${jumpPosition.y}`, - HIGHLIGHT, - FOREGROUND, - HIGHLIGHT, - FOREGROUND, - HIGHLIGHT, - ) - - sendMessage(jumpPosition.y, jumpPosition.x, SCROLL_TO_OFFSET) // X&Y reversed at sendMessage uses height/width - } - - const hash = location.split('#')[1] || location // Remove # if present - const hashData = decodeURIComponent(hash) - const target = - document.getElementById(hashData) || - document.getElementsByName(hashData)[0] - - if (target !== undefined) { - jumpToTarget(target) - return - } - - log(`In page link (#${hash}) not found in iframe, so sending to parent`) - sendMessage(0, 0, IN_PAGE_LINK, `#${hash}`) - } - - function checkLocationHash() { - const { hash, href } = window.location - - if (hash !== '' && hash !== '#') { - findTarget(href) - } - } - - function bindAnchors() { - for (const link of document.querySelectorAll('a[href^="#"]')) { - if (link.getAttribute('href') !== '#') { - addEventListener(link, 'click', (e) => { - e.preventDefault() - findTarget(link.getAttribute('href')) - }) - } - } - } - - function bindLocationHash() { - addEventListener(window, 'hashchange', checkLocationHash) - } - - function initCheck() { - // Check if page loaded with location hash after init resize - setTimeout(checkLocationHash, EVENT_CANCEL_TIMER) - } - - function enableInPageLinks() { - log('Setting up location.hash handlers') - bindAnchors() - bindLocationHash() - initCheck() - } - - const { mode } = settings - - if (enabled) { - if (mode === 1) { - advise(getModeData(5)) - } else { - enableInPageLinks() - } - } else { - log('In page linking not enabled') - } - - state.inPageLinks = { - enabled, - findTarget, - } - } - function overflowObserved() { const { hasOverflow } = state const { hasOverflowUpdated, overflowedNodeSet } = checkOverflow() diff --git a/packages/child/page/links.js b/packages/child/page/links.js new file mode 100644 index 000000000..0eb0d4d24 --- /dev/null +++ b/packages/child/page/links.js @@ -0,0 +1,112 @@ +import { FOREGROUND, HIGHLIGHT } from 'auto-console-group' + +import { + BASE, + EVENT_CANCEL_TIMER, + IN_PAGE_LINK, + SCROLL_TO_OFFSET, +} from '../../common/consts' +import { getModeData } from '../../common/mode' +import { advise, log } from '../console' +import { addEventListener } from '../events/listeners' +import sendMessage from '../send/message' +import settings from '../values/settings' +import state from '../values/state' + +const getPagePosition = () => ({ + x: document.documentElement.scrollLeft, + y: document.documentElement.scrollTop, +}) + +function getElementPosition(el) { + const elPosition = el.getBoundingClientRect() + const pagePosition = getPagePosition() + + return { + x: parseInt(elPosition.left, BASE) + parseInt(pagePosition.x, BASE), + y: parseInt(elPosition.top, BASE) + parseInt(pagePosition.y, BASE), + } +} + +function jumpToTarget(hash, target) { + const jumpPosition = getElementPosition(target) + + log( + `Moving to in page link (%c#${hash}%c) at x: %c${jumpPosition.x}%c y: %c${jumpPosition.y}`, + HIGHLIGHT, + FOREGROUND, + HIGHLIGHT, + FOREGROUND, + HIGHLIGHT, + ) + + sendMessage(jumpPosition.y, jumpPosition.x, SCROLL_TO_OFFSET) // X&Y reversed at sendMessage uses height/width +} + +function findTarget(location) { + const hash = location.split('#')[1] || location // Remove # if present + const hashData = decodeURIComponent(hash) + const target = + document.getElementById(hashData) || document.getElementsByName(hashData)[0] + + if (target !== undefined) { + jumpToTarget(hash, target) + return + } + + log(`In page link (#${hash}) not found in iframe, so sending to parent`) + sendMessage(0, 0, IN_PAGE_LINK, `#${hash}`) +} + +function checkLocationHash() { + const { hash, href } = window.location + + if (hash !== '' && hash !== '#') { + findTarget(href) + } +} + +function bindAnchors() { + for (const link of document.querySelectorAll('a[href^="#"]')) { + if (link.getAttribute('href') !== '#') { + addEventListener(link, 'click', (e) => { + e.preventDefault() + findTarget(link.getAttribute('href')) + }) + } + } +} + +function bindLocationHash() { + addEventListener(window, 'hashchange', checkLocationHash) +} + +function initCheck() { + // Check if page loaded with location hash after init resize + setTimeout(checkLocationHash, EVENT_CANCEL_TIMER) +} + +function enableInPageLinks() { + log('Setting up location.hash handlers') + bindAnchors() + bindLocationHash() + initCheck() + + state.inPageLinks = { + findTarget, + } +} + +export default function setupInPageLinks(enabled) { + const { mode } = settings + + if (enabled) { + if (mode === 1) { + advise(getModeData(5)) + } else { + enableInPageLinks() + } + } else { + log('In page linking not enabled') + } +} diff --git a/packages/common/consts.js b/packages/common/consts.js index da4c91925..918ed65cf 100644 --- a/packages/common/consts.js +++ b/packages/common/consts.js @@ -123,6 +123,8 @@ export const COLLAPSE = 'collapsed' export const HEIGHT_CALC_MODE_DEFAULT = AUTO export const WIDTH_CALC_MODE_DEFAULT = SCROLL +export const EVENT_CANCEL_TIMER = 128 + export const LOG_OPTIONS = Object.freeze({ [EXPAND]: 1, [COLLAPSE]: 1, From 8bfe416cffe039d23c941ebb1e58f45537fd1bd9 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Mon, 3 Nov 2025 14:33:58 +0000 Subject: [PATCH 27/38] Extract print events --- packages/child/check/settings.js | 12 +++++++ packages/child/check/tags.js | 11 +++++++ packages/child/events/print.js | 19 +++++++++++ packages/child/index.js | 55 +++----------------------------- packages/child/read/from-page.js | 15 ++------- 5 files changed, 50 insertions(+), 62 deletions(-) create mode 100644 packages/child/check/settings.js create mode 100644 packages/child/check/tags.js create mode 100644 packages/child/events/print.js diff --git a/packages/child/check/settings.js b/packages/child/check/settings.js new file mode 100644 index 000000000..b2e89fb00 --- /dev/null +++ b/packages/child/check/settings.js @@ -0,0 +1,12 @@ +import { HIGHLIGHT } from 'auto-console-group' + +import { info, log } from '../console' +import settings from '../values/settings' + +export default () => { + info(`Set targetOrigin for parent: %c${settings.targetOrigin}`, HIGHLIGHT) + + if (settings.autoResize !== true) { + log('Auto Resize disabled') + } +} diff --git a/packages/child/check/tags.js b/packages/child/check/tags.js new file mode 100644 index 000000000..7b852582a --- /dev/null +++ b/packages/child/check/tags.js @@ -0,0 +1,11 @@ +import { HIGHLIGHT } from 'auto-console-group' + +import { SIZE_ATTR } from '../../common/consts' +import { log } from '../console' +import state from '../values/state' + +export default function checkAndSetupTags() { + state.taggedElements = document.querySelectorAll(`[${SIZE_ATTR}]`) + state.hasTags = state.taggedElements.length > 0 + log(`Tagged elements found: %c${state.hasTags}`, HIGHLIGHT) +} diff --git a/packages/child/events/print.js b/packages/child/events/print.js new file mode 100644 index 000000000..f4f6a68e0 --- /dev/null +++ b/packages/child/events/print.js @@ -0,0 +1,19 @@ +import sendSize from '../send/size' +import { addEventListener } from './listeners' + +function add({ eventType, eventName }) { + const handleEvent = () => sendSize(eventName, eventType) + addEventListener(window, eventName, handleEvent, { passive: true }) +} + +export default function setupPrintListeners() { + add({ + eventType: 'After Print', + eventName: 'afterprint', + }) + + add({ + eventType: 'Before Print', + eventName: 'beforeprint', + }) +} diff --git a/packages/child/index.js b/packages/child/index.js index dc105ce2c..5e6b07a13 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -15,7 +15,6 @@ import { READY_STATE_CHANGE, RESIZE_OBSERVER, SEPARATOR, - SIZE_ATTR, UNDEFINED, VERSION, VISIBILITY_OBSERVER, @@ -31,6 +30,8 @@ import checkMode from './check/mode' import checkOverflow from './check/overflow' import checkQuirksMode from './check/quirks-mode' import checkReadyYet from './check/ready' +import checkSettings from './check/settings' +import checkAndSetupTags from './check/tags' import checkVersion from './check/version' import { advise, @@ -51,6 +52,7 @@ import { } from './events/listeners' import setupMouseEvents from './events/mouse' import setupOnPageHide from './events/page-hide' +import setupPrintListeners from './events/print' import ready from './events/ready' import setupPublicMethods from './methods' import createMutationObserver from './observers/mutation' @@ -131,6 +133,7 @@ function iframeResizerChild() { checkDeprecatedAttrs, checkQuirksMode, checkAndSetupTags, + checkSettings, bothDirections ? id : checkBlockingCSS, () => setMargin(settings), @@ -144,7 +147,7 @@ function iframeResizerChild() { attachObservers, () => setupInPageLinks(inPageLinks), - setupEventListeners, + setupPrintListeners, () => setupMouseEvents(settings), setupOnPageHide, () => setupPublicMethods(win), @@ -167,54 +170,6 @@ function iframeResizerChild() { sendTitle() } - function checkAndSetupTags() { - state.taggedElements = document.querySelectorAll(`[${SIZE_ATTR}]`) - state.hasTags = state.taggedElements.length > 0 - log(`Tagged elements found: %c${state.hasTags}`, HIGHLIGHT) - } - - const eventHandlersByName = {} - function manageTriggerEvent(options) { - const listener = { - add(eventName) { - const handleEvent = () => sendSize(options.eventName, options.eventType) - - eventHandlersByName[eventName] = handleEvent - addEventListener(window, eventName, handleEvent, { passive: true }) - }, - remove(eventName) { - const handleEvent = eventHandlersByName[eventName] - delete eventHandlersByName[eventName] - - removeEventListener(window, eventName, handleEvent) - }, - } - - listener[options.method](options.eventName) - } - - function manageEventListeners(method) { - manageTriggerEvent({ - method, - eventType: 'After Print', - eventName: 'afterprint', - }) - - manageTriggerEvent({ - method, - eventType: 'Before Print', - eventName: 'beforeprint', - }) - } - - function setupEventListeners() { - if (settings.autoResize !== true) { - log('Auto Resize disabled') - } - - manageEventListeners('add') - } - function overflowObserved() { const { hasOverflow } = state const { hasOverflowUpdated, overflowedNodeSet } = checkOverflow() diff --git a/packages/child/read/from-page.js b/packages/child/read/from-page.js index 74983eeb4..31d79cb24 100644 --- a/packages/child/read/from-page.js +++ b/packages/child/read/from-page.js @@ -1,5 +1,3 @@ -import { HIGHLIGHT } from 'auto-console-group' - import { FUNCTION, HEIGHT, @@ -11,7 +9,7 @@ import { WIDTH, } from '../../common/consts' import { getKey } from '../../common/mode' -import { deprecateOption, info, log } from '../console' +import { deprecateOption, log } from '../console' import setupCustomCalcMethod from '../size/custom' import settings from '../values/settings' @@ -75,16 +73,9 @@ function readData(data) { } export default function readDataFromPage() { - const { mode, targetOrigin } = settings + const { mode } = settings if (mode === 1) return {} const data = window.iframeResizer || window.iFrameResizer - const localSettings = isObject(data) ? readData(data) : {} - - info( - `Set targetOrigin for parent: %c${localSettings.targetOrigin || targetOrigin}`, - HIGHLIGHT, - ) - - return localSettings + return isObject(data) ? readData(data) : {} } From 5de0cf42af35e8997833679784db9da73b55f9ed Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Mon, 3 Nov 2025 14:49:44 +0000 Subject: [PATCH 28/38] Extract utils --- packages/child/check/both.js | 3 ++ packages/child/index.js | 41 ++++++---------------------- packages/child/observed/index.js | 0 packages/child/utils/isolate.js | 17 ++++++++++++ packages/child/utils/map-settings.js | 7 +++++ packages/child/values/state.js | 1 + 6 files changed, 37 insertions(+), 32 deletions(-) create mode 100644 packages/child/check/both.js create mode 100644 packages/child/observed/index.js create mode 100644 packages/child/utils/isolate.js create mode 100644 packages/child/utils/map-settings.js diff --git a/packages/child/check/both.js b/packages/child/check/both.js new file mode 100644 index 000000000..7aba1e748 --- /dev/null +++ b/packages/child/check/both.js @@ -0,0 +1,3 @@ +// checkBoth +export default ({ calculateWidth, calculateHeight }) => + calculateWidth === calculateHeight diff --git a/packages/child/index.js b/packages/child/index.js index 5e6b07a13..fa0a64681 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -22,6 +22,7 @@ import { } from '../common/consts' import { getElementName, id, isolateUserCode, once } from '../common/utils' import checkBlockingCSS from './check/blocking-css' +import checkBoth from './check/both' import { checkHeightMode, checkWidthMode } from './check/calculation-mode' import checkCrossDomain from './check/cross-domain' import checkDeprecatedAttrs from './check/deprecated-attributes' @@ -34,9 +35,7 @@ import checkSettings from './check/settings' import checkAndSetupTags from './check/tags' import checkVersion from './check/version' import { - advise, endAutoGroup, - error, errorBoundary, event as consoleEvent, info, @@ -72,39 +71,14 @@ import sendMessage from './send/message' import sendSize from './send/size' import sendTitle from './send/title' import { getAllElements } from './size/all' +import isolate from './utils/isolate' +import map2settings from './utils/map-settings' import settings from './values/settings' import state from './values/state' function iframeResizerChild() { - let applySelectors = id - let overflowObserver - let resizeObserver let win = window - function isolate(funcs) { - const { mode } = settings - funcs.forEach((func) => { - try { - func() - } catch (error_) { - if (mode < 0) throw error_ - advise( - `Error in setup function\niframe-resizer detected an error during setup.\n\nPlease report the following error message at https://github.com/davidjbradshaw/iframe-resizer/issues`, - ) - error(error_) - } - }) - } - - const checkBoth = ({ calculateWidth, calculateHeight }) => - calculateWidth === calculateHeight - - function map2settings(data) { - for (const [key, value] of Object.entries(data)) { - if (value) settings[key] = value - } - } - function startLogging({ logExpand, logging, parentId }) { setConsoleOptions({ id: parentId, enabled: logging, expand: logExpand }) consoleEvent('initReceived') @@ -121,7 +95,7 @@ function iframeResizerChild() { const { bodyBackground, bodyPadding, inPageLinks, onReady } = settings const bothDirections = checkBoth(settings) - applySelectors = createApplySelectors(settings) + state.applySelectors = createApplySelectors(settings) const setup = [ () => checkVersion(settings), @@ -143,7 +117,7 @@ function iframeResizerChild() { bothDirections ? id : stopInfiniteResizingOfIframe, injectClearFixIntoBodyElement, - applySelectors, + state.applySelectors, attachObservers, () => setupInPageLinks(inPageLinks), @@ -170,6 +144,9 @@ function iframeResizerChild() { sendTitle() } + let overflowObserver + let resizeObserver + function overflowObserved() { const { hasOverflow } = state const { hasOverflowUpdated, overflowedNodeSet } = checkOverflow() @@ -266,7 +243,7 @@ function iframeResizerChild() { function contentMutated({ addedNodes, removedNodes }) { consoleEvent('contentMutated') - applySelectors() + state.applySelectors() checkAndSetupTags() checkOverflow() endAutoGroup() diff --git a/packages/child/observed/index.js b/packages/child/observed/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/packages/child/utils/isolate.js b/packages/child/utils/isolate.js new file mode 100644 index 000000000..74ba9c1e1 --- /dev/null +++ b/packages/child/utils/isolate.js @@ -0,0 +1,17 @@ +import { advise, error } from '../console' +import settings from '../values/settings' + +export default function isolate(funcs) { + const { mode } = settings + funcs.forEach((func) => { + try { + func() + } catch (error_) { + if (mode < 0) throw error_ + advise( + `Error in setup function\niframe-resizer detected an error during setup.\n\nPlease report the following error message at https://github.com/davidjbradshaw/iframe-resizer/issues`, + ) + error(error_) + } + }) +} diff --git a/packages/child/utils/map-settings.js b/packages/child/utils/map-settings.js new file mode 100644 index 000000000..34c2f7316 --- /dev/null +++ b/packages/child/utils/map-settings.js @@ -0,0 +1,7 @@ +import settings from '../values/settings' + +export default function map2settings(data) { + for (const [key, value] of Object.entries(data)) { + if (value) settings[key] = value + } +} diff --git a/packages/child/values/state.js b/packages/child/values/state.js index f59650959..a79a8b15f 100644 --- a/packages/child/values/state.js +++ b/packages/child/values/state.js @@ -1,4 +1,5 @@ export default { + applySelectors: null, firstRun: true, hasTags: false, hasOverflow: false, From 89b0651a00fa6759b3517fb50e8dc17348865689 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Mon, 3 Nov 2025 15:14:25 +0000 Subject: [PATCH 29/38] Extract Observed and Init --- packages/child/index.js | 254 +------------------------------ packages/child/init.js | 111 ++++++++++++++ packages/child/methods/index.js | 6 +- packages/child/observed/index.js | 155 +++++++++++++++++++ packages/child/values/state.js | 1 + 5 files changed, 275 insertions(+), 252 deletions(-) create mode 100644 packages/child/init.js diff --git a/packages/child/index.js b/packages/child/index.js index fa0a64681..7c49f76cc 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -2,279 +2,35 @@ import { HIGHLIGHT } from 'auto-console-group' import { EVENT_CANCEL_TIMER, - HEIGHT_EDGE, INIT, MESSAGE, MESSAGE_ID, MESSAGE_ID_LENGTH, - MUTATION_OBSERVER, - OVERFLOW_OBSERVER, PAGE_INFO, PARENT_INFO, PARENT_RESIZE_REQUEST, READY_STATE_CHANGE, - RESIZE_OBSERVER, SEPARATOR, UNDEFINED, - VERSION, - VISIBILITY_OBSERVER, - WIDTH_EDGE, } from '../common/consts' -import { getElementName, id, isolateUserCode, once } from '../common/utils' -import checkBlockingCSS from './check/blocking-css' -import checkBoth from './check/both' -import { checkHeightMode, checkWidthMode } from './check/calculation-mode' -import checkCrossDomain from './check/cross-domain' -import checkDeprecatedAttrs from './check/deprecated-attributes' -import checkIgnoredElements from './check/ignored-elements' -import checkMode from './check/mode' -import checkOverflow from './check/overflow' -import checkQuirksMode from './check/quirks-mode' -import checkReadyYet from './check/ready' -import checkSettings from './check/settings' -import checkAndSetupTags from './check/tags' -import checkVersion from './check/version' +import { isolateUserCode } from '../common/utils' import { - endAutoGroup, errorBoundary, event as consoleEvent, - info, label, log, - setConsoleOptions, warn, } from './console' -import { - addEventListener, - removeEventListener, - tearDownList, -} from './events/listeners' -import setupMouseEvents from './events/mouse' -import setupOnPageHide from './events/page-hide' -import setupPrintListeners from './events/print' +import { addEventListener, removeEventListener } from './events/listeners' import ready from './events/ready' -import setupPublicMethods from './methods' -import createMutationObserver from './observers/mutation' -import createOverflowObserver from './observers/overflow' -import createPerformanceObserver from './observers/perf' -import createResizeObserver from './observers/resize' -import createVisibilityObserver from './observers/visibility' -import createApplySelectors from './page/apply-selectors' -import injectClearFixIntoBodyElement from './page/clear-fix' -import { setBodyStyle, setMargin } from './page/css' -import setupInPageLinks from './page/links' +import init from './init' import { triggerReset } from './page/reset' -import stopInfiniteResizingOfIframe from './page/stop-infinite-resizing' -import readDataFromPage from './read/from-page' -import readDataFromParent from './read/from-parent' import sendMessage from './send/message' import sendSize from './send/size' -import sendTitle from './send/title' -import { getAllElements } from './size/all' -import isolate from './utils/isolate' -import map2settings from './utils/map-settings' import settings from './values/settings' import state from './values/state' function iframeResizerChild() { - let win = window - - function startLogging({ logExpand, logging, parentId }) { - setConsoleOptions({ id: parentId, enabled: logging, expand: logExpand }) - consoleEvent('initReceived') - log(`Initialising iframe v${VERSION} ${window.location.href}`) - } - - function init(data) { - if (!state.firstRun) return - map2settings(readDataFromParent(data)) - startLogging(settings) - map2settings(readDataFromPage()) - // debug({ ...settings }) - - const { bodyBackground, bodyPadding, inPageLinks, onReady } = settings - const bothDirections = checkBoth(settings) - - state.applySelectors = createApplySelectors(settings) - - const setup = [ - () => checkVersion(settings), - () => checkMode(settings), - checkIgnoredElements, - checkCrossDomain, - checkHeightMode, - checkWidthMode, - checkDeprecatedAttrs, - checkQuirksMode, - checkAndSetupTags, - checkSettings, - bothDirections ? id : checkBlockingCSS, - - () => setMargin(settings), - () => setBodyStyle('background', bodyBackground), - () => setBodyStyle('padding', bodyPadding), - - bothDirections ? id : stopInfiniteResizingOfIframe, - injectClearFixIntoBodyElement, - - state.applySelectors, - attachObservers, - - () => setupInPageLinks(inPageLinks), - setupPrintListeners, - () => setupMouseEvents(settings), - setupOnPageHide, - () => setupPublicMethods(win), - ] - - isolate(setup) - - checkReadyYet(once(onReady)) - log('Initialization complete', settings) - endAutoGroup() - - sendSize( - INIT, - 'Init message from host page', - undefined, - undefined, - `${VERSION}:${settings.mode}`, - ) - - sendTitle() - } - - let overflowObserver - let resizeObserver - - function overflowObserved() { - const { hasOverflow } = state - const { hasOverflowUpdated, overflowedNodeSet } = checkOverflow() - - switch (true) { - case !hasOverflowUpdated: - return - - case overflowedNodeSet.size > 1: - info('Overflowed Elements:', overflowedNodeSet) - break - - case hasOverflow: - break - - default: - info('No overflow detected') - } - - sendSize(OVERFLOW_OBSERVER, 'Overflow updated') - } - - function createOverflowObservers(nodeList) { - const overflowObserverOptions = { - root: document.documentElement, - side: settings.calculateHeight ? HEIGHT_EDGE : WIDTH_EDGE, - } - - overflowObserver = createOverflowObserver( - overflowObserved, - overflowObserverOptions, - ) - - overflowObserver.attachObservers(nodeList) - - return overflowObserver - } - - function resizeObserved(entries) { - if (!Array.isArray(entries) || entries.length === 0) return - const el = entries[0].target - sendSize(RESIZE_OBSERVER, `Element resized <${getElementName(el)}>`) - } - - function createResizeObservers(nodeList) { - resizeObserver = createResizeObserver(resizeObserved) - resizeObserver.attachObserverToNonStaticElements(nodeList) - return resizeObserver - } - - function visibilityChange(isVisible) { - log(`Visible: %c${isVisible}`, HIGHLIGHT) - state.isHidden = !isVisible - sendSize(VISIBILITY_OBSERVER, 'Visibility changed') - } - - const getCombinedElementLists = (nodeList) => { - const elements = new Set() - - for (const node of nodeList) { - elements.add(node) - for (const element of getAllElements(node)) elements.add(element) - } - - info(`Inspecting:\n`, elements) - return elements - } - - const addObservers = (nodeList) => { - if (nodeList.size === 0) return - - consoleEvent('addObservers') - - const elements = getCombinedElementLists(nodeList) - - overflowObserver.attachObservers(elements) - resizeObserver.attachObserverToNonStaticElements(elements) - - endAutoGroup() - } - - const removeObservers = (nodeList) => { - if (nodeList.size === 0) return - - consoleEvent('removeObservers') - - const elements = getCombinedElementLists(nodeList) - - overflowObserver.detachObservers(elements) - resizeObserver.detachObservers(elements) - - endAutoGroup() - } - - function contentMutated({ addedNodes, removedNodes }) { - consoleEvent('contentMutated') - state.applySelectors() - checkAndSetupTags() - checkOverflow() - endAutoGroup() - - removeObservers(removedNodes) - addObservers(addedNodes) - } - - function mutationObserved(mutations) { - contentMutated(mutations) - sendSize(MUTATION_OBSERVER, 'Mutation Observed') - } - - function pushDisconnectsOnToTearDown(observers) { - tearDownList.push(...observers.map((observer) => observer.disconnect)) - } - - function attachObservers() { - const nodeList = getAllElements(document.documentElement) - - const observers = [ - createMutationObserver(mutationObserved), - createOverflowObservers(nodeList), - createPerformanceObserver(), - createResizeObservers(nodeList), - createVisibilityObserver(visibilityChange), - ] - - pushDisconnectsOnToTearDown(observers) - } - let initLock = true function receiver(event) { consoleEvent('onMessage') @@ -431,13 +187,13 @@ function iframeResizerChild() { /* TEST CODE START */ function mockMsgListener(msgObject) { received(msgObject) - return win + return state.win } try { // eslint-disable-next-line no-restricted-globals if (top?.document?.getElementById('banner')) { - win = {} + state.win = {} // Create test hooks window.mockMsgListener = mockMsgListener diff --git a/packages/child/init.js b/packages/child/init.js new file mode 100644 index 000000000..1969737eb --- /dev/null +++ b/packages/child/init.js @@ -0,0 +1,111 @@ +import { INIT, VERSION } from '../common/consts' +import { id, once } from '../common/utils' +import checkBlockingCSS from './check/blocking-css' +import checkBoth from './check/both' +import { checkHeightMode, checkWidthMode } from './check/calculation-mode' +import checkCrossDomain from './check/cross-domain' +import checkDeprecatedAttrs from './check/deprecated-attributes' +import checkIgnoredElements from './check/ignored-elements' +import checkMode from './check/mode' +import checkQuirksMode from './check/quirks-mode' +import checkReadyYet from './check/ready' +import checkSettings from './check/settings' +import checkAndSetupTags from './check/tags' +import checkVersion from './check/version' +import { + endAutoGroup, + event as consoleEvent, + log, + setConsoleOptions, +} from './console' +import setupMouseEvents from './events/mouse' +import setupOnPageHide from './events/page-hide' +import setupPrintListeners from './events/print' +import setupPublicMethods from './methods' +import attachObservers from './observed' +import createApplySelectors from './page/apply-selectors' +import injectClearFixIntoBodyElement from './page/clear-fix' +import { setBodyStyle, setMargin } from './page/css' +import setupInPageLinks from './page/links' +import stopInfiniteResizingOfIframe from './page/stop-infinite-resizing' +import readDataFromPage from './read/from-page' +import readDataFromParent from './read/from-parent' +import sendSize from './send/size' +import sendTitle from './send/title' +import isolate from './utils/isolate' +import map2settings from './utils/map-settings' +import settings from './values/settings' +import state from './values/state' + +function startLogging({ logExpand, logging, parentId }) { + setConsoleOptions({ id: parentId, enabled: logging, expand: logExpand }) + consoleEvent('initReceived') + log(`Initialising iframe v${VERSION} ${window.location.href}`) +} + +function startIframeResizer({ + bodyBackground, + bodyPadding, + inPageLinks, + onReady, +}) { + const bothDirections = checkBoth(settings) + + const setup = [ + () => checkVersion(settings), + () => checkMode(settings), + checkIgnoredElements, + checkCrossDomain, + checkHeightMode, + checkWidthMode, + checkDeprecatedAttrs, + checkQuirksMode, + checkAndSetupTags, + checkSettings, + bothDirections ? id : checkBlockingCSS, + + () => setMargin(settings), + () => setBodyStyle('background', bodyBackground), + () => setBodyStyle('padding', bodyPadding), + + bothDirections ? id : stopInfiniteResizingOfIframe, + injectClearFixIntoBodyElement, + + state.applySelectors, + attachObservers, + + () => setupInPageLinks(inPageLinks), + setupPrintListeners, + () => setupMouseEvents(settings), + setupOnPageHide, + () => setupPublicMethods(), + ] + + isolate(setup) + + checkReadyYet(once(onReady)) + log('Initialization complete', settings) + endAutoGroup() + + sendSize( + INIT, + 'Init message from host page', + undefined, + undefined, + `${VERSION}:${settings.mode}`, + ) + + sendTitle() +} + +export default function (data) { + if (!state.firstRun) return + + map2settings(readDataFromParent(data)) + startLogging(settings) + map2settings(readDataFromPage()) + + state.applySelectors = createApplySelectors(settings) + + startIframeResizer(settings) +} diff --git a/packages/child/methods/index.js b/packages/child/methods/index.js index 372a44ae3..9bcd26399 100644 --- a/packages/child/methods/index.js +++ b/packages/child/methods/index.js @@ -35,10 +35,10 @@ import sendSize from '../send/size' import settings from '../values/settings' import state from '../values/state' -export default function setupPublicMethods(win) { +export default function setupPublicMethods() { if (settings.mode === 1) return - win.parentIframe = Object.freeze({ + state.win.parentIframe = Object.freeze({ autoResize: (enable) => { typeAssert(enable, BOOLEAN, 'parentIframe.autoResize(enable) enable') const { autoResize, calculateHeight, calculateWidth } = settings @@ -210,5 +210,5 @@ export default function setupPublicMethods(win) { }, }) - win.parentIFrame = win.parentIframe + state.win.parentIFrame = state.win.parentIframe } diff --git a/packages/child/observed/index.js b/packages/child/observed/index.js index e69de29bb..04492c1a9 100644 --- a/packages/child/observed/index.js +++ b/packages/child/observed/index.js @@ -0,0 +1,155 @@ +import { HIGHLIGHT } from 'auto-console-group' + +import { + HEIGHT_EDGE, + MUTATION_OBSERVER, + OVERFLOW_OBSERVER, + RESIZE_OBSERVER, + VISIBILITY_OBSERVER, + WIDTH_EDGE, +} from '../../common/consts' +import { getElementName } from '../../common/utils' +import checkOverflow from '../check/overflow' +import checkAndSetupTags from '../check/tags' +import { endAutoGroup, event as consoleEvent, info, log } from '../console' +import { tearDownList } from '../events/listeners' +import createMutationObserver from '../observers/mutation' +import createOverflowObserver from '../observers/overflow' +import createPerformanceObserver from '../observers/perf' +import createResizeObserver from '../observers/resize' +import createVisibilityObserver from '../observers/visibility' +import sendSize from '../send/size' +import { getAllElements } from '../size/all' +import settings from '../values/settings' +import state from '../values/state' + +let overflowObserver +let resizeObserver + +function overflowObserved() { + const { hasOverflow } = state + const { hasOverflowUpdated, overflowedNodeSet } = checkOverflow() + + switch (true) { + case !hasOverflowUpdated: + return + + case overflowedNodeSet.size > 1: + info('Overflowed Elements:', overflowedNodeSet) + break + + case hasOverflow: + break + + default: + info('No overflow detected') + } + + sendSize(OVERFLOW_OBSERVER, 'Overflow updated') +} + +function createOverflowObservers(nodeList) { + const overflowObserverOptions = { + root: document.documentElement, + side: settings.calculateHeight ? HEIGHT_EDGE : WIDTH_EDGE, + } + + overflowObserver = createOverflowObserver( + overflowObserved, + overflowObserverOptions, + ) + + overflowObserver.attachObservers(nodeList) + + return overflowObserver +} + +function resizeObserved(entries) { + if (!Array.isArray(entries) || entries.length === 0) return + const el = entries[0].target + sendSize(RESIZE_OBSERVER, `Element resized <${getElementName(el)}>`) +} + +function createResizeObservers(nodeList) { + resizeObserver = createResizeObserver(resizeObserved) + resizeObserver.attachObserverToNonStaticElements(nodeList) + return resizeObserver +} + +function visibilityChange(isVisible) { + log(`Visible: %c${isVisible}`, HIGHLIGHT) + state.isHidden = !isVisible + sendSize(VISIBILITY_OBSERVER, 'Visibility changed') +} + +const getCombinedElementLists = (nodeList) => { + const elements = new Set() + + for (const node of nodeList) { + elements.add(node) + for (const element of getAllElements(node)) elements.add(element) + } + + info(`Inspecting:\n`, elements) + return elements +} + +const addObservers = (nodeList) => { + if (nodeList.size === 0) return + + consoleEvent('addObservers') + + const elements = getCombinedElementLists(nodeList) + + overflowObserver.attachObservers(elements) + resizeObserver.attachObserverToNonStaticElements(elements) + + endAutoGroup() +} + +const removeObservers = (nodeList) => { + if (nodeList.size === 0) return + + consoleEvent('removeObservers') + + const elements = getCombinedElementLists(nodeList) + + overflowObserver.detachObservers(elements) + resizeObserver.detachObservers(elements) + + endAutoGroup() +} + +function contentMutated({ addedNodes, removedNodes }) { + consoleEvent('contentMutated') + state.applySelectors() + checkAndSetupTags() + checkOverflow() + endAutoGroup() + + removeObservers(removedNodes) + addObservers(addedNodes) +} + +function mutationObserved(mutations) { + contentMutated(mutations) + sendSize(MUTATION_OBSERVER, 'Mutation Observed') +} + +function pushDisconnectsOnToTearDown(observers) { + tearDownList.push(...observers.map((observer) => observer.disconnect)) +} + +export default function attachObservers() { + const nodeList = getAllElements(document.documentElement) + + const observers = [ + createMutationObserver(mutationObserved), + createOverflowObservers(nodeList), + createPerformanceObserver(), + createResizeObservers(nodeList), + createVisibilityObserver(visibilityChange), + ] + + pushDisconnectsOnToTearDown(observers) +} diff --git a/packages/child/values/state.js b/packages/child/values/state.js index a79a8b15f..42b4877ea 100644 --- a/packages/child/values/state.js +++ b/packages/child/values/state.js @@ -11,6 +11,7 @@ export default { taggedElements: [], target: window?.parent, triggerLocked: false, + win: window, onPageInfo: null, onParentInfo: null, } From 9f28efa9c9728d9484f964dba146b9ad82f275b0 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Mon, 3 Nov 2025 15:18:11 +0000 Subject: [PATCH 30/38] extract received --- packages/child/index.js | 171 +------------------------------ packages/child/received/index.js | 167 ++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+), 168 deletions(-) create mode 100644 packages/child/received/index.js diff --git a/packages/child/index.js b/packages/child/index.js index 7c49f76cc..668af8eb8 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -1,176 +1,11 @@ -import { HIGHLIGHT } from 'auto-console-group' - -import { - EVENT_CANCEL_TIMER, - INIT, - MESSAGE, - MESSAGE_ID, - MESSAGE_ID_LENGTH, - PAGE_INFO, - PARENT_INFO, - PARENT_RESIZE_REQUEST, - READY_STATE_CHANGE, - SEPARATOR, - UNDEFINED, -} from '../common/consts' -import { isolateUserCode } from '../common/utils' -import { - errorBoundary, - event as consoleEvent, - label, - log, - warn, -} from './console' +import { MESSAGE, READY_STATE_CHANGE, UNDEFINED } from '../common/consts' +import { event as consoleEvent, warn } from './console' import { addEventListener, removeEventListener } from './events/listeners' import ready from './events/ready' -import init from './init' -import { triggerReset } from './page/reset' -import sendMessage from './send/message' -import sendSize from './send/size' -import settings from './values/settings' +import received from './received' import state from './values/state' function iframeResizerChild() { - let initLock = true - function receiver(event) { - consoleEvent('onMessage') - const { freeze } = Object - const { parse } = JSON - const parseFrozen = (data) => freeze(parse(data)) - - const notExpected = (type) => sendMessage(0, 0, `${type}Stop`) - - const processRequestFromParent = { - init: function initFromParent() { - if (document.readyState === 'loading') { - log('Page not ready, ignoring init message') - return - } - - const data = event.data.slice(MESSAGE_ID_LENGTH).split(SEPARATOR) - - state.target = event.source - state.origin = event.origin - - init(data) - - state.firstRun = false - - setTimeout(() => { - initLock = false - }, EVENT_CANCEL_TIMER) - }, - - reset() { - if (initLock) { - log('Page reset ignored by init') - return - } - - log('Page size reset by host page') - triggerReset('resetPage') - }, - - resize() { - // This method is used by the tabVisible event on the parent page - log('Resize requested by host page') - sendSize(PARENT_RESIZE_REQUEST, 'Parent window requested size check') - }, - - moveToAnchor() { - state.inPageLinks.findTarget(getData()) - }, - - inPageLink() { - this.moveToAnchor() - }, // Backward compatibility - - pageInfo() { - const { onPageInfo } = state - const msgBody = getData() - log(`PageInfo received from parent:`, parseFrozen(msgBody)) - if (onPageInfo) { - isolateUserCode(onPageInfo, parse(msgBody)) - } else { - notExpected(PAGE_INFO) - } - }, - - parentInfo() { - const { onParentInfo } = state - const msgBody = parseFrozen(getData()) - log(`ParentInfo received from parent:`, msgBody) - if (onParentInfo) { - isolateUserCode(onParentInfo, msgBody) - } else { - notExpected(PARENT_INFO) - } - }, - - message() { - const msgBody = getData() - log(`onMessage called from parent:%c`, HIGHLIGHT, parseFrozen(msgBody)) - // eslint-disable-next-line sonarjs/no-extra-arguments - isolateUserCode(settings.onMessage, parse(msgBody)) - }, - } - - const isMessageForUs = () => - MESSAGE_ID === `${event.data}`.slice(0, MESSAGE_ID_LENGTH) - - const getMessageType = () => event.data.split(']')[1].split(SEPARATOR)[0] - - const getData = () => event.data.slice(event.data.indexOf(SEPARATOR) + 1) - - const isMiddleTier = () => - 'iframeResize' in window || - (window.jQuery !== undefined && '' in window.jQuery.prototype) - - // Test if this message is from a child below us. This is an ugly test, however, updating - // the message format would break backwards compatibility. - const isInitMsg = () => - event.data.split(SEPARATOR)[2] in { true: 1, false: 1 } - - function callFromParent() { - const messageType = getMessageType() - - consoleEvent(messageType) - - if (messageType in processRequestFromParent) { - processRequestFromParent[messageType]() - return - } - - if (!isMiddleTier() && !isInitMsg()) { - warn(`Unexpected message (${event.data})`) - } - } - - function processMessage() { - if (state.firstRun === false) { - callFromParent() - return - } - - if (isInitMsg()) { - label(getMessageType()) - consoleEvent(INIT) - processRequestFromParent.init() - return - } - - log( - `Ignored message of type "${getMessageType()}". Received before initialization.`, - ) - } - - if (isMessageForUs()) { - processMessage() - } - } - - const received = errorBoundary(receiver) - if ('iframeChildListener' in window) { warn('Already setup') } else { diff --git a/packages/child/received/index.js b/packages/child/received/index.js new file mode 100644 index 000000000..674bf9c3f --- /dev/null +++ b/packages/child/received/index.js @@ -0,0 +1,167 @@ +import { HIGHLIGHT } from 'auto-console-group' + +import { + EVENT_CANCEL_TIMER, + INIT, + MESSAGE_ID, + MESSAGE_ID_LENGTH, + PAGE_INFO, + PARENT_INFO, + PARENT_RESIZE_REQUEST, + SEPARATOR, +} from '../../common/consts' +import { isolateUserCode } from '../../common/utils' +import { + errorBoundary, + event as consoleEvent, + label, + log, + warn, +} from '../console' +import init from '../init' +import { triggerReset } from '../page/reset' +import sendMessage from '../send/message' +import sendSize from '../send/size' +import settings from '../values/settings' +import state from '../values/state' + +let initLock = true + +function receiver(event) { + consoleEvent('onMessage') + const { freeze } = Object + const { parse } = JSON + const parseFrozen = (data) => freeze(parse(data)) + + const notExpected = (type) => sendMessage(0, 0, `${type}Stop`) + + const processRequestFromParent = { + init: function initFromParent() { + if (document.readyState === 'loading') { + log('Page not ready, ignoring init message') + return + } + + const data = event.data.slice(MESSAGE_ID_LENGTH).split(SEPARATOR) + + state.target = event.source + state.origin = event.origin + + init(data) + + state.firstRun = false + + setTimeout(() => { + initLock = false + }, EVENT_CANCEL_TIMER) + }, + + reset() { + if (initLock) { + log('Page reset ignored by init') + return + } + + log('Page size reset by host page') + triggerReset('resetPage') + }, + + resize() { + // This method is used by the tabVisible event on the parent page + log('Resize requested by host page') + sendSize(PARENT_RESIZE_REQUEST, 'Parent window requested size check') + }, + + moveToAnchor() { + state.inPageLinks.findTarget(getData()) + }, + + inPageLink() { + this.moveToAnchor() + }, // Backward compatibility + + pageInfo() { + const { onPageInfo } = state + const msgBody = getData() + log(`PageInfo received from parent:`, parseFrozen(msgBody)) + if (onPageInfo) { + isolateUserCode(onPageInfo, parse(msgBody)) + } else { + notExpected(PAGE_INFO) + } + }, + + parentInfo() { + const { onParentInfo } = state + const msgBody = parseFrozen(getData()) + log(`ParentInfo received from parent:`, msgBody) + if (onParentInfo) { + isolateUserCode(onParentInfo, msgBody) + } else { + notExpected(PARENT_INFO) + } + }, + + message() { + const msgBody = getData() + log(`onMessage called from parent:%c`, HIGHLIGHT, parseFrozen(msgBody)) + // eslint-disable-next-line sonarjs/no-extra-arguments + isolateUserCode(settings.onMessage, parse(msgBody)) + }, + } + + const isMessageForUs = () => + MESSAGE_ID === `${event.data}`.slice(0, MESSAGE_ID_LENGTH) + + const getMessageType = () => event.data.split(']')[1].split(SEPARATOR)[0] + + const getData = () => event.data.slice(event.data.indexOf(SEPARATOR) + 1) + + const isMiddleTier = () => + 'iframeResize' in window || + (window.jQuery !== undefined && '' in window.jQuery.prototype) + + // Test if this message is from a child below us. This is an ugly test, however, updating + // the message format would break backwards compatibility. + const isInitMsg = () => + event.data.split(SEPARATOR)[2] in { true: 1, false: 1 } + + function callFromParent() { + const messageType = getMessageType() + + consoleEvent(messageType) + + if (messageType in processRequestFromParent) { + processRequestFromParent[messageType]() + return + } + + if (!isMiddleTier() && !isInitMsg()) { + warn(`Unexpected message (${event.data})`) + } + } + + function processMessage() { + if (state.firstRun === false) { + callFromParent() + return + } + + if (isInitMsg()) { + label(getMessageType()) + consoleEvent(INIT) + processRequestFromParent.init() + return + } + + log( + `Ignored message of type "${getMessageType()}". Received before initialization.`, + ) + } + + if (isMessageForUs()) { + processMessage() + } +} + +export default errorBoundary(receiver) From 03cee85df42854c8a7a4df4d5de7b9ac2ac32f61 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Mon, 3 Nov 2025 15:58:22 +0000 Subject: [PATCH 31/38] Tidy Received --- packages/child/index.js | 17 ++- packages/child/received/index.js | 167 +++++---------------- packages/child/received/process-request.js | 109 ++++++++++++++ 3 files changed, 153 insertions(+), 140 deletions(-) create mode 100644 packages/child/received/process-request.js diff --git a/packages/child/index.js b/packages/child/index.js index 668af8eb8..c94069483 100644 --- a/packages/child/index.js +++ b/packages/child/index.js @@ -8,16 +8,17 @@ import state from './values/state' function iframeResizerChild() { if ('iframeChildListener' in window) { warn('Already setup') - } else { - window.iframeChildListener = (data) => - setTimeout(() => received({ data, sameOrigin: true })) + return + } - consoleEvent('listen') - addEventListener(window, MESSAGE, received) - addEventListener(document, READY_STATE_CHANGE, ready) + window.iframeChildListener = (data) => + setTimeout(() => received({ data, sameOrigin: true })) - ready() - } + consoleEvent('listen') + addEventListener(window, MESSAGE, received) + addEventListener(document, READY_STATE_CHANGE, ready) + + ready() /* TEST CODE START */ function mockMsgListener(msgObject) { diff --git a/packages/child/received/index.js b/packages/child/received/index.js index 674bf9c3f..843d8c3c4 100644 --- a/packages/child/received/index.js +++ b/packages/child/received/index.js @@ -1,16 +1,9 @@ -import { HIGHLIGHT } from 'auto-console-group' - import { - EVENT_CANCEL_TIMER, INIT, MESSAGE_ID, MESSAGE_ID_LENGTH, - PAGE_INFO, - PARENT_INFO, - PARENT_RESIZE_REQUEST, SEPARATOR, } from '../../common/consts' -import { isolateUserCode } from '../../common/utils' import { errorBoundary, event as consoleEvent, @@ -18,149 +11,59 @@ import { log, warn, } from '../console' -import init from '../init' -import { triggerReset } from '../page/reset' -import sendMessage from '../send/message' -import sendSize from '../send/size' -import settings from '../values/settings' import state from '../values/state' +import processRequest from './process-request' -let initLock = true - -function receiver(event) { - consoleEvent('onMessage') - const { freeze } = Object - const { parse } = JSON - const parseFrozen = (data) => freeze(parse(data)) - - const notExpected = (type) => sendMessage(0, 0, `${type}Stop`) - - const processRequestFromParent = { - init: function initFromParent() { - if (document.readyState === 'loading') { - log('Page not ready, ignoring init message') - return - } - - const data = event.data.slice(MESSAGE_ID_LENGTH).split(SEPARATOR) +const isMessageForUs = (event) => + MESSAGE_ID === `${event.data}`.slice(0, MESSAGE_ID_LENGTH) - state.target = event.source - state.origin = event.origin +const getMessageType = (event) => event.data.split(']')[1].split(SEPARATOR)[0] - init(data) +const isMiddleTier = () => + 'iframeResize' in window || + (window.jQuery !== undefined && '' in window.jQuery.prototype) - state.firstRun = false +// Test if this message is from a child below us. This is an ugly test, however, updating +// the message format would break backwards compatibility. +const isInitMessage = (event) => + event.data.split(SEPARATOR)[2] in { true: 1, false: 1 } - setTimeout(() => { - initLock = false - }, EVENT_CANCEL_TIMER) - }, +function callFromParent(event) { + const messageType = getMessageType(event) - reset() { - if (initLock) { - log('Page reset ignored by init') - return - } + consoleEvent(messageType) - log('Page size reset by host page') - triggerReset('resetPage') - }, - - resize() { - // This method is used by the tabVisible event on the parent page - log('Resize requested by host page') - sendSize(PARENT_RESIZE_REQUEST, 'Parent window requested size check') - }, - - moveToAnchor() { - state.inPageLinks.findTarget(getData()) - }, - - inPageLink() { - this.moveToAnchor() - }, // Backward compatibility - - pageInfo() { - const { onPageInfo } = state - const msgBody = getData() - log(`PageInfo received from parent:`, parseFrozen(msgBody)) - if (onPageInfo) { - isolateUserCode(onPageInfo, parse(msgBody)) - } else { - notExpected(PAGE_INFO) - } - }, - - parentInfo() { - const { onParentInfo } = state - const msgBody = parseFrozen(getData()) - log(`ParentInfo received from parent:`, msgBody) - if (onParentInfo) { - isolateUserCode(onParentInfo, msgBody) - } else { - notExpected(PARENT_INFO) - } - }, - - message() { - const msgBody = getData() - log(`onMessage called from parent:%c`, HIGHLIGHT, parseFrozen(msgBody)) - // eslint-disable-next-line sonarjs/no-extra-arguments - isolateUserCode(settings.onMessage, parse(msgBody)) - }, + if (messageType in processRequest) { + processRequest[messageType](event) + return } - const isMessageForUs = () => - MESSAGE_ID === `${event.data}`.slice(0, MESSAGE_ID_LENGTH) - - const getMessageType = () => event.data.split(']')[1].split(SEPARATOR)[0] - - const getData = () => event.data.slice(event.data.indexOf(SEPARATOR) + 1) - - const isMiddleTier = () => - 'iframeResize' in window || - (window.jQuery !== undefined && '' in window.jQuery.prototype) - - // Test if this message is from a child below us. This is an ugly test, however, updating - // the message format would break backwards compatibility. - const isInitMsg = () => - event.data.split(SEPARATOR)[2] in { true: 1, false: 1 } - - function callFromParent() { - const messageType = getMessageType() + if (!isMiddleTier() && !isInitMessage(event)) { + warn(`Unexpected message (${event.data})`) + } +} - consoleEvent(messageType) +function receiver(event) { + consoleEvent('onMessage') - if (messageType in processRequestFromParent) { - processRequestFromParent[messageType]() + switch (true) { + case !isMessageForUs(event): return - } - - if (!isMiddleTier() && !isInitMsg()) { - warn(`Unexpected message (${event.data})`) - } - } - function processMessage() { - if (state.firstRun === false) { - callFromParent() + case state.firstRun === false: + callFromParent(event) return - } - if (isInitMsg()) { - label(getMessageType()) - consoleEvent(INIT) - processRequestFromParent.init() + case !isInitMessage(event): + log( + `Ignored message of type "${getMessageType()}". Received before initialization.`, + ) return - } - log( - `Ignored message of type "${getMessageType()}". Received before initialization.`, - ) - } - - if (isMessageForUs()) { - processMessage() + default: + label(getMessageType(event)) + consoleEvent(INIT) + processRequest.init(event) } } diff --git a/packages/child/received/process-request.js b/packages/child/received/process-request.js new file mode 100644 index 000000000..e2190883c --- /dev/null +++ b/packages/child/received/process-request.js @@ -0,0 +1,109 @@ +import { HIGHLIGHT } from 'auto-console-group' + +import { + EVENT_CANCEL_TIMER, + MESSAGE_ID_LENGTH, + PAGE_INFO, + PARENT_INFO, + PARENT_RESIZE_REQUEST, + SEPARATOR, +} from '../../common/consts' +import { isolateUserCode } from '../../common/utils' +import { log } from '../console' +import init from '../init' +import { triggerReset } from '../page/reset' +import sendMessage from '../send/message' +import sendSize from '../send/size' +import settings from '../values/settings' +import state from '../values/state' + +const { freeze } = Object +const { parse } = JSON + +const parseFrozen = (data) => freeze(parse(data)) +const notExpected = (type) => sendMessage(0, 0, `${type}Stop`) + +const getData = (event) => event.data.slice(event.data.indexOf(SEPARATOR) + 1) + +let initLock = true + +export default { + init: function initFromParent(event) { + if (document.readyState === 'loading') { + log('Page not ready, ignoring init message') + return + } + + const data = event.data.slice(MESSAGE_ID_LENGTH).split(SEPARATOR) + + state.target = event.source + state.origin = event.origin + + init(data) + + state.firstRun = false + + setTimeout(() => { + initLock = false + }, EVENT_CANCEL_TIMER) + }, + + reset() { + if (initLock) { + log('Page reset ignored by init') + return + } + + log('Page size reset by host page') + triggerReset('resetPage') + }, + + resize() { + // This method is used by the tabVisible event on the parent page + log('Resize requested by host page') + sendSize(PARENT_RESIZE_REQUEST, 'Parent window requested size check') + }, + + moveToAnchor(event) { + state.inPageLinks.findTarget(getData(event)) + }, + + inPageLink() { + this.moveToAnchor() + }, // Backward compatibility + + pageInfo(event) { + const { onPageInfo } = state + const msgBody = getData(event) + + log(`PageInfo received from parent:`, parseFrozen(msgBody)) + + if (onPageInfo) { + isolateUserCode(onPageInfo, parse(msgBody)) + } else { + notExpected(PAGE_INFO) + } + }, + + parentInfo(event) { + const { onParentInfo } = state + const msgBody = parseFrozen(getData(event)) + + log(`ParentInfo received from parent:`, msgBody) + + if (onParentInfo) { + isolateUserCode(onParentInfo, msgBody) + } else { + notExpected(PARENT_INFO) + } + }, + + message(event) { + const msgBody = getData(event) + + log(`onMessage called from parent:%c`, HIGHLIGHT, parseFrozen(msgBody)) + + // eslint-disable-next-line sonarjs/no-extra-arguments + isolateUserCode(settings.onMessage, parse(msgBody)) + }, +} From d8261b16f91c5612df15e93891ada025c0579aff Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Tue, 4 Nov 2025 13:19:13 +0000 Subject: [PATCH 32/38] Extract methods --- packages/child/methods/auto-resize.js | 30 +++ packages/child/methods/calculation-methods.js | 12 + packages/child/methods/index.js | 250 ++++-------------- packages/child/methods/move-to-anchor.js | 8 + packages/child/methods/offset-size.js | 11 + packages/child/methods/origin.js | 26 ++ packages/child/methods/page-info.js | 20 ++ packages/child/methods/parent-props.js | 26 ++ packages/child/methods/proxy.js | 35 +++ packages/child/methods/resize.js | 28 ++ packages/child/methods/scroll.js | 18 ++ packages/child/methods/send-message.js | 13 + 12 files changed, 273 insertions(+), 204 deletions(-) create mode 100644 packages/child/methods/auto-resize.js create mode 100644 packages/child/methods/calculation-methods.js create mode 100644 packages/child/methods/move-to-anchor.js create mode 100644 packages/child/methods/offset-size.js create mode 100644 packages/child/methods/origin.js create mode 100644 packages/child/methods/page-info.js create mode 100644 packages/child/methods/parent-props.js create mode 100644 packages/child/methods/proxy.js create mode 100644 packages/child/methods/resize.js create mode 100644 packages/child/methods/scroll.js create mode 100644 packages/child/methods/send-message.js diff --git a/packages/child/methods/auto-resize.js b/packages/child/methods/auto-resize.js new file mode 100644 index 000000000..8f78d4cbe --- /dev/null +++ b/packages/child/methods/auto-resize.js @@ -0,0 +1,30 @@ +import { AUTO_RESIZE, BOOLEAN, ENABLE, NONE } from '../../common/consts' +import { typeAssert } from '../../common/utils' +import { advise, event as consoleEvent } from '../console' +import sendMessage from '../send/message' +import sendSize from '../send/size' +import settings from '../values/settings' + +export default function autoResize(enable) { + typeAssert(enable, BOOLEAN, 'parentIframe.autoResize(enable) enable') + const { autoResize, calculateHeight, calculateWidth } = settings + + if (calculateWidth === false && calculateHeight === false) { + consoleEvent(ENABLE) + advise( + `Auto Resize can not be changed when direction is set to '${NONE}'.`, // or '${BOTH}' + ) + return false + } + + if (enable === true && autoResize === false) { + settings.autoResize = true + queueMicrotask(() => sendSize(ENABLE, 'Auto Resize enabled')) + } else if (enable === false && autoResize === true) { + settings.autoResize = false + } + + sendMessage(0, 0, AUTO_RESIZE, JSON.stringify(settings.autoResize)) + + return settings.autoResize +} diff --git a/packages/child/methods/calculation-methods.js b/packages/child/methods/calculation-methods.js new file mode 100644 index 000000000..72b172680 --- /dev/null +++ b/packages/child/methods/calculation-methods.js @@ -0,0 +1,12 @@ +import { checkHeightMode, checkWidthMode } from '../check/calculation-mode' +import settings from '../values/settings' + +export function setHeightCalculationMethod(heightCalculationMethod) { + settings.heightCalcMode = heightCalculationMethod + checkHeightMode() +} + +export function setWidthCalculationMethod(widthCalculationMethod) { + settings.widthCalcMode = widthCalculationMethod + checkWidthMode() +} diff --git a/packages/child/methods/index.js b/packages/child/methods/index.js index 9bcd26399..9a7942b98 100644 --- a/packages/child/methods/index.js +++ b/packages/child/methods/index.js @@ -1,214 +1,56 @@ -import { HIGHLIGHT } from 'auto-console-group' - -import { - AUTO_RESIZE, - BOOLEAN, - CLOSE, - ENABLE, - FUNCTION, - MANUAL_RESIZE_REQUEST, - MESSAGE, - NONE, - NUMBER, - PAGE_INFO, - PAGE_INFO_STOP, - PARENT_INFO, - PARENT_INFO_STOP, - SCROLL_BY, - SCROLL_TO, - SCROLL_TO_OFFSET, - SET_OFFSET_SIZE, - STRING, -} from '../../common/consts' -import { typeAssert } from '../../common/utils' -import { checkHeightMode, checkWidthMode } from '../check/calculation-mode' -import { - advise, - deprecateMethod, - deprecateMethodReplace, - event as consoleEvent, - log, -} from '../console' +import { CLOSE } from '../../common/consts' +import { warn } from '../console' import { resetIframe } from '../page/reset' -import sendMessage from '../send/message' -import sendSize from '../send/size' +import APIsendMessage from '../send/message' import settings from '../values/settings' import state from '../values/state' +import autoResize from './auto-resize' +import { + setHeightCalculationMethod, + setWidthCalculationMethod, +} from './calculation-methods' +import moveToAnchor from './move-to-anchor' +import setOffsetSize from './offset-size' +import { getOrigin, getParentOrigin, setTargetOrigin } from './origin' +import getPageInfo from './page-info' +import { getParentProperties, getParentProps } from './parent-props' +import deprecationProxy from './proxy' +import resize from './resize' +import { scrollBy, scrollTo, scrollToOffset } from './scroll' +import sendMessage from './send-message' + +const close = () => APIsendMessage(0, 0, CLOSE) +const getId = () => settings.parentId +const reset = () => resetIframe('parentIframe.reset') +const size = () => + warn('parentIframe.size() has been renamed parentIframe.resize()') export default function setupPublicMethods() { + const { win } = state // Required for old Karma tests if (settings.mode === 1) return - state.win.parentIframe = Object.freeze({ - autoResize: (enable) => { - typeAssert(enable, BOOLEAN, 'parentIframe.autoResize(enable) enable') - const { autoResize, calculateHeight, calculateWidth } = settings - - if (calculateWidth === false && calculateHeight === false) { - consoleEvent(ENABLE) - advise( - `Auto Resize can not be changed when direction is set to '${NONE}'.`, // or '${BOTH}' - ) - return false - } - - if (enable === true && autoResize === false) { - settings.autoResize = true - queueMicrotask(() => sendSize(ENABLE, 'Auto Resize enabled')) - } else if (enable === false && autoResize === true) { - settings.autoResize = false - } - - sendMessage(0, 0, AUTO_RESIZE, JSON.stringify(settings.autoResize)) - - return settings.autoResize - }, - - close() { - sendMessage(0, 0, CLOSE) - }, - - getId: () => settings.parentId, - - getOrigin: () => { - consoleEvent('getOrigin') - deprecateMethod('getOrigin()', 'getParentOrigin()') - return state.origin - }, - - getParentOrigin: () => state.origin, - - getPageInfo(callback) { - if (typeof callback === FUNCTION) { - state.onPageInfo = callback - sendMessage(0, 0, PAGE_INFO) - deprecateMethodReplace( - 'getPageInfo()', - 'getParentProps()', - 'See https://iframe-resizer.com/upgrade for details. ', - ) - return - } - - state.onPageInfo = null - sendMessage(0, 0, PAGE_INFO_STOP) - }, - - getParentProps(callback) { - typeAssert( - callback, - FUNCTION, - 'parentIframe.getParentProps(callback) callback', - ) - - state.onParentInfo = callback - sendMessage(0, 0, PARENT_INFO) - - return () => { - state.onParentInfo = null - sendMessage(0, 0, PARENT_INFO_STOP) - } - }, - - getParentProperties(callback) { - deprecateMethod('getParentProperties()', 'getParentProps()') - this.getParentProps(callback) - }, - - moveToAnchor(anchor) { - typeAssert(anchor, STRING, 'parentIframe.moveToAnchor(anchor) anchor') - state.inPageLinks.findTarget(anchor) - }, - - reset() { - resetIframe('parentIframe.reset') - }, - - setOffsetSize(newOffset) { - typeAssert(newOffset, NUMBER, 'parentIframe.setOffsetSize(offset) offset') - settings.offsetHeight = newOffset - settings.offsetWidth = newOffset - sendSize(SET_OFFSET_SIZE, `parentIframe.setOffsetSize(${newOffset})`) - }, - - scrollBy(x, y) { - typeAssert(x, NUMBER, 'parentIframe.scrollBy(x, y) x') - typeAssert(y, NUMBER, 'parentIframe.scrollBy(x, y) y') - sendMessage(y, x, SCROLL_BY) // X&Y reversed at sendMessage uses height/width - }, - - scrollTo(x, y) { - typeAssert(x, NUMBER, 'parentIframe.scrollTo(x, y) x') - typeAssert(y, NUMBER, 'parentIframe.scrollTo(x, y) y') - sendMessage(y, x, SCROLL_TO) // X&Y reversed at sendMessage uses height/width - }, - - scrollToOffset(x, y) { - typeAssert(x, NUMBER, 'parentIframe.scrollToOffset(x, y) x') - typeAssert(y, NUMBER, 'parentIframe.scrollToOffset(x, y) y') - sendMessage(y, x, SCROLL_TO_OFFSET) // X&Y reversed at sendMessage uses height/width - }, - - sendMessage(msg, targetOrigin) { - if (targetOrigin) - typeAssert( - targetOrigin, - STRING, - 'parentIframe.sendMessage(msg, targetOrigin) targetOrigin', - ) - sendMessage(0, 0, MESSAGE, JSON.stringify(msg), targetOrigin) - }, - - setHeightCalculationMethod(heightCalculationMethod) { - settings.heightCalcMode = heightCalculationMethod - checkHeightMode() - }, - - setWidthCalculationMethod(widthCalculationMethod) { - settings.widthCalcMode = widthCalculationMethod - checkWidthMode() - }, - - setTargetOrigin(targetOrigin) { - typeAssert( - targetOrigin, - STRING, - 'parentIframe.setTargetOrigin(targetOrigin) targetOrigin', - ) - - log(`Set targetOrigin: %c${targetOrigin}`, HIGHLIGHT) - settings.targetOrigin = targetOrigin - }, - - resize(customHeight, customWidth) { - if (customHeight !== undefined) - typeAssert( - customHeight, - NUMBER, - 'parentIframe.resize(customHeight, customWidth) customHeight', - ) - - if (customWidth !== undefined) - typeAssert( - customWidth, - NUMBER, - 'parentIframe.resize(customHeight, customWidth) customWidth', - ) - - const valString = `${customHeight || ''}${customWidth ? `,${customWidth}` : ''}` - - sendSize( - MANUAL_RESIZE_REQUEST, - `parentIframe.resize(${valString})`, - customHeight, - customWidth, - ) - }, - - size(customHeight, customWidth) { - deprecateMethod('size()', 'resize()') - this.resize(customHeight, customWidth) - }, + win.parentIframe = Object.freeze({ + autoResize, + close, + getId, + getOrigin, // TODO Remove in V6 + getParentOrigin, + getPageInfo, // TODO Remove in V6 + getParentProps, + getParentProperties, // TODO Remove in V6 + moveToAnchor, + reset, + setOffsetSize, + scrollBy, + scrollTo, + scrollToOffset, + sendMessage, + setHeightCalculationMethod, + setWidthCalculationMethod, + setTargetOrigin, + resize, + size, // TODO Remove in V6 }) - state.win.parentIFrame = state.win.parentIframe + win.parentIFrame = deprecationProxy(win.parentIframe) } diff --git a/packages/child/methods/move-to-anchor.js b/packages/child/methods/move-to-anchor.js new file mode 100644 index 000000000..f477f7e2f --- /dev/null +++ b/packages/child/methods/move-to-anchor.js @@ -0,0 +1,8 @@ +import { STRING } from '../../common/consts' +import { typeAssert } from '../../common/utils' +import state from '../values/state' + +export default function moveToAnchor(anchor) { + typeAssert(anchor, STRING, 'parentIframe.moveToAnchor(anchor) anchor') + state.inPageLinks.findTarget(anchor) +} diff --git a/packages/child/methods/offset-size.js b/packages/child/methods/offset-size.js new file mode 100644 index 000000000..22d0d2f86 --- /dev/null +++ b/packages/child/methods/offset-size.js @@ -0,0 +1,11 @@ +import { NUMBER, SET_OFFSET_SIZE } from '../../common/consts' +import { typeAssert } from '../../common/utils' +import sendSize from '../send/size' +import settings from '../values/settings' + +export default function setOffsetSize(newOffset) { + typeAssert(newOffset, NUMBER, 'parentIframe.setOffsetSize(offset) offset') + settings.offsetHeight = newOffset + settings.offsetWidth = newOffset + sendSize(SET_OFFSET_SIZE, `parentIframe.setOffsetSize(${newOffset})`) +} diff --git a/packages/child/methods/origin.js b/packages/child/methods/origin.js new file mode 100644 index 000000000..8d9ce3fef --- /dev/null +++ b/packages/child/methods/origin.js @@ -0,0 +1,26 @@ +import { HIGHLIGHT } from 'auto-console-group' + +import { STRING } from '../../common/consts' +import { typeAssert } from '../../common/utils' +import { deprecateMethod, event as consoleEvent, log } from '../console' +import settings from '../values/settings' +import state from '../values/state' + +export function getOrigin() { + consoleEvent('getOrigin') + deprecateMethod('getOrigin()', 'getParentOrigin()') + return state.origin +} + +export const getParentOrigin = () => state.origin + +export function setTargetOrigin(targetOrigin) { + typeAssert( + targetOrigin, + STRING, + 'parentIframe.setTargetOrigin(targetOrigin) targetOrigin', + ) + + log(`Set targetOrigin: %c${targetOrigin}`, HIGHLIGHT) + settings.targetOrigin = targetOrigin +} diff --git a/packages/child/methods/page-info.js b/packages/child/methods/page-info.js new file mode 100644 index 000000000..a20cfc59d --- /dev/null +++ b/packages/child/methods/page-info.js @@ -0,0 +1,20 @@ +import { FUNCTION, PAGE_INFO, PAGE_INFO_STOP } from '../../common/consts' +import { deprecateMethodReplace } from '../console' +import sendMessage from '../send/message' +import state from '../values/state' + +export default function getPageInfo(callback) { + if (typeof callback === FUNCTION) { + state.onPageInfo = callback + sendMessage(0, 0, PAGE_INFO) + deprecateMethodReplace( + 'getPageInfo()', + 'getParentProps()', + 'See https://iframe-resizer.com/upgrade for details. ', + ) + return + } + + state.onPageInfo = null + sendMessage(0, 0, PAGE_INFO_STOP) +} diff --git a/packages/child/methods/parent-props.js b/packages/child/methods/parent-props.js new file mode 100644 index 000000000..932d48a5a --- /dev/null +++ b/packages/child/methods/parent-props.js @@ -0,0 +1,26 @@ +import { FUNCTION, PARENT_INFO, PARENT_INFO_STOP } from '../../common/consts' +import { typeAssert } from '../../common/utils' +import { deprecateMethod } from '../console' +import sendMessage from '../send/message' +import state from '../values/state' + +export function getParentProps(callback) { + typeAssert( + callback, + FUNCTION, + 'parentIframe.getParentProps(callback) callback', + ) + + state.onParentInfo = callback + sendMessage(0, 0, PARENT_INFO) + + return () => { + state.onParentInfo = null + sendMessage(0, 0, PARENT_INFO_STOP) + } +} + +export function getParentProperties(callback) { + deprecateMethod('getParentProperties()', 'getParentProps()') + this.getParentProps(callback) +} diff --git a/packages/child/methods/proxy.js b/packages/child/methods/proxy.js new file mode 100644 index 000000000..13f0cdb31 --- /dev/null +++ b/packages/child/methods/proxy.js @@ -0,0 +1,35 @@ +import { advise } from '../console' + +const oldObjectName = (prop) => + `Deprecated API object name +The window.parentIFrame object has been renamed to window.parentIframe. Please update your code as the old object will be removed in a future version. + +Called property: '${String(prop)}' +` + +export default function deprecationProxy(target) { + const warnedProps = new Set() + + return new Proxy(target, { + get(target, prop) { + if (!warnedProps.has(prop)) { + advise(oldObjectName(prop)) + warnedProps.add(prop) + } + + const value = target[prop] + const descriptor = Object.getOwnPropertyDescriptor(target, prop) + + // If property is non-configurable and non-writable, return the actual value + if (descriptor && !descriptor.configurable && !descriptor.writable) { + return value + } + + if (typeof value === 'function') { + return value.bind(target) + } + + return value + }, + }) +} diff --git a/packages/child/methods/resize.js b/packages/child/methods/resize.js new file mode 100644 index 000000000..a0357b340 --- /dev/null +++ b/packages/child/methods/resize.js @@ -0,0 +1,28 @@ +import { MANUAL_RESIZE_REQUEST, NUMBER } from '../../common/consts' +import { typeAssert } from '../../common/utils' +import sendSize from '../send/size' + +export default function resize(customHeight, customWidth) { + if (customHeight !== undefined) + typeAssert( + customHeight, + NUMBER, + 'parentIframe.resize(customHeight, customWidth) customHeight', + ) + + if (customWidth !== undefined) + typeAssert( + customWidth, + NUMBER, + 'parentIframe.resize(customHeight, customWidth) customWidth', + ) + + const valString = `${customHeight || ''}${customWidth ? `,${customWidth}` : ''}` + + sendSize( + MANUAL_RESIZE_REQUEST, + `parentIframe.resize(${valString})`, + customHeight, + customWidth, + ) +} diff --git a/packages/child/methods/scroll.js b/packages/child/methods/scroll.js new file mode 100644 index 000000000..879c8e3aa --- /dev/null +++ b/packages/child/methods/scroll.js @@ -0,0 +1,18 @@ +import { + NUMBER, + SCROLL_BY, + SCROLL_TO, + SCROLL_TO_OFFSET, +} from '../../common/consts' +import { typeAssert } from '../../common/utils' +import sendMessage from '../send/message' + +const createScrollMethod = (TYPE) => (x, y) => { + typeAssert(x, NUMBER, `parentIframe.${TYPE}(x, y) x`) + typeAssert(y, NUMBER, `parentIframe.${TYPE}(x, y) y`) + sendMessage(y, x, TYPE) // X&Y reversed at sendMessage uses height/width +} + +export const scrollBy = createScrollMethod(SCROLL_BY) +export const scrollTo = createScrollMethod(SCROLL_TO) +export const scrollToOffset = createScrollMethod(SCROLL_TO_OFFSET) diff --git a/packages/child/methods/send-message.js b/packages/child/methods/send-message.js new file mode 100644 index 000000000..2ea648be9 --- /dev/null +++ b/packages/child/methods/send-message.js @@ -0,0 +1,13 @@ +import { MESSAGE, STRING } from '../../common/consts' +import { typeAssert } from '../../common/utils' +import sendMessage from '../send/message' + +export default function (msg, targetOrigin) { + if (targetOrigin) + typeAssert( + targetOrigin, + STRING, + 'parentIframe.sendMessage(msg, targetOrigin) targetOrigin', + ) + sendMessage(0, 0, MESSAGE, JSON.stringify(msg), targetOrigin) +} From 7250f4dd8e5d4057b88f566dcc141560194afcaf Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Tue, 4 Nov 2025 13:50:05 +0000 Subject: [PATCH 33/38] Refactor child received --- packages/child/init.js | 4 +- packages/child/received/index.js | 67 ++++++++------------------------ packages/child/received/is.js | 13 +++++++ 3 files changed, 32 insertions(+), 52 deletions(-) create mode 100644 packages/child/received/is.js diff --git a/packages/child/init.js b/packages/child/init.js index 1969737eb..771b23bc1 100644 --- a/packages/child/init.js +++ b/packages/child/init.js @@ -43,7 +43,7 @@ function startLogging({ logExpand, logging, parentId }) { log(`Initialising iframe v${VERSION} ${window.location.href}`) } -function startIframeResizer({ +function startIframeResizerChild({ bodyBackground, bodyPadding, inPageLinks, @@ -107,5 +107,5 @@ export default function (data) { state.applySelectors = createApplySelectors(settings) - startIframeResizer(settings) + startIframeResizerChild(settings) } diff --git a/packages/child/received/index.js b/packages/child/received/index.js index 843d8c3c4..9fb285c15 100644 --- a/packages/child/received/index.js +++ b/packages/child/received/index.js @@ -1,70 +1,37 @@ -import { - INIT, - MESSAGE_ID, - MESSAGE_ID_LENGTH, - SEPARATOR, -} from '../../common/consts' -import { - errorBoundary, - event as consoleEvent, - label, - log, - warn, -} from '../console' +import { SEPARATOR } from '../../common/consts' +import { errorBoundary, event as consoleEvent, warn } from '../console' import state from '../values/state' +import { isInitMessage, isMessageForUs, isMiddleTier } from './is' import processRequest from './process-request' -const isMessageForUs = (event) => - MESSAGE_ID === `${event.data}`.slice(0, MESSAGE_ID_LENGTH) - const getMessageType = (event) => event.data.split(']')[1].split(SEPARATOR)[0] -const isMiddleTier = () => - 'iframeResize' in window || - (window.jQuery !== undefined && '' in window.jQuery.prototype) - -// Test if this message is from a child below us. This is an ugly test, however, updating -// the message format would break backwards compatibility. -const isInitMessage = (event) => - event.data.split(SEPARATOR)[2] in { true: 1, false: 1 } - -function callFromParent(event) { +function receiver(event) { + const { firstRun } = state const messageType = getMessageType(event) consoleEvent(messageType) - if (messageType in processRequest) { - processRequest[messageType](event) - return - } - - if (!isMiddleTier() && !isInitMessage(event)) { - warn(`Unexpected message (${event.data})`) - } -} - -function receiver(event) { - consoleEvent('onMessage') - switch (true) { - case !isMessageForUs(event): - return + case !firstRun && messageType in processRequest: + processRequest[messageType](event) + break - case state.firstRun === false: - callFromParent(event) - return + case !firstRun && !isMiddleTier() && !isInitMessage(event): + warn(`Unexpected message (${event.data})`) + break case !isInitMessage(event): - log( - `Ignored message of type "${getMessageType()}". Received before initialization.`, + warn( + `Ignored message of type "${messageType}". Received before initialization.`, ) - return + break default: - label(getMessageType(event)) - consoleEvent(INIT) processRequest.init(event) } } -export default errorBoundary(receiver) +export default errorBoundary((event) => { + if (isMessageForUs(event)) receiver(event) +}) diff --git a/packages/child/received/is.js b/packages/child/received/is.js new file mode 100644 index 000000000..92890a211 --- /dev/null +++ b/packages/child/received/is.js @@ -0,0 +1,13 @@ +import { MESSAGE_ID, MESSAGE_ID_LENGTH, SEPARATOR } from '../../common/consts' + +export const isMessageForUs = (event) => + MESSAGE_ID === `${event.data}`.slice(0, MESSAGE_ID_LENGTH) + +export const isMiddleTier = () => + 'iframeResize' in window || + (window.jQuery !== undefined && '' in window.jQuery.prototype) + +// Test if this message is from a child below us. This is an ugly test, however, updating +// the message format would break backwards compatibility. +export const isInitMessage = (event) => + event.data.split(SEPARATOR)[2] in { true: 1, false: 1 } From 52d1a6f9dcf7892163d98e0a5382f07bebf6980c Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Tue, 4 Nov 2025 15:19:23 +0000 Subject: [PATCH 34/38] Breakup received --- packages/child/received/init.js | 28 +++++ packages/child/received/message.js | 15 +++ packages/child/received/page-info.js | 18 ++++ packages/child/received/parent-info.js | 18 ++++ packages/child/received/process-request.js | 120 +++------------------ packages/child/received/reset.js | 13 +++ packages/child/received/resize.js | 9 ++ packages/child/received/utils.js | 11 ++ packages/child/values/state.js | 1 + 9 files changed, 129 insertions(+), 104 deletions(-) create mode 100644 packages/child/received/init.js create mode 100644 packages/child/received/message.js create mode 100644 packages/child/received/page-info.js create mode 100644 packages/child/received/parent-info.js create mode 100644 packages/child/received/reset.js create mode 100644 packages/child/received/resize.js create mode 100644 packages/child/received/utils.js diff --git a/packages/child/received/init.js b/packages/child/received/init.js new file mode 100644 index 000000000..26a480178 --- /dev/null +++ b/packages/child/received/init.js @@ -0,0 +1,28 @@ +import { + EVENT_CANCEL_TIMER, + MESSAGE_ID_LENGTH, + SEPARATOR, +} from '../../common/consts' +import { log } from '../console' +import init from '../init' +import state from '../values/state' + +export default function initFromParent(event) { + if (document.readyState === 'loading') { + log('Page not ready, ignoring init message') + return + } + + const data = event.data.slice(MESSAGE_ID_LENGTH).split(SEPARATOR) + + state.target = event.source + state.origin = event.origin + + init(data) + + state.firstRun = false + + setTimeout(() => { + state.initLock = false + }, EVENT_CANCEL_TIMER) +} diff --git a/packages/child/received/message.js b/packages/child/received/message.js new file mode 100644 index 000000000..107c0db43 --- /dev/null +++ b/packages/child/received/message.js @@ -0,0 +1,15 @@ +import { HIGHLIGHT } from 'auto-console-group' + +import { isolateUserCode } from '../../common/utils' +import { log } from '../console' +import settings from '../values/settings' +import { getData, parse, parseFrozen } from './utils' + +export default function message(event) { + const msgBody = getData(event) + + log(`onMessage called from parent:%c`, HIGHLIGHT, parseFrozen(msgBody)) + + // eslint-disable-next-line sonarjs/no-extra-arguments + isolateUserCode(settings.onMessage, parse(msgBody)) +} diff --git a/packages/child/received/page-info.js b/packages/child/received/page-info.js new file mode 100644 index 000000000..db797a281 --- /dev/null +++ b/packages/child/received/page-info.js @@ -0,0 +1,18 @@ +import { PAGE_INFO } from '../../common/consts' +import { isolateUserCode } from '../../common/utils' +import { log } from '../console' +import state from '../values/state' +import { getData, notExpected, parse, parseFrozen } from './utils' + +export default function pageInfo(event) { + const { onPageInfo } = state + const msgBody = getData(event) + + log(`PageInfo received from parent:`, parseFrozen(msgBody)) + + if (onPageInfo) { + isolateUserCode(onPageInfo, parse(msgBody)) + } else { + notExpected(PAGE_INFO) + } +} diff --git a/packages/child/received/parent-info.js b/packages/child/received/parent-info.js new file mode 100644 index 000000000..6f388e84f --- /dev/null +++ b/packages/child/received/parent-info.js @@ -0,0 +1,18 @@ +import { PARENT_INFO } from '../../common/consts' +import { isolateUserCode } from '../../common/utils' +import { log } from '../console' +import state from '../values/state' +import { getData, notExpected, parseFrozen } from './utils' + +export default function parentInfo(event) { + const { onParentInfo } = state + const msgBody = parseFrozen(getData(event)) + + log(`ParentInfo received from parent:`, msgBody) + + if (onParentInfo) { + isolateUserCode(onParentInfo, msgBody) + } else { + notExpected(PARENT_INFO) + } +} diff --git a/packages/child/received/process-request.js b/packages/child/received/process-request.js index e2190883c..502f4124b 100644 --- a/packages/child/received/process-request.js +++ b/packages/child/received/process-request.js @@ -1,109 +1,21 @@ -import { HIGHLIGHT } from 'auto-console-group' - -import { - EVENT_CANCEL_TIMER, - MESSAGE_ID_LENGTH, - PAGE_INFO, - PARENT_INFO, - PARENT_RESIZE_REQUEST, - SEPARATOR, -} from '../../common/consts' -import { isolateUserCode } from '../../common/utils' -import { log } from '../console' -import init from '../init' -import { triggerReset } from '../page/reset' -import sendMessage from '../send/message' -import sendSize from '../send/size' -import settings from '../values/settings' import state from '../values/state' +import init from './init' +import message from './message' +import pageInfo from './page-info' +import parentInfo from './parent-info' +import reset from './reset' +import resize from './resize' +import { getData } from './utils' -const { freeze } = Object -const { parse } = JSON - -const parseFrozen = (data) => freeze(parse(data)) -const notExpected = (type) => sendMessage(0, 0, `${type}Stop`) - -const getData = (event) => event.data.slice(event.data.indexOf(SEPARATOR) + 1) - -let initLock = true +const moveToAnchor = (event) => state.inPageLinks.findTarget(getData(event)) export default { - init: function initFromParent(event) { - if (document.readyState === 'loading') { - log('Page not ready, ignoring init message') - return - } - - const data = event.data.slice(MESSAGE_ID_LENGTH).split(SEPARATOR) - - state.target = event.source - state.origin = event.origin - - init(data) - - state.firstRun = false - - setTimeout(() => { - initLock = false - }, EVENT_CANCEL_TIMER) - }, - - reset() { - if (initLock) { - log('Page reset ignored by init') - return - } - - log('Page size reset by host page') - triggerReset('resetPage') - }, - - resize() { - // This method is used by the tabVisible event on the parent page - log('Resize requested by host page') - sendSize(PARENT_RESIZE_REQUEST, 'Parent window requested size check') - }, - - moveToAnchor(event) { - state.inPageLinks.findTarget(getData(event)) - }, - - inPageLink() { - this.moveToAnchor() - }, // Backward compatibility - - pageInfo(event) { - const { onPageInfo } = state - const msgBody = getData(event) - - log(`PageInfo received from parent:`, parseFrozen(msgBody)) - - if (onPageInfo) { - isolateUserCode(onPageInfo, parse(msgBody)) - } else { - notExpected(PAGE_INFO) - } - }, - - parentInfo(event) { - const { onParentInfo } = state - const msgBody = parseFrozen(getData(event)) - - log(`ParentInfo received from parent:`, msgBody) - - if (onParentInfo) { - isolateUserCode(onParentInfo, msgBody) - } else { - notExpected(PARENT_INFO) - } - }, - - message(event) { - const msgBody = getData(event) - - log(`onMessage called from parent:%c`, HIGHLIGHT, parseFrozen(msgBody)) - - // eslint-disable-next-line sonarjs/no-extra-arguments - isolateUserCode(settings.onMessage, parse(msgBody)) - }, + init, + reset, + resize, + moveToAnchor, + inPageLink: moveToAnchor, // Backward compatibility + pageInfo, + parentInfo, + message, } diff --git a/packages/child/received/reset.js b/packages/child/received/reset.js new file mode 100644 index 000000000..6197a247d --- /dev/null +++ b/packages/child/received/reset.js @@ -0,0 +1,13 @@ +import { log } from '../console' +import { triggerReset } from '../page/reset' +import state from '../values/state' + +export default function reset() { + if (state.initLock) { + log('Page reset ignored by init') + return + } + + log('Page size reset by host page') + triggerReset('resetPage') +} diff --git a/packages/child/received/resize.js b/packages/child/received/resize.js new file mode 100644 index 000000000..5a8897635 --- /dev/null +++ b/packages/child/received/resize.js @@ -0,0 +1,9 @@ +import { PARENT_RESIZE_REQUEST } from '../../common/consts' +import { log } from '../console' +import sendSize from '../send/size' + +export default function resize() { + // This method is used by the tabVisible event on the parent page + log('Resize requested by host page') + sendSize(PARENT_RESIZE_REQUEST, 'Parent window requested size check') +} diff --git a/packages/child/received/utils.js b/packages/child/received/utils.js new file mode 100644 index 000000000..59da0804e --- /dev/null +++ b/packages/child/received/utils.js @@ -0,0 +1,11 @@ +import { SEPARATOR } from '../../common/consts' +import sendMessage from '../send/message' + +const { freeze } = Object +export const { parse } = JSON +export const parseFrozen = (data) => freeze(parse(data)) + +export const notExpected = (type) => sendMessage(0, 0, `${type}Stop`) + +export const getData = (event) => + event.data.slice(event.data.indexOf(SEPARATOR) + 1) diff --git a/packages/child/values/state.js b/packages/child/values/state.js index 42b4877ea..5960198b6 100644 --- a/packages/child/values/state.js +++ b/packages/child/values/state.js @@ -4,6 +4,7 @@ export default { hasTags: false, hasOverflow: false, isHidden: false, + initLock: true, inPageLinks: {}, origin: undefined, overflowedNodeSet: new Set(), From ad00afd018808a04c22c4586e4384d2cd0ddaa87 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Tue, 4 Nov 2025 15:58:54 +0000 Subject: [PATCH 35/38] Breakup Observers --- packages/child/observed/index.js | 136 +------------------------- packages/child/observed/mutation.js | 62 ++++++++++++ packages/child/observed/observers.js | 1 + packages/child/observed/overflow.js | 43 ++++++++ packages/child/observed/resize.js | 17 ++++ packages/child/observed/visibility.js | 12 +++ 6 files changed, 139 insertions(+), 132 deletions(-) create mode 100644 packages/child/observed/mutation.js create mode 100644 packages/child/observed/observers.js create mode 100644 packages/child/observed/overflow.js create mode 100644 packages/child/observed/resize.js create mode 100644 packages/child/observed/visibility.js diff --git a/packages/child/observed/index.js b/packages/child/observed/index.js index 04492c1a9..1520d6c53 100644 --- a/packages/child/observed/index.js +++ b/packages/child/observed/index.js @@ -1,140 +1,12 @@ -import { HIGHLIGHT } from 'auto-console-group' - -import { - HEIGHT_EDGE, - MUTATION_OBSERVER, - OVERFLOW_OBSERVER, - RESIZE_OBSERVER, - VISIBILITY_OBSERVER, - WIDTH_EDGE, -} from '../../common/consts' -import { getElementName } from '../../common/utils' -import checkOverflow from '../check/overflow' -import checkAndSetupTags from '../check/tags' -import { endAutoGroup, event as consoleEvent, info, log } from '../console' import { tearDownList } from '../events/listeners' import createMutationObserver from '../observers/mutation' -import createOverflowObserver from '../observers/overflow' import createPerformanceObserver from '../observers/perf' -import createResizeObserver from '../observers/resize' import createVisibilityObserver from '../observers/visibility' -import sendSize from '../send/size' import { getAllElements } from '../size/all' -import settings from '../values/settings' -import state from '../values/state' - -let overflowObserver -let resizeObserver - -function overflowObserved() { - const { hasOverflow } = state - const { hasOverflowUpdated, overflowedNodeSet } = checkOverflow() - - switch (true) { - case !hasOverflowUpdated: - return - - case overflowedNodeSet.size > 1: - info('Overflowed Elements:', overflowedNodeSet) - break - - case hasOverflow: - break - - default: - info('No overflow detected') - } - - sendSize(OVERFLOW_OBSERVER, 'Overflow updated') -} - -function createOverflowObservers(nodeList) { - const overflowObserverOptions = { - root: document.documentElement, - side: settings.calculateHeight ? HEIGHT_EDGE : WIDTH_EDGE, - } - - overflowObserver = createOverflowObserver( - overflowObserved, - overflowObserverOptions, - ) - - overflowObserver.attachObservers(nodeList) - - return overflowObserver -} - -function resizeObserved(entries) { - if (!Array.isArray(entries) || entries.length === 0) return - const el = entries[0].target - sendSize(RESIZE_OBSERVER, `Element resized <${getElementName(el)}>`) -} - -function createResizeObservers(nodeList) { - resizeObserver = createResizeObserver(resizeObserved) - resizeObserver.attachObserverToNonStaticElements(nodeList) - return resizeObserver -} - -function visibilityChange(isVisible) { - log(`Visible: %c${isVisible}`, HIGHLIGHT) - state.isHidden = !isVisible - sendSize(VISIBILITY_OBSERVER, 'Visibility changed') -} - -const getCombinedElementLists = (nodeList) => { - const elements = new Set() - - for (const node of nodeList) { - elements.add(node) - for (const element of getAllElements(node)) elements.add(element) - } - - info(`Inspecting:\n`, elements) - return elements -} - -const addObservers = (nodeList) => { - if (nodeList.size === 0) return - - consoleEvent('addObservers') - - const elements = getCombinedElementLists(nodeList) - - overflowObserver.attachObservers(elements) - resizeObserver.attachObserverToNonStaticElements(elements) - - endAutoGroup() -} - -const removeObservers = (nodeList) => { - if (nodeList.size === 0) return - - consoleEvent('removeObservers') - - const elements = getCombinedElementLists(nodeList) - - overflowObserver.detachObservers(elements) - resizeObserver.detachObservers(elements) - - endAutoGroup() -} - -function contentMutated({ addedNodes, removedNodes }) { - consoleEvent('contentMutated') - state.applySelectors() - checkAndSetupTags() - checkOverflow() - endAutoGroup() - - removeObservers(removedNodes) - addObservers(addedNodes) -} - -function mutationObserved(mutations) { - contentMutated(mutations) - sendSize(MUTATION_OBSERVER, 'Mutation Observed') -} +import mutationObserved from './mutation' +import createOverflowObservers from './overflow' +import createResizeObservers from './resize' +import visibilityChange from './visibility' function pushDisconnectsOnToTearDown(observers) { tearDownList.push(...observers.map((observer) => observer.disconnect)) diff --git a/packages/child/observed/mutation.js b/packages/child/observed/mutation.js new file mode 100644 index 000000000..e7adf330c --- /dev/null +++ b/packages/child/observed/mutation.js @@ -0,0 +1,62 @@ +import { MUTATION_OBSERVER } from '../../common/consts' +import checkOverflow from '../check/overflow' +import checkAndSetupTags from '../check/tags' +import { endAutoGroup, event as consoleEvent, info } from '../console' +import sendSize from '../send/size' +import { getAllElements } from '../size/all' +import state from '../values/state' +import observers from './observers' + +const getCombinedElementLists = (nodeList) => { + const elements = new Set() + + for (const node of nodeList) { + elements.add(node) + for (const element of getAllElements(node)) elements.add(element) + } + + info(`Inspecting:\n`, elements) + return elements +} + +const addObservers = (nodeList) => { + if (nodeList.size === 0) return + + consoleEvent('addObservers') + + const elements = getCombinedElementLists(nodeList) + + observers.overflow.attachObservers(elements) + observers.resize.attachObserverToNonStaticElements(elements) + + endAutoGroup() +} + +const removeObservers = (nodeList) => { + if (nodeList.size === 0) return + + consoleEvent('removeObservers') + + const elements = getCombinedElementLists(nodeList) + + observers.overflow.detachObservers(elements) + observers.resize.detachObservers(elements) + + endAutoGroup() +} + +function contentMutated({ addedNodes, removedNodes }) { + consoleEvent('contentMutated') + state.applySelectors() + checkAndSetupTags() + checkOverflow() + endAutoGroup() + + removeObservers(removedNodes) + addObservers(addedNodes) +} + +export default function mutationObserved(mutations) { + contentMutated(mutations) + sendSize(MUTATION_OBSERVER, 'Mutation Observed') +} diff --git a/packages/child/observed/observers.js b/packages/child/observed/observers.js new file mode 100644 index 000000000..b1c6ea436 --- /dev/null +++ b/packages/child/observed/observers.js @@ -0,0 +1 @@ +export default {} diff --git a/packages/child/observed/overflow.js b/packages/child/observed/overflow.js new file mode 100644 index 000000000..fbc855f61 --- /dev/null +++ b/packages/child/observed/overflow.js @@ -0,0 +1,43 @@ +import { HEIGHT_EDGE, OVERFLOW_OBSERVER, WIDTH_EDGE } from '../../common/consts' +import checkOverflow from '../check/overflow' +import { info } from '../console' +import createOverflowObserver from '../observers/overflow' +import sendSize from '../send/size' +import settings from '../values/settings' +import state from '../values/state' +import observers from './observers' + +function overflowObserved() { + const { hasOverflow } = state + const { hasOverflowUpdated, overflowedNodeSet } = checkOverflow() + + switch (true) { + case !hasOverflowUpdated: + return + + case overflowedNodeSet.size > 1: + info('Overflowed Elements:', overflowedNodeSet) + break + + case hasOverflow: + break + + default: + info('No overflow detected') + } + + sendSize(OVERFLOW_OBSERVER, 'Overflow updated') +} + +export default function createOverflowObservers(nodeList) { + const overflowOptions = { + root: document.documentElement, + side: settings.calculateHeight ? HEIGHT_EDGE : WIDTH_EDGE, + } + + observers.overflow = createOverflowObserver(overflowObserved, overflowOptions) + + observers.overflow.attachObservers(nodeList) + + return observers.overflow +} diff --git a/packages/child/observed/resize.js b/packages/child/observed/resize.js new file mode 100644 index 000000000..6e2fad3a3 --- /dev/null +++ b/packages/child/observed/resize.js @@ -0,0 +1,17 @@ +import { RESIZE_OBSERVER } from '../../common/consts' +import { getElementName } from '../../common/utils' +import createResizeObserver from '../observers/resize' +import sendSize from '../send/size' +import observers from './observers' + +function resizeObserved(entries) { + if (!Array.isArray(entries) || entries.length === 0) return + const el = entries[0].target + sendSize(RESIZE_OBSERVER, `Element resized <${getElementName(el)}>`) +} + +export default function createResizeObservers(nodeList) { + observers.resize = createResizeObserver(resizeObserved) + observers.resize.attachObserverToNonStaticElements(nodeList) + return observers.resize +} diff --git a/packages/child/observed/visibility.js b/packages/child/observed/visibility.js new file mode 100644 index 000000000..70ce87419 --- /dev/null +++ b/packages/child/observed/visibility.js @@ -0,0 +1,12 @@ +import { HIGHLIGHT } from 'auto-console-group' + +import { VISIBILITY_OBSERVER } from '../../common/consts' +import { log } from '../console' +import sendSize from '../send/size' +import state from '../values/state' + +export default function visibilityChange(isVisible) { + log(`Visible: %c${isVisible}`, HIGHLIGHT) + state.isHidden = !isVisible + sendSize(VISIBILITY_OBSERVER, 'Visibility changed') +} From 169170d792e2c2bb6c5b437f4a847b9b0d52ccdc Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Thu, 6 Nov 2025 11:41:01 +0000 Subject: [PATCH 36/38] Tidy Received --- packages/child/received/index.js | 18 +++++++----------- packages/child/received/is.js | 10 ++++++---- packages/child/received/page-info.js | 6 +++--- packages/child/received/parent-info.js | 6 +++--- packages/child/received/resize.js | 2 +- packages/child/received/utils.js | 6 +++++- spec/childSpec.js | 2 +- 7 files changed, 26 insertions(+), 24 deletions(-) diff --git a/packages/child/received/index.js b/packages/child/received/index.js index 9fb285c15..93b3cfe8b 100644 --- a/packages/child/received/index.js +++ b/packages/child/received/index.js @@ -1,10 +1,8 @@ -import { SEPARATOR } from '../../common/consts' import { errorBoundary, event as consoleEvent, warn } from '../console' import state from '../values/state' -import { isInitMessage, isMessageForUs, isMiddleTier } from './is' +import { isMessageForUs, isMiddleTier } from './is' import processRequest from './process-request' - -const getMessageType = (event) => event.data.split(']')[1].split(SEPARATOR)[0] +import { getMessageType } from './utils' function receiver(event) { const { firstRun } = state @@ -13,22 +11,20 @@ function receiver(event) { consoleEvent(messageType) switch (true) { - case !firstRun && messageType in processRequest: + case messageType in processRequest: processRequest[messageType](event) break - case !firstRun && !isMiddleTier() && !isInitMessage(event): - warn(`Unexpected message (${event.data})`) - break - - case !isInitMessage(event): + case firstRun && isMiddleTier(): warn( `Ignored message of type "${messageType}". Received before initialization.`, ) break default: - processRequest.init(event) + warn( + `Unexpected message (${event.data}), this is likely due to a newer version on iframe-resizer running on the parent page.`, + ) } } diff --git a/packages/child/received/is.js b/packages/child/received/is.js index 92890a211..a1c2d8025 100644 --- a/packages/child/received/is.js +++ b/packages/child/received/is.js @@ -1,13 +1,15 @@ import { MESSAGE_ID, MESSAGE_ID_LENGTH, SEPARATOR } from '../../common/consts' +const IFRAME_RESIZE = 'iframeResize' + export const isMessageForUs = (event) => MESSAGE_ID === `${event.data}`.slice(0, MESSAGE_ID_LENGTH) export const isMiddleTier = () => - 'iframeResize' in window || - (window.jQuery !== undefined && '' in window.jQuery.prototype) + IFRAME_RESIZE in window || + (window.jQuery !== undefined && IFRAME_RESIZE in window.jQuery.prototype) -// Test if this message is from a child below us. This is an ugly test, however, updating -// the message format would break backwards compatibility. +// Test if this message is from a child below us. This is an ugly test, +// however, updating the message format would break backwards compatibility. export const isInitMessage = (event) => event.data.split(SEPARATOR)[2] in { true: 1, false: 1 } diff --git a/packages/child/received/page-info.js b/packages/child/received/page-info.js index db797a281..11a7a0a52 100644 --- a/packages/child/received/page-info.js +++ b/packages/child/received/page-info.js @@ -6,12 +6,12 @@ import { getData, notExpected, parse, parseFrozen } from './utils' export default function pageInfo(event) { const { onPageInfo } = state - const msgBody = getData(event) + const messageBody = getData(event) - log(`PageInfo received from parent:`, parseFrozen(msgBody)) + log(`PageInfo received from parent:`, parseFrozen(messageBody)) if (onPageInfo) { - isolateUserCode(onPageInfo, parse(msgBody)) + isolateUserCode(onPageInfo, parse(messageBody)) } else { notExpected(PAGE_INFO) } diff --git a/packages/child/received/parent-info.js b/packages/child/received/parent-info.js index 6f388e84f..31a62c71a 100644 --- a/packages/child/received/parent-info.js +++ b/packages/child/received/parent-info.js @@ -6,12 +6,12 @@ import { getData, notExpected, parseFrozen } from './utils' export default function parentInfo(event) { const { onParentInfo } = state - const msgBody = parseFrozen(getData(event)) + const messageBody = parseFrozen(getData(event)) - log(`ParentInfo received from parent:`, msgBody) + log(`ParentInfo received from parent:`, messageBody) if (onParentInfo) { - isolateUserCode(onParentInfo, msgBody) + isolateUserCode(onParentInfo, messageBody) } else { notExpected(PARENT_INFO) } diff --git a/packages/child/received/resize.js b/packages/child/received/resize.js index 5a8897635..d65477c09 100644 --- a/packages/child/received/resize.js +++ b/packages/child/received/resize.js @@ -2,8 +2,8 @@ import { PARENT_RESIZE_REQUEST } from '../../common/consts' import { log } from '../console' import sendSize from '../send/size' +// This method is used by the tabVisible event on the parent page export default function resize() { - // This method is used by the tabVisible event on the parent page log('Resize requested by host page') sendSize(PARENT_RESIZE_REQUEST, 'Parent window requested size check') } diff --git a/packages/child/received/utils.js b/packages/child/received/utils.js index 59da0804e..c47f6f0bb 100644 --- a/packages/child/received/utils.js +++ b/packages/child/received/utils.js @@ -1,5 +1,6 @@ -import { SEPARATOR } from '../../common/consts' +import { INIT, SEPARATOR } from '../../common/consts' import sendMessage from '../send/message' +import { isInitMessage } from './is' const { freeze } = Object export const { parse } = JSON @@ -9,3 +10,6 @@ export const notExpected = (type) => sendMessage(0, 0, `${type}Stop`) export const getData = (event) => event.data.slice(event.data.indexOf(SEPARATOR) + 1) + +export const getMessageType = (event) => + isInitMessage(event) ? INIT : event.data.split(']')[1].split(SEPARATOR)[0] diff --git a/spec/childSpec.js b/spec/childSpec.js index 92faba194..c78b2fa56 100644 --- a/spec/childSpec.js +++ b/spec/childSpec.js @@ -287,7 +287,7 @@ define(['iframeResizerChild', 'jquery'], (mockMsgListener, $) => { setTimeout(() => { expect(console.warn).toHaveBeenCalledWith( - 'Unexpected message ([iFrameSizer]foo)', + 'Unexpected message ([iFrameSizer]foo), this is likely due to a newer version on iframe-resizer running on the parent page.', ) done() }) From dec6fe51b05684eb3f74032bf220cf5645c4fe4f Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Thu, 6 Nov 2025 12:05:04 +0000 Subject: [PATCH 37/38] Tidy Size --- packages/child/size/all.js | 1 + packages/child/size/auto.js | 6 +++--- packages/child/size/max-element.js | 6 ++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/child/size/all.js b/packages/child/size/all.js index 9432621e3..3d97b1044 100644 --- a/packages/child/size/all.js +++ b/packages/child/size/all.js @@ -2,6 +2,7 @@ import { IGNORE_TAGS } from '../../common/consts' const addNot = (tagName) => `:not(${tagName})` const selector = `* ${Array.from(IGNORE_TAGS).map(addNot).join('')}` + export const getAllElements = (node) => node.querySelectorAll(selector) export const getAllMeasurements = (dimension) => [ diff --git a/packages/child/size/auto.js b/packages/child/size/auto.js index 69fcb74b9..ebc61069e 100644 --- a/packages/child/size/auto.js +++ b/packages/child/size/auto.js @@ -4,6 +4,8 @@ import { HEIGHT, MIN_SIZE } from '../../common/consts' import { info } from '../console' import state from '../values/state' +const BOUNDING_FORMAT = [HIGHLIGHT, FOREGROUND, HIGHLIGHT] + const prevScrollSize = { height: 0, width: 0, @@ -17,7 +19,7 @@ const prevBoundingSize = { function getBoundingClientRect(dimension, boundingSize, scrollSize) { prevBoundingSize[dimension] = boundingSize prevScrollSize[dimension] = scrollSize - return Math.max(boundingSize, MIN_SIZE) + return boundingSize } function getOffset(getDimension) { @@ -29,8 +31,6 @@ function getOffset(getDimension) { const getAdjustedScroll = (getDimension) => getDimension.documentElementScroll() + Math.max(0, getDimension.getOffset()) -const BOUNDING_FORMAT = [HIGHLIGHT, FOREGROUND, HIGHLIGHT] - export default function getAutoSize(getDimension) { const { hasOverflow, hasTags, triggerLocked } = state const dimension = getDimension.label diff --git a/packages/child/size/max-element.js b/packages/child/size/max-element.js index 2c5e3831b..eea0cc4da 100644 --- a/packages/child/size/max-element.js +++ b/packages/child/size/max-element.js @@ -19,16 +19,18 @@ function getSelectedElements() { } function findMaxElement(targetElements, side) { + const marginSide = `margin-${side}` + let elVal = MIN_SIZE let maxEl = document.documentElement let maxVal = state.hasTags - ? 0 + ? MIN_SIZE : document.documentElement.getBoundingClientRect().bottom for (const element of targetElements) { elVal = element.getBoundingClientRect()[side] + - parseFloat(getComputedStyle(element).getPropertyValue(`margin-${side}`)) + parseFloat(getComputedStyle(element).getPropertyValue(marginSide)) if (elVal > maxVal) { maxVal = elVal From 035ed8929586599c19d220f1c16761a5ee1f5944 Mon Sep 17 00:00:00 2001 From: "David J. Bradshaw" Date: Thu, 6 Nov 2025 12:15:48 +0000 Subject: [PATCH 38/38] Tidy --- packages/child/observed/mutation.js | 1 + packages/child/observed/resize.js | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/child/observed/mutation.js b/packages/child/observed/mutation.js index e7adf330c..b7dbfcb15 100644 --- a/packages/child/observed/mutation.js +++ b/packages/child/observed/mutation.js @@ -16,6 +16,7 @@ const getCombinedElementLists = (nodeList) => { } info(`Inspecting:\n`, elements) + return elements } diff --git a/packages/child/observed/resize.js b/packages/child/observed/resize.js index 6e2fad3a3..c172701e9 100644 --- a/packages/child/observed/resize.js +++ b/packages/child/observed/resize.js @@ -13,5 +13,6 @@ function resizeObserved(entries) { export default function createResizeObservers(nodeList) { observers.resize = createResizeObserver(resizeObserved) observers.resize.attachObserverToNonStaticElements(nodeList) + return observers.resize }