@data-fair/frame 0.18.7 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"d-frame-content.min.js","sources":["../../lib/messages.js","../../lib/utils/in-iframe.js","../../lib/DFrameContent.js","../../lib/vue-reactive/util.js","../../lib/v-iframe-compat/d-frame-content.js"],"sourcesContent":["export function isMouseEventMessage(message) {\n return message[0] === 'df-global' && message[1] === 'mouse';\n}\nexport function isInitChildMessage(message) {\n return message[0] === 'df-child' && message[1] === 'init';\n}\nexport function isHeightMessage(message) {\n return message[0] === 'df-child' && message[1] === 'height';\n}\nexport function isReinitHeightMessage(message) {\n return message[0] === 'df-child' && message[1] === 'reinit-height';\n}\nexport function isStateChangeMessage(message) {\n return message[0] === 'df-child' && message[1] === 'stateChange';\n}\nexport function isCustomMessage(message) {\n return message[0] === 'df-child' && message[1] === 'custom';\n}\nexport function isNotifMessage(message) {\n return message[0] === 'df-child' && message[1] === 'notif';\n}\nexport function isAddParentUrlListenerMessage(message) {\n return message[0] === 'df-child' && message[1] === 'addParentUrlListener';\n}\nexport function isRemoveParentUrlListenerMessage(message) {\n return message[0] === 'df-child' && message[1] === 'removeParentUrlListener';\n}\nexport function isReadyMessage(message) {\n return message[0] === 'df-child' && message[1] === 'ready';\n}\nexport function isInitParentMessage(message) {\n return message[0] === 'df-parent' && message[1] === 'init';\n}\nexport function isUpdateSrcMessage(message) {\n return message[0] === 'df-parent' && message[1] === 'updateSrc';\n}\nexport function isParentUrlMessage(message) {\n return message[0] === 'df-parent' && message[1] === 'parentUrl';\n}\n","const inIframe = (() => {\n try {\n return window.top !== window.self;\n }\n catch (e) {\n return true;\n }\n})();\nexport default inIframe;\n","import { isInitParentMessage, isMouseEventMessage, isUpdateSrcMessage, isParentUrlMessage } from './messages.js';\nimport inIframe from './utils/in-iframe.js';\nconst windowEventTypes = [\n 'animationstart',\n 'webkitAnimationStart',\n 'animationiteration',\n 'webkitAnimationIteration',\n 'animationend',\n 'webkitAnimationEnd',\n 'input',\n 'mouseup',\n 'mousedown',\n 'orientationchange',\n 'afterprint',\n 'beforeprint',\n 'readystatechange',\n 'touchstart',\n 'touchend',\n 'touchcancel',\n 'transitionstart',\n 'webkitTransitionStart',\n 'MSTransitionStart',\n 'oTransitionStart',\n 'otransitionstart', 'transitioniteration',\n 'webkitTransitionIteration',\n 'MSTransitionIteration',\n 'oTransitionIteration',\n 'otransitioniteration', 'transitionend',\n 'webkitTransitionEnd',\n 'MSTransitionEnd',\n 'oTransitionEnd',\n 'otransitionend', 'resize'\n];\nexport default class DFrameContent {\n initialized = false;\n options;\n pendingCheckHeight = false;\n debug = false;\n id;\n throttledCheckHeight;\n lastHeight = 0;\n parentUrlListeners = {};\n parentUrlResults = {};\n constructor(options) {\n this.options = options ?? {};\n if (typeof window === 'undefined')\n return;\n if (window.__d_frame__content) {\n throw new Error('DFrameContent was already initialized');\n }\n window.__d_frame__content = this;\n const afCallback = () => {\n this.checkHeight();\n this.pendingCheckHeight = false;\n };\n this.throttledCheckHeight = () => {\n if (this.pendingCheckHeight)\n return;\n this.pendingCheckHeight = true;\n requestAnimationFrame(afCallback);\n };\n window.addEventListener('message', (e) => this.onMessage(e));\n // simple handshake for initialization\n this.postMessageToParent(['df-child', 'init']);\n }\n log(level, ...args) {\n if (level === 'debug' && !this.debug)\n return;\n if (level === 'debug')\n console.timeLog(`d-frame:${this.id}:content`, ...args);\n if (level === 'info')\n console.info(`d-frame:${this.id}:content`, ...args);\n if (level === 'error')\n console.error(`d-frame:${this.id}:content`, ...args);\n }\n onMessage(e) {\n if (e.source === window.parent && Array.isArray(e.data)) {\n const message = e.data;\n if (isInitParentMessage(message)) {\n this.id = message[2].id;\n this.debug = !!message[2].debug;\n if (this.debug)\n console.time(`d-frame:${this.id}:content`);\n this.log('debug', 'init handshake is ok', message, this.options);\n if (message[2].src && message[2].src !== window.location.href) {\n this.log('error', `src mismatch at init, maybe the frame navigated before the initialization handshake: ${window.location.href} !== ${message[2].src}`);\n }\n if (message[2].resize !== 'no')\n this.initResize();\n if (message[2].syncParams || message[2].syncPath || message[2].stateChangeEvents)\n this.initStateChangeWatcher();\n if (message[2].mouseEvents)\n this.initMouseEvents(message[2].mouseEvents);\n this.initialized = true;\n }\n if (isUpdateSrcMessage(message)) {\n const newSrc = message[2].startsWith('/') ? window.location.origin + message[2] : message[2];\n if (newSrc === window.location.href)\n return;\n this.log('debug', `update src ${window.location.href} -> ${newSrc}`);\n if (this.options.updateSrc) {\n this.options.updateSrc(newSrc, this);\n }\n else {\n this.log('debug', 'no updateSrc method available, falling back to updating window.location.href', newSrc);\n window.location.href = newSrc;\n }\n }\n if (isMouseEventMessage(message)) {\n this.applySyncedMouseEvent(message);\n }\n if (isParentUrlMessage(message)) {\n if (!this.parentUrlListeners[message[2]]) {\n this.log('error', `received getParentUrlResponse message but no matching callback is registered: ${message[2]}`);\n }\n else {\n for (const listener of this.parentUrlListeners[message[2]]) {\n listener(message[3]);\n }\n }\n }\n }\n }\n postMessageToParent(message) {\n window.parent.postMessage(message, '*');\n }\n sendNotif(notif) {\n this.postMessageToParent(['df-child', 'notif', notif]);\n }\n sendMessage(msg) {\n this.postMessageToParent(['df-child', 'custom', msg]);\n }\n ready() {\n this.postMessageToParent(['df-child', 'ready']);\n }\n reinitHeight() {\n this.postMessageToParent(['df-child', 'reinit-height']);\n }\n // this is not a single-time callback, it can be called multiple times when parent url changes\n addParentUrlListener(partialChildHref, listener) {\n const childHref = new URL(partialChildHref, window.location.href).href;\n if (!inIframe) {\n listener(childHref);\n return;\n }\n if (this.parentUrlResults[childHref]) {\n listener(this.parentUrlResults[childHref]);\n }\n this.parentUrlListeners[childHref] = this.parentUrlListeners[childHref] ?? [];\n this.parentUrlListeners[childHref].push(listener);\n this.postMessageToParent(['df-child', 'addParentUrlListener', childHref]);\n }\n removeParentUrlListener(partialChildHref, listener) {\n const childHref = new URL(partialChildHref, window.location.href).href;\n if (this.parentUrlListeners[childHref]) {\n this.parentUrlListeners[childHref] = this.parentUrlListeners[childHref].filter(cb => cb !== listener);\n if (!this.parentUrlListeners[childHref].length) {\n delete this.parentUrlListeners[childHref];\n this.postMessageToParent(['df-child', 'removeParentUrlListener', childHref]);\n }\n }\n }\n currentSyncedMouseEvents = new Set();\n initMouseEvents(types) {\n this.log('debug', 'initMouseEvents');\n for (const eventType of types) {\n document.addEventListener(eventType, (e) => {\n if (!(e instanceof MouseEvent))\n return;\n this.transmitSyncedMouseEvent(['df-global', 'mouse', eventType, { altKey: e.altKey, ctrlKey: e.ctrlKey, shiftKey: e.shiftKey, metaKey: e.metaKey }]);\n }, true);\n }\n }\n transmitSyncedMouseEvent(message) {\n if (this.currentSyncedMouseEvents.has(message[2])) {\n this.currentSyncedMouseEvents.delete(message[2]);\n }\n else {\n this.postMessageToParent(message);\n }\n }\n applySyncedMouseEvent(message) {\n this.currentSyncedMouseEvents.add(message[2]);\n const event = new MouseEvent(message[2], { bubbles: true, cancelable: true, view: window, ...message[3] });\n document.body.dispatchEvent(event);\n }\n initResize() {\n this.log('debug', 'initResize');\n this.checkHeight();\n this.createMutationObserver();\n this.createWindowEventListeners();\n }\n initStateChangeWatcher() {\n this.log('debug', 'initStateChangeWatcher');\n // monkey patch pushState and replaceState to send all state change info to the parent window\n // this is to compensate the lack of event emission by window.history\n const oldReplaceState = window.history.replaceState;\n const newPushState = (...args) => {\n const before = window.location.href;\n // do a replace instead of a push, the push will be done in the parent window if sync-state is activated\n const ret = oldReplaceState.apply(window.history, args);\n if (window.location.href !== before) {\n this.postMessageToParent(['df-child', 'stateChange', 'push', window.location.href]);\n }\n return ret;\n };\n window.history.pushState = newPushState;\n const newReplaceState = (...args) => {\n const before = window.location.href;\n const ret = oldReplaceState.apply(window.history, args);\n if (window.location.href !== before) {\n this.postMessageToParent(['df-child', 'stateChange', 'replace', window.location.href]);\n }\n return ret;\n };\n window.history.replaceState = newReplaceState;\n }\n createMutationObserver() {\n const body = document.querySelector('body');\n if (!body)\n throw new Error('DFrameContentManager was initialized before the HTML body');\n if (!this.throttledCheckHeight)\n throw new Error('throttledCheckHeight is not defined');\n const observer = new window.MutationObserver(this.throttledCheckHeight);\n observer.observe(body, {\n attributes: false,\n attributeOldValue: false,\n characterData: false,\n characterDataOldValue: false,\n childList: true,\n subtree: true,\n });\n }\n createWindowEventListeners() {\n if (!this.throttledCheckHeight)\n throw new Error('throttledCheckHeight is not defined');\n for (const eventType of windowEventTypes) {\n window.addEventListener(eventType, this.throttledCheckHeight, { passive: true });\n }\n }\n checkHeight() {\n const elements = document.querySelectorAll('[data-iframe-height]');\n let maxWithoutOffset = 0;\n let max = 0;\n for (const element of elements) {\n const dataAttribute = element.getAttribute('data-iframe-height');\n if (dataAttribute === 'false' || dataAttribute === 'no')\n continue;\n const bottomWithoutOffset = element.getBoundingClientRect().bottom +\n parseFloat(getComputedStyle(element).getPropertyValue('margin-bottom'));\n const bottom = dataAttribute ? (bottomWithoutOffset + parseFloat(dataAttribute)) : bottomWithoutOffset;\n if (bottomWithoutOffset > maxWithoutOffset)\n maxWithoutOffset = bottomWithoutOffset;\n if (bottom > max)\n max = bottom;\n }\n max = Math.ceil(max);\n if (maxWithoutOffset > 0 && max !== this.lastHeight) {\n this.postMessageToParent(['df-child', 'height', max]);\n this.lastHeight = max;\n }\n }\n}\n","export const applySearchParams = (reactiveParams, searchParams) => {\n const existingKeys = Object.keys(reactiveParams);\n searchParams.forEach((value, key) => {\n reactiveParams[key] = value;\n existingKeys.splice(existingKeys.indexOf(key), 1);\n });\n existingKeys.forEach(key => {\n delete reactiveParams[key];\n });\n};\n","import DFrameContent from '../DFrameContent.js';\nimport { applySearchParams } from '../vue-reactive/util.js';\nconst dFrameContent = new DFrameContent({\n updateSrc(src, instance) {\n const vIframeOptions = window.vIframeOptions;\n const reactiveParams = vIframeOptions?.reactiveParams;\n if (reactiveParams && typeof reactiveParams === 'object') {\n const srcUrl = new URL(src);\n if (srcUrl.origin === window.location.origin && srcUrl.pathname === window.location.pathname) {\n instance.log('debug', 'applying reactive params', srcUrl.searchParams);\n applySearchParams(reactiveParams, srcUrl.searchParams);\n return;\n }\n }\n const router = vIframeOptions?.router;\n if (router) {\n // @ts-ignore vue-router v2\n const base = router.options?.base ?? router.options.history?.base;\n if (base === null || base === undefined) {\n instance.log('error', 'failed to access base path in router (no router.options.base nor router.options.history.base)', router);\n }\n else {\n const urlPrefix = window.location.origin + base;\n if (!src.startsWith(urlPrefix)) {\n instance.log('info', 'src does not start with base path', src, urlPrefix);\n }\n else {\n let newRoute = src.replace(urlPrefix, '');\n if (!newRoute.startsWith('/'))\n newRoute = '/' + newRoute;\n instance.log('debug', 'applying router.replace', newRoute);\n router.replace(newRoute);\n return;\n }\n }\n }\n instance.log('info', 'falling back to updating window.location.href', src);\n window.location.href = src;\n }\n});\nwindow.dFrameContent = dFrameContent;\nexport default dFrameContent;\n"],"names":["isMouseEventMessage","message","isInitParentMessage","isUpdateSrcMessage","isParentUrlMessage","inIframe","windowEventTypes","DFrameContent","options","__publicField","afCallback","e","level","args","newSrc","listener","notif","msg","partialChildHref","childHref","cb","types","eventType","event","oldReplaceState","newPushState","before","ret","newReplaceState","body","elements","maxWithoutOffset","max","element","dataAttribute","bottomWithoutOffset","bottom","applySearchParams","reactiveParams","searchParams","existingKeys","value","key","dFrameContent","src","instance","vIframeOptions","srcUrl","router","base","_a","_b","urlPrefix","newRoute"],"mappings":"2NAAO,SAASA,EAAoBC,EAAS,CACzC,OAAOA,EAAQ,CAAC,IAAM,aAAeA,EAAQ,CAAC,IAAM,OACxD,CA4BO,SAASC,EAAoBD,EAAS,CACzC,OAAOA,EAAQ,CAAC,IAAM,aAAeA,EAAQ,CAAC,IAAM,MACxD,CACO,SAASE,EAAmBF,EAAS,CACxC,OAAOA,EAAQ,CAAC,IAAM,aAAeA,EAAQ,CAAC,IAAM,WACxD,CACO,SAASG,EAAmBH,EAAS,CACxC,OAAOA,EAAQ,CAAC,IAAM,aAAeA,EAAQ,CAAC,IAAM,WACxD,CCtCA,MAAMI,GAAY,IAAM,CACpB,GAAI,CACA,OAAO,OAAO,MAAQ,OAAO,IACrC,MACc,CACN,MAAO,EACf,CACA,GAAI,ECLEC,EAAmB,CACrB,iBACA,uBACA,qBACA,2BACA,eACA,qBACA,QACA,UACA,YACA,oBACA,aACA,cACA,mBACA,aACA,WACA,cACA,kBACA,wBACA,oBACA,mBACA,mBAAoB,sBACpB,4BACA,wBACA,uBACA,uBAAwB,gBACxB,sBACA,kBACA,iBACA,iBAAkB,QACtB,EACe,MAAMC,CAAc,CAU/B,YAAYC,EAAS,CATrBC,EAAA,mBAAc,IACdA,EAAA,gBACAA,EAAA,0BAAqB,IACrBA,EAAA,aAAQ,IACRA,EAAA,WACAA,EAAA,6BACAA,EAAA,kBAAa,GACbA,EAAA,0BAAqB,CAAE,GACvBA,EAAA,wBAAmB,CAAE,GAwHrBA,EAAA,gCAA2B,IAAI,KArH3B,GADA,KAAK,QAAUD,GAAW,CAAE,EACxB,OAAO,OAAW,IAClB,OACJ,GAAI,OAAO,mBACP,MAAM,IAAI,MAAM,uCAAuC,EAE3D,OAAO,mBAAqB,KAC5B,MAAME,EAAa,IAAM,CACrB,KAAK,YAAa,EAClB,KAAK,mBAAqB,EAC7B,EACD,KAAK,qBAAuB,IAAM,CAC1B,KAAK,qBAET,KAAK,mBAAqB,GAC1B,sBAAsBA,CAAU,EACnC,EACD,OAAO,iBAAiB,UAAYC,GAAM,KAAK,UAAUA,CAAC,CAAC,EAE3D,KAAK,oBAAoB,CAAC,WAAY,MAAM,CAAC,CACrD,CACI,IAAIC,KAAUC,EAAM,CACZD,IAAU,SAAW,CAAC,KAAK,QAE3BA,IAAU,SACV,QAAQ,QAAQ,WAAW,KAAK,EAAE,WAAY,GAAGC,CAAI,EACrDD,IAAU,QACV,QAAQ,KAAK,WAAW,KAAK,EAAE,WAAY,GAAGC,CAAI,EAClDD,IAAU,SACV,QAAQ,MAAM,WAAW,KAAK,EAAE,WAAY,GAAGC,CAAI,EAC/D,CACI,UAAU,EAAG,CACT,GAAI,EAAE,SAAW,OAAO,QAAU,MAAM,QAAQ,EAAE,IAAI,EAAG,CACrD,MAAMZ,EAAU,EAAE,KAkBlB,GAjBIC,EAAoBD,CAAO,IAC3B,KAAK,GAAKA,EAAQ,CAAC,EAAE,GACrB,KAAK,MAAQ,CAAC,CAACA,EAAQ,CAAC,EAAE,MACtB,KAAK,OACL,QAAQ,KAAK,WAAW,KAAK,EAAE,UAAU,EAC7C,KAAK,IAAI,QAAS,uBAAwBA,EAAS,KAAK,OAAO,EAC3DA,EAAQ,CAAC,EAAE,KAAOA,EAAQ,CAAC,EAAE,MAAQ,OAAO,SAAS,MACrD,KAAK,IAAI,QAAS,wFAAwF,OAAO,SAAS,IAAI,QAAQA,EAAQ,CAAC,EAAE,GAAG,EAAE,EAEtJA,EAAQ,CAAC,EAAE,SAAW,MACtB,KAAK,WAAY,GACjBA,EAAQ,CAAC,EAAE,YAAcA,EAAQ,CAAC,EAAE,UAAYA,EAAQ,CAAC,EAAE,oBAC3D,KAAK,uBAAwB,EAC7BA,EAAQ,CAAC,EAAE,aACX,KAAK,gBAAgBA,EAAQ,CAAC,EAAE,WAAW,EAC/C,KAAK,YAAc,IAEnBE,EAAmBF,CAAO,EAAG,CAC7B,MAAMa,EAASb,EAAQ,CAAC,EAAE,WAAW,GAAG,EAAI,OAAO,SAAS,OAASA,EAAQ,CAAC,EAAIA,EAAQ,CAAC,EAC3F,GAAIa,IAAW,OAAO,SAAS,KAC3B,OACJ,KAAK,IAAI,QAAS,cAAc,OAAO,SAAS,IAAI,OAAOA,CAAM,EAAE,EAC/D,KAAK,QAAQ,UACb,KAAK,QAAQ,UAAUA,EAAQ,IAAI,GAGnC,KAAK,IAAI,QAAS,+EAAgFA,CAAM,EACxG,OAAO,SAAS,KAAOA,EAE3C,CAIY,GAHId,EAAoBC,CAAO,GAC3B,KAAK,sBAAsBA,CAAO,EAElCG,EAAmBH,CAAO,EAC1B,GAAI,CAAC,KAAK,mBAAmBA,EAAQ,CAAC,CAAC,EACnC,KAAK,IAAI,QAAS,iFAAiFA,EAAQ,CAAC,CAAC,EAAE,MAG/G,WAAWc,KAAY,KAAK,mBAAmBd,EAAQ,CAAC,CAAC,EACrDc,EAASd,EAAQ,CAAC,CAAC,CAI3C,CACA,CACI,oBAAoBA,EAAS,CACzB,OAAO,OAAO,YAAYA,EAAS,GAAG,CAC9C,CACI,UAAUe,EAAO,CACb,KAAK,oBAAoB,CAAC,WAAY,QAASA,CAAK,CAAC,CAC7D,CACI,YAAYC,EAAK,CACb,KAAK,oBAAoB,CAAC,WAAY,SAAUA,CAAG,CAAC,CAC5D,CACI,OAAQ,CACJ,KAAK,oBAAoB,CAAC,WAAY,OAAO,CAAC,CACtD,CACI,cAAe,CACX,KAAK,oBAAoB,CAAC,WAAY,eAAe,CAAC,CAC9D,CAEI,qBAAqBC,EAAkBH,EAAU,CAC7C,MAAMI,EAAY,IAAI,IAAID,EAAkB,OAAO,SAAS,IAAI,EAAE,KAClE,GAAI,CAACb,EAAU,CACXU,EAASI,CAAS,EAClB,MACZ,CACY,KAAK,iBAAiBA,CAAS,GAC/BJ,EAAS,KAAK,iBAAiBI,CAAS,CAAC,EAE7C,KAAK,mBAAmBA,CAAS,EAAI,KAAK,mBAAmBA,CAAS,GAAK,CAAE,EAC7E,KAAK,mBAAmBA,CAAS,EAAE,KAAKJ,CAAQ,EAChD,KAAK,oBAAoB,CAAC,WAAY,uBAAwBI,CAAS,CAAC,CAChF,CACI,wBAAwBD,EAAkBH,EAAU,CAChD,MAAMI,EAAY,IAAI,IAAID,EAAkB,OAAO,SAAS,IAAI,EAAE,KAC9D,KAAK,mBAAmBC,CAAS,IACjC,KAAK,mBAAmBA,CAAS,EAAI,KAAK,mBAAmBA,CAAS,EAAE,OAAOC,GAAMA,IAAOL,CAAQ,EAC/F,KAAK,mBAAmBI,CAAS,EAAE,SACpC,OAAO,KAAK,mBAAmBA,CAAS,EACxC,KAAK,oBAAoB,CAAC,WAAY,0BAA2BA,CAAS,CAAC,GAG3F,CAEI,gBAAgBE,EAAO,CACnB,KAAK,IAAI,QAAS,iBAAiB,EACnC,UAAWC,KAAaD,EACpB,SAAS,iBAAiBC,EAAYX,GAAM,CAClCA,aAAa,YAEnB,KAAK,yBAAyB,CAAC,YAAa,QAASW,EAAW,CAAE,OAAQX,EAAE,OAAQ,QAASA,EAAE,QAAS,SAAUA,EAAE,SAAU,QAASA,EAAE,OAAO,CAAE,CAAC,CACtJ,EAAE,EAAI,CAEnB,CACI,yBAAyBV,EAAS,CAC1B,KAAK,yBAAyB,IAAIA,EAAQ,CAAC,CAAC,EAC5C,KAAK,yBAAyB,OAAOA,EAAQ,CAAC,CAAC,EAG/C,KAAK,oBAAoBA,CAAO,CAE5C,CACI,sBAAsBA,EAAS,CAC3B,KAAK,yBAAyB,IAAIA,EAAQ,CAAC,CAAC,EAC5C,MAAMsB,EAAQ,IAAI,WAAWtB,EAAQ,CAAC,EAAG,CAAE,QAAS,GAAM,WAAY,GAAM,KAAM,OAAQ,GAAGA,EAAQ,CAAC,EAAG,EACzG,SAAS,KAAK,cAAcsB,CAAK,CACzC,CACI,YAAa,CACT,KAAK,IAAI,QAAS,YAAY,EAC9B,KAAK,YAAa,EAClB,KAAK,uBAAwB,EAC7B,KAAK,2BAA4B,CACzC,CACI,wBAAyB,CACrB,KAAK,IAAI,QAAS,wBAAwB,EAG1C,MAAMC,EAAkB,OAAO,QAAQ,aACjCC,EAAe,IAAIZ,IAAS,CAC9B,MAAMa,EAAS,OAAO,SAAS,KAEzBC,EAAMH,EAAgB,MAAM,OAAO,QAASX,CAAI,EACtD,OAAI,OAAO,SAAS,OAASa,GACzB,KAAK,oBAAoB,CAAC,WAAY,cAAe,OAAQ,OAAO,SAAS,IAAI,CAAC,EAE/EC,CACV,EACD,OAAO,QAAQ,UAAYF,EAC3B,MAAMG,EAAkB,IAAIf,IAAS,CACjC,MAAMa,EAAS,OAAO,SAAS,KACzBC,EAAMH,EAAgB,MAAM,OAAO,QAASX,CAAI,EACtD,OAAI,OAAO,SAAS,OAASa,GACzB,KAAK,oBAAoB,CAAC,WAAY,cAAe,UAAW,OAAO,SAAS,IAAI,CAAC,EAElFC,CACV,EACD,OAAO,QAAQ,aAAeC,CACtC,CACI,wBAAyB,CACrB,MAAMC,EAAO,SAAS,cAAc,MAAM,EAC1C,GAAI,CAACA,EACD,MAAM,IAAI,MAAM,2DAA2D,EAC/E,GAAI,CAAC,KAAK,qBACN,MAAM,IAAI,MAAM,qCAAqC,EACxC,IAAI,OAAO,iBAAiB,KAAK,oBAAoB,EAC7D,QAAQA,EAAM,CACnB,WAAY,GACZ,kBAAmB,GACnB,cAAe,GACf,sBAAuB,GACvB,UAAW,GACX,QAAS,EACrB,CAAS,CACT,CACI,4BAA6B,CACzB,GAAI,CAAC,KAAK,qBACN,MAAM,IAAI,MAAM,qCAAqC,EACzD,UAAWP,KAAahB,EACpB,OAAO,iBAAiBgB,EAAW,KAAK,qBAAsB,CAAE,QAAS,GAAM,CAE3F,CACI,aAAc,CACV,MAAMQ,EAAW,SAAS,iBAAiB,sBAAsB,EACjE,IAAIC,EAAmB,EACnBC,EAAM,EACV,UAAWC,KAAWH,EAAU,CAC5B,MAAMI,EAAgBD,EAAQ,aAAa,oBAAoB,EAC/D,GAAIC,IAAkB,SAAWA,IAAkB,KAC/C,SACJ,MAAMC,EAAsBF,EAAQ,sBAAqB,EAAG,OACxD,WAAW,iBAAiBA,CAAO,EAAE,iBAAiB,eAAe,CAAC,EACpEG,EAASF,EAAiBC,EAAsB,WAAWD,CAAa,EAAKC,EAC/EA,EAAsBJ,IACtBA,EAAmBI,GACnBC,EAASJ,IACTA,EAAMI,EACtB,CACQJ,EAAM,KAAK,KAAKA,CAAG,EACfD,EAAmB,GAAKC,IAAQ,KAAK,aACrC,KAAK,oBAAoB,CAAC,WAAY,SAAUA,CAAG,CAAC,EACpD,KAAK,WAAaA,EAE9B,CACA,CCtQO,MAAMK,EAAoB,CAACC,EAAgBC,IAAiB,CAC/D,MAAMC,EAAe,OAAO,KAAKF,CAAc,EAC/CC,EAAa,QAAQ,CAACE,EAAOC,IAAQ,CACjCJ,EAAeI,CAAG,EAAID,EACtBD,EAAa,OAAOA,EAAa,QAAQE,CAAG,EAAG,CAAC,CACxD,CAAK,EACDF,EAAa,QAAQE,GAAO,CACxB,OAAOJ,EAAeI,CAAG,CACjC,CAAK,CACL,ECPMC,EAAgB,IAAIpC,EAAc,CACpC,UAAUqC,EAAKC,EAAU,SACrB,MAAMC,EAAiB,OAAO,eACxBR,EAAiBQ,GAAA,YAAAA,EAAgB,eACvC,GAAIR,GAAkB,OAAOA,GAAmB,SAAU,CACtD,MAAMS,EAAS,IAAI,IAAIH,CAAG,EAC1B,GAAIG,EAAO,SAAW,OAAO,SAAS,QAAUA,EAAO,WAAa,OAAO,SAAS,SAAU,CAC1FF,EAAS,IAAI,QAAS,2BAA4BE,EAAO,YAAY,EACrEV,EAAkBC,EAAgBS,EAAO,YAAY,EACrD,MAChB,CACA,CACQ,MAAMC,EAASF,GAAA,YAAAA,EAAgB,OAC/B,GAAIE,EAAQ,CAER,MAAMC,IAAOC,EAAAF,EAAO,UAAP,YAAAE,EAAgB,SAAQC,EAAAH,EAAO,QAAQ,UAAf,YAAAG,EAAwB,MAC7D,GAAIF,GAAS,KACTJ,EAAS,IAAI,QAAS,gGAAiGG,CAAM,MAE5H,CACD,MAAMI,EAAY,OAAO,SAAS,OAASH,EAC3C,GAAI,CAACL,EAAI,WAAWQ,CAAS,EACzBP,EAAS,IAAI,OAAQ,oCAAqCD,EAAKQ,CAAS,MAEvE,CACD,IAAIC,EAAWT,EAAI,QAAQQ,EAAW,EAAE,EACnCC,EAAS,WAAW,GAAG,IACxBA,EAAW,IAAMA,GACrBR,EAAS,IAAI,QAAS,0BAA2BQ,CAAQ,EACzDL,EAAO,QAAQK,CAAQ,EACvB,MACpB,CACA,CACA,CACQR,EAAS,IAAI,OAAQ,gDAAiDD,CAAG,EACzE,OAAO,SAAS,KAAOA,CAC/B,CACA,CAAC,EACD,cAAO,cAAgBD"}
1
+ {"version":3,"file":"d-frame-content.min.js","sources":["../../lib/utils/url.js","../../lib/messages.js","../../lib/utils/in-iframe.js","../../lib/DFrameContent.js","../../lib/vue-reactive/util.js","../../lib/v-iframe-compat/d-frame-content.ts"],"sourcesContent":["export function getPathDirectory(path) {\n if (path.endsWith('/'))\n return path;\n if (path === '')\n return '/';\n return path.slice(0, path.lastIndexOf('/') + 1);\n}\nexport function getPathWithTrailingSlash(path) {\n if (path.endsWith('/'))\n return path;\n return path + '/';\n}\nexport function getUrlRelativePath(srcUrl, currentUrl) {\n if (currentUrl.origin !== srcUrl.origin)\n return currentUrl.href;\n if (getPathWithTrailingSlash(currentUrl.pathname) === getPathWithTrailingSlash(srcUrl.pathname))\n return '';\n const srcDirectory = getPathDirectory(srcUrl.pathname);\n if (!currentUrl.pathname.startsWith(srcDirectory))\n return currentUrl.pathname;\n return currentUrl.pathname.replace(srcDirectory, '');\n}\n// a URL that can be navigated to or used as a link without running code (no javascript:, data:...)\nexport function isHttpUrl(value, base) {\n if (typeof value !== 'string')\n return false;\n try {\n return ['http:', 'https:'].includes(new URL(value, base).protocol);\n }\n catch {\n return false;\n }\n}\n","import { isHttpUrl } from './utils/url.js';\nexport function isMouseEventMessage(message) {\n return message[0] === 'df-global' && message[1] === 'mouse' && typeof message[2] === 'string';\n}\nexport function isInitChildMessage(message) {\n return message[0] === 'df-child' && message[1] === 'init';\n}\nexport function isHeightMessage(message) {\n // the height is written in the style of the wrapper, anything else than a number would inject css\n return message[0] === 'df-child' && message[1] === 'height' && typeof message[2] === 'number' && Number.isFinite(message[2]) && message[2] >= 0;\n}\nexport function isReinitHeightMessage(message) {\n return message[0] === 'df-child' && message[1] === 'reinit-height';\n}\n// the URL of the child becomes the src of the iframe when it is reloaded: a javascript: URL would run in the parent\nexport function isStateChangeMessage(message) {\n return message[0] === 'df-child' && message[1] === 'stateChange' && (message[2] === 'push' || message[2] === 'replace') && isHttpUrl(message[3]);\n}\nexport function isCustomMessage(message) {\n return message[0] === 'df-child' && message[1] === 'custom';\n}\nexport function isNotifMessage(message) {\n return message[0] === 'df-child' && message[1] === 'notif';\n}\nexport function isAddParentUrlListenerMessage(message) {\n return message[0] === 'df-child' && message[1] === 'addParentUrlListener' && isHttpUrl(message[2]);\n}\nexport function isRemoveParentUrlListenerMessage(message) {\n return message[0] === 'df-child' && message[1] === 'removeParentUrlListener';\n}\nexport function isReadyMessage(message) {\n return message[0] === 'df-child' && message[1] === 'ready';\n}\nexport function isInitParentMessage(message) {\n return message[0] === 'df-parent' && message[1] === 'init';\n}\nexport function isUpdateSrcMessage(message) {\n return message[0] === 'df-parent' && message[1] === 'updateSrc';\n}\nexport function isParentUrlMessage(message) {\n return message[0] === 'df-parent' && message[1] === 'parentUrl';\n}\n","const inIframe = (() => {\n try {\n return window.top !== window.self;\n }\n catch (e) {\n return true;\n }\n})();\nexport default inIframe;\n","import { isInitParentMessage, isMouseEventMessage, isUpdateSrcMessage, isParentUrlMessage } from './messages.js';\nimport inIframe from './utils/in-iframe.js';\nimport { isHttpUrl } from './utils/url.js';\n// prefixed variants are not listed: browsers do not fire them when there is an unprefixed listener\nconst windowEventTypes = [\n 'animationstart',\n 'animationiteration',\n 'animationend',\n 'input',\n 'mouseup',\n 'mousedown',\n 'orientationchange',\n 'afterprint',\n 'beforeprint',\n 'touchstart',\n 'touchend',\n 'touchcancel',\n 'transitionstart',\n 'transitionend',\n 'resize'\n];\nexport default class DFrameContent {\n initialized = false;\n options;\n pendingCheckHeight = false;\n debug = false;\n id;\n throttledCheckHeight;\n lastHeight = 0;\n parentUrlListeners = {};\n parentUrlResults = {};\n constructor(options) {\n this.options = options ?? {};\n if (typeof window === 'undefined')\n return;\n if (window.__d_frame__content) {\n throw new Error('DFrameContent was already initialized');\n }\n window.__d_frame__content = this;\n const afCallback = () => {\n this.checkHeight();\n this.pendingCheckHeight = false;\n };\n this.throttledCheckHeight = () => {\n if (this.pendingCheckHeight)\n return;\n this.pendingCheckHeight = true;\n requestAnimationFrame(afCallback);\n };\n window.addEventListener('message', (e) => this.onMessage(e));\n // simple handshake for initialization\n this.postMessageToParent(['df-child', 'init']);\n }\n log(level, ...args) {\n if (level === 'debug' && !this.debug)\n return;\n if (level === 'debug')\n console.timeLog(`d-frame:${this.id}:content`, ...args);\n if (level === 'info')\n console.info(`d-frame:${this.id}:content`, ...args);\n if (level === 'error')\n console.error(`d-frame:${this.id}:content`, ...args);\n }\n onMessage(e) {\n if (e.source === window.parent && Array.isArray(e.data)) {\n const message = e.data;\n if (isInitParentMessage(message)) {\n this.id = message[2].id;\n this.debug = !!message[2].debug;\n if (this.debug)\n console.time(`d-frame:${this.id}:content`);\n this.log('debug', 'init handshake is ok', message, this.options);\n if (message[2].src && message[2].src !== window.location.href) {\n this.log('error', `src mismatch at init, maybe the frame navigated before the initialization handshake: ${window.location.href} !== ${message[2].src}`);\n }\n if (message[2].resize !== 'no')\n this.initResize();\n if (message[2].syncParams || message[2].syncPath || message[2].stateChangeEvents)\n this.initStateChangeWatcher();\n if (message[2].mouseEvents)\n this.initMouseEvents(message[2].mouseEvents);\n this.initialized = true;\n }\n if (isUpdateSrcMessage(message)) {\n // the parent can be any page, it must not be able to run code in this window (javascript: URL)\n if (!isHttpUrl(message[2], window.location.href)) {\n this.log('error', 'refused to navigate to a src that is not a http(s) URL', message[2]);\n return;\n }\n const newSrc = new URL(message[2], window.location.href).href;\n if (newSrc === window.location.href)\n return;\n this.log('debug', `update src ${window.location.href} -> ${newSrc}`);\n if (this.options.updateSrc) {\n this.options.updateSrc(newSrc, this);\n }\n else {\n this.log('debug', 'no updateSrc method available, falling back to updating window.location.href', newSrc);\n window.location.href = newSrc;\n }\n }\n if (isMouseEventMessage(message) && this.mouseEventTypes.includes(message[2])) {\n this.applySyncedMouseEvent(message);\n }\n if (isParentUrlMessage(message)) {\n if (!isHttpUrl(message[3])) {\n // the result is used as a link, it must not be able to run code\n this.log('error', 'refused a parent URL that is not a http(s) URL', message[3]);\n }\n else if (!this.parentUrlListeners[message[2]]) {\n this.log('error', `received getParentUrlResponse message but no matching callback is registered: ${message[2]}`);\n }\n else {\n this.parentUrlResults[message[2]] = message[3];\n for (const listener of this.parentUrlListeners[message[2]]) {\n listener(message[3]);\n }\n }\n }\n }\n }\n postMessageToParent(message) {\n window.parent.postMessage(message, '*');\n }\n sendNotif(notif) {\n this.postMessageToParent(['df-child', 'notif', notif]);\n }\n sendMessage(msg) {\n this.postMessageToParent(['df-child', 'custom', msg]);\n }\n ready() {\n this.postMessageToParent(['df-child', 'ready']);\n }\n reinitHeight() {\n // the parent forgets the height, the next one must be sent even if it did not change\n this.lastHeight = 0;\n this.postMessageToParent(['df-child', 'reinit-height']);\n }\n // this is not a single-time callback, it can be called multiple times when parent url changes\n addParentUrlListener(partialChildHref, listener) {\n const childHref = new URL(partialChildHref, window.location.href).href;\n if (!inIframe) {\n listener(childHref);\n return;\n }\n if (this.parentUrlListeners[childHref]) {\n // the parent already sends the updates of this URL, the result is known or on its way\n this.parentUrlListeners[childHref].push(listener);\n if (childHref in this.parentUrlResults)\n listener(this.parentUrlResults[childHref]);\n return;\n }\n this.parentUrlListeners[childHref] = [listener];\n this.postMessageToParent(['df-child', 'addParentUrlListener', childHref]);\n }\n removeParentUrlListener(partialChildHref, listener) {\n const childHref = new URL(partialChildHref, window.location.href).href;\n if (this.parentUrlListeners[childHref]) {\n this.parentUrlListeners[childHref] = this.parentUrlListeners[childHref].filter(cb => cb !== listener);\n if (!this.parentUrlListeners[childHref].length) {\n delete this.parentUrlListeners[childHref];\n delete this.parentUrlResults[childHref];\n this.postMessageToParent(['df-child', 'removeParentUrlListener', childHref]);\n }\n }\n }\n currentSyncedMouseEvents = new Set();\n mouseEventTypes = [];\n initMouseEvents(types) {\n this.log('debug', 'initMouseEvents');\n this.mouseEventTypes = types;\n for (const eventType of types) {\n document.addEventListener(eventType, (e) => {\n if (!(e instanceof MouseEvent))\n return;\n this.transmitSyncedMouseEvent(['df-global', 'mouse', eventType, { altKey: e.altKey, ctrlKey: e.ctrlKey, shiftKey: e.shiftKey, metaKey: e.metaKey }]);\n }, true);\n }\n }\n transmitSyncedMouseEvent(message) {\n if (this.currentSyncedMouseEvents.has(message[2])) {\n this.currentSyncedMouseEvents.delete(message[2]);\n }\n else {\n this.postMessageToParent(message);\n }\n }\n applySyncedMouseEvent(message) {\n this.currentSyncedMouseEvents.add(message[2]);\n const event = new MouseEvent(message[2], { bubbles: true, cancelable: true, view: window, ...message[3] });\n document.body.dispatchEvent(event);\n }\n initResize() {\n this.log('debug', 'initResize');\n this.checkHeight();\n this.createMutationObserver();\n this.createWindowEventListeners();\n }\n initStateChangeWatcher() {\n this.log('debug', 'initStateChangeWatcher');\n // monkey patch pushState and replaceState to send all state change info to the parent window\n // this is to compensate the lack of event emission by window.history\n const oldReplaceState = window.history.replaceState;\n const newPushState = (...args) => {\n const before = window.location.href;\n // do a replace instead of a push, the push will be done in the parent window if sync-state is activated\n const ret = oldReplaceState.apply(window.history, args);\n if (window.location.href !== before) {\n this.postMessageToParent(['df-child', 'stateChange', 'push', window.location.href]);\n }\n return ret;\n };\n window.history.pushState = newPushState;\n const newReplaceState = (...args) => {\n const before = window.location.href;\n const ret = oldReplaceState.apply(window.history, args);\n if (window.location.href !== before) {\n this.postMessageToParent(['df-child', 'stateChange', 'replace', window.location.href]);\n }\n return ret;\n };\n window.history.replaceState = newReplaceState;\n }\n createMutationObserver() {\n const body = document.querySelector('body');\n if (!body)\n throw new Error('DFrameContentManager was initialized before the HTML body');\n if (!this.throttledCheckHeight)\n throw new Error('throttledCheckHeight is not defined');\n const observer = new window.MutationObserver(this.throttledCheckHeight);\n observer.observe(body, {\n attributes: false,\n attributeOldValue: false,\n characterData: false,\n characterDataOldValue: false,\n childList: true,\n subtree: true,\n });\n }\n createWindowEventListeners() {\n if (!this.throttledCheckHeight)\n throw new Error('throttledCheckHeight is not defined');\n for (const eventType of windowEventTypes) {\n window.addEventListener(eventType, this.throttledCheckHeight, { passive: true });\n }\n }\n checkHeight() {\n const elements = document.querySelectorAll('[data-iframe-height]');\n let maxWithoutOffset = 0;\n let max = 0;\n for (const element of elements) {\n const dataAttribute = element.getAttribute('data-iframe-height');\n if (dataAttribute === 'false' || dataAttribute === 'no')\n continue;\n const bottomWithoutOffset = element.getBoundingClientRect().bottom +\n parseFloat(getComputedStyle(element).getPropertyValue('margin-bottom'));\n const bottom = dataAttribute ? (bottomWithoutOffset + parseFloat(dataAttribute)) : bottomWithoutOffset;\n if (bottomWithoutOffset > maxWithoutOffset)\n maxWithoutOffset = bottomWithoutOffset;\n if (bottom > max)\n max = bottom;\n }\n max = Math.ceil(max);\n if (maxWithoutOffset > 0 && max !== this.lastHeight) {\n this.postMessageToParent(['df-child', 'height', max]);\n this.lastHeight = max;\n }\n }\n}\n","export const applySearchParams = (reactiveParams, searchParams) => {\n searchParams.forEach((value, key) => {\n reactiveParams[key] = value;\n });\n for (const key of Object.keys(reactiveParams)) {\n if (!searchParams.has(key))\n delete reactiveParams[key];\n }\n};\n","import DFrameContent from '../DFrameContent.js'\nimport type { Router } from 'vue-router'\nimport { applySearchParams } from '../vue-reactive/util.js'\n\nexport type VIframeOptions = {\n router?: Router,\n reactiveParams?: Record<string, string> | boolean\n}\n\nconst dFrameContent = new DFrameContent({\n updateSrc (src: string, instance: DFrameContent) {\n const vIframeOptions = (window as Window & { vIframeOptions?: VIframeOptions }).vIframeOptions\n\n const reactiveParams = vIframeOptions?.reactiveParams\n if (reactiveParams && typeof reactiveParams === 'object') {\n const srcUrl = new URL(src)\n if (srcUrl.origin === window.location.origin && srcUrl.pathname === window.location.pathname) {\n instance.log('debug', 'applying reactive params', srcUrl.searchParams)\n applySearchParams(reactiveParams, srcUrl.searchParams)\n return\n }\n }\n\n const router = vIframeOptions?.router\n if (router) {\n // @ts-ignore vue-router v2\n const base = router.options?.base ?? router.options.history?.base\n if (base === null || base === undefined) {\n instance.log('error', 'failed to access base path in router (no router.options.base nor router.options.history.base)', router)\n } else {\n const urlPrefix = window.location.origin + base\n if (!src.startsWith(urlPrefix)) {\n instance.log('info', 'src does not start with base path', src, urlPrefix)\n } else {\n let newRoute = src.replace(urlPrefix, '')\n if (!newRoute.startsWith('/')) newRoute = '/' + newRoute\n instance.log('debug', 'applying router.replace', newRoute)\n router.replace(newRoute)\n return\n }\n }\n }\n\n instance.log('info', 'falling back to updating window.location.href', src)\n window.location.href = src\n }\n});\n\n(window as (Window & { dFrameContent?: DFrameContent })).dFrameContent = dFrameContent\n\nexport default dFrameContent\n"],"names":["isHttpUrl","value","base","isMouseEventMessage","message","isInitParentMessage","isUpdateSrcMessage","isParentUrlMessage","inIframe","windowEventTypes","DFrameContent","options","afCallback","e","level","args","newSrc","listener","notif","msg","partialChildHref","childHref","cb","types","eventType","event","oldReplaceState","newPushState","before","ret","newReplaceState","body","elements","maxWithoutOffset","max","element","dataAttribute","bottomWithoutOffset","bottom","applySearchParams","reactiveParams","searchParams","key","dFrameContent","src","instance","vIframeOptions","srcUrl","router","urlPrefix","newRoute"],"mappings":"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"}
@@ -4,7 +4,7 @@ export type DFrameContentOptions = {
4
4
  };
5
5
  declare global {
6
6
  interface Window {
7
- __d_frame__content: DFrameContent;
7
+ __d_frame__content?: DFrameContent;
8
8
  }
9
9
  }
10
10
  export default class DFrameContent {
@@ -28,6 +28,7 @@ export default class DFrameContent {
28
28
  addParentUrlListener(partialChildHref: string, listener: (result: string) => any): void;
29
29
  removeParentUrlListener(partialChildHref: string, listener: (result: string) => any): void;
30
30
  private currentSyncedMouseEvents;
31
+ private mouseEventTypes;
31
32
  private initMouseEvents;
32
33
  private transmitSyncedMouseEvent;
33
34
  private applySyncedMouseEvent;
@@ -1,35 +1,23 @@
1
1
  import { isInitParentMessage, isMouseEventMessage, isUpdateSrcMessage, isParentUrlMessage } from './messages.js';
2
2
  import inIframe from './utils/in-iframe.js';
3
+ import { isHttpUrl } from './utils/url.js';
4
+ // prefixed variants are not listed: browsers do not fire them when there is an unprefixed listener
3
5
  const windowEventTypes = [
4
6
  'animationstart',
5
- 'webkitAnimationStart',
6
7
  'animationiteration',
7
- 'webkitAnimationIteration',
8
8
  'animationend',
9
- 'webkitAnimationEnd',
10
9
  'input',
11
10
  'mouseup',
12
11
  'mousedown',
13
12
  'orientationchange',
14
13
  'afterprint',
15
14
  'beforeprint',
16
- 'readystatechange',
17
15
  'touchstart',
18
16
  'touchend',
19
17
  'touchcancel',
20
18
  'transitionstart',
21
- 'webkitTransitionStart',
22
- 'MSTransitionStart',
23
- 'oTransitionStart',
24
- 'otransitionstart', 'transitioniteration',
25
- 'webkitTransitionIteration',
26
- 'MSTransitionIteration',
27
- 'oTransitionIteration',
28
- 'otransitioniteration', 'transitionend',
29
- 'webkitTransitionEnd',
30
- 'MSTransitionEnd',
31
- 'oTransitionEnd',
32
- 'otransitionend', 'resize'
19
+ 'transitionend',
20
+ 'resize'
33
21
  ];
34
22
  export default class DFrameContent {
35
23
  initialized = false;
@@ -94,7 +82,12 @@ export default class DFrameContent {
94
82
  this.initialized = true;
95
83
  }
96
84
  if (isUpdateSrcMessage(message)) {
97
- const newSrc = message[2].startsWith('/') ? window.location.origin + message[2] : message[2];
85
+ // the parent can be any page, it must not be able to run code in this window (javascript: URL)
86
+ if (!isHttpUrl(message[2], window.location.href)) {
87
+ this.log('error', 'refused to navigate to a src that is not a http(s) URL', message[2]);
88
+ return;
89
+ }
90
+ const newSrc = new URL(message[2], window.location.href).href;
98
91
  if (newSrc === window.location.href)
99
92
  return;
100
93
  this.log('debug', `update src ${window.location.href} -> ${newSrc}`);
@@ -106,14 +99,19 @@ export default class DFrameContent {
106
99
  window.location.href = newSrc;
107
100
  }
108
101
  }
109
- if (isMouseEventMessage(message)) {
102
+ if (isMouseEventMessage(message) && this.mouseEventTypes.includes(message[2])) {
110
103
  this.applySyncedMouseEvent(message);
111
104
  }
112
105
  if (isParentUrlMessage(message)) {
113
- if (!this.parentUrlListeners[message[2]]) {
106
+ if (!isHttpUrl(message[3])) {
107
+ // the result is used as a link, it must not be able to run code
108
+ this.log('error', 'refused a parent URL that is not a http(s) URL', message[3]);
109
+ }
110
+ else if (!this.parentUrlListeners[message[2]]) {
114
111
  this.log('error', `received getParentUrlResponse message but no matching callback is registered: ${message[2]}`);
115
112
  }
116
113
  else {
114
+ this.parentUrlResults[message[2]] = message[3];
117
115
  for (const listener of this.parentUrlListeners[message[2]]) {
118
116
  listener(message[3]);
119
117
  }
@@ -134,6 +132,8 @@ export default class DFrameContent {
134
132
  this.postMessageToParent(['df-child', 'ready']);
135
133
  }
136
134
  reinitHeight() {
135
+ // the parent forgets the height, the next one must be sent even if it did not change
136
+ this.lastHeight = 0;
137
137
  this.postMessageToParent(['df-child', 'reinit-height']);
138
138
  }
139
139
  // this is not a single-time callback, it can be called multiple times when parent url changes
@@ -143,11 +143,14 @@ export default class DFrameContent {
143
143
  listener(childHref);
144
144
  return;
145
145
  }
146
- if (this.parentUrlResults[childHref]) {
147
- listener(this.parentUrlResults[childHref]);
146
+ if (this.parentUrlListeners[childHref]) {
147
+ // the parent already sends the updates of this URL, the result is known or on its way
148
+ this.parentUrlListeners[childHref].push(listener);
149
+ if (childHref in this.parentUrlResults)
150
+ listener(this.parentUrlResults[childHref]);
151
+ return;
148
152
  }
149
- this.parentUrlListeners[childHref] = this.parentUrlListeners[childHref] ?? [];
150
- this.parentUrlListeners[childHref].push(listener);
153
+ this.parentUrlListeners[childHref] = [listener];
151
154
  this.postMessageToParent(['df-child', 'addParentUrlListener', childHref]);
152
155
  }
153
156
  removeParentUrlListener(partialChildHref, listener) {
@@ -156,13 +159,16 @@ export default class DFrameContent {
156
159
  this.parentUrlListeners[childHref] = this.parentUrlListeners[childHref].filter(cb => cb !== listener);
157
160
  if (!this.parentUrlListeners[childHref].length) {
158
161
  delete this.parentUrlListeners[childHref];
162
+ delete this.parentUrlResults[childHref];
159
163
  this.postMessageToParent(['df-child', 'removeParentUrlListener', childHref]);
160
164
  }
161
165
  }
162
166
  }
163
167
  currentSyncedMouseEvents = new Set();
168
+ mouseEventTypes = [];
164
169
  initMouseEvents(types) {
165
170
  this.log('debug', 'initMouseEvents');
171
+ this.mouseEventTypes = types;
166
172
  for (const eventType of types) {
167
173
  document.addEventListener(eventType, (e) => {
168
174
  if (!(e instanceof MouseEvent))
@@ -56,7 +56,7 @@ export class DFrameElement extends HTMLElement {
56
56
  }
57
57
  get src() { return this.getAttribute('src'); }
58
58
  set src(value) { this.setAttribute('src', value); }
59
- get fullSrc() { return this.src.startsWith('/') ? (window.location.origin + this.src) : this.src; }
59
+ get fullSrc() { return new URL(this.src, window.location.href).href; }
60
60
  get scrolling() { return this.getAttribute('scrolling'); }
61
61
  set scrolling(value) { this.setAttribute('scrolling', value); }
62
62
  get aspectRatio() {
@@ -109,9 +109,9 @@ export class DFrameElement extends HTMLElement {
109
109
  }
110
110
  set syncPath(value) {
111
111
  if (value !== null)
112
- this.setAttribute('sync-params', value);
112
+ this.setAttribute('sync-path', value);
113
113
  else
114
- this.removeAttribute('sync-params');
114
+ this.removeAttribute('sync-path');
115
115
  }
116
116
  get stateChangeEvents() { return this.hasAttribute('state-change-events'); }
117
117
  set stateChangeEvents(value) {
@@ -235,6 +235,8 @@ export class DFrameElement extends HTMLElement {
235
235
  src: this.currentChildSrc
236
236
  }]);
237
237
  this.childInitialized = true;
238
+ // a new document starts in the iframe, it registers its own parent URL listeners
239
+ this.parentUrlListeners = {};
238
240
  }
239
241
  if (isHeightMessage(message)) {
240
242
  this.resizedHeight = message[2];
@@ -246,6 +248,8 @@ export class DFrameElement extends HTMLElement {
246
248
  }
247
249
  if (isReinitHeightMessage(message) && this.reinitHeight === 'on-message') {
248
250
  this.resizedHeight = undefined;
251
+ // not maintained while the height came from the child
252
+ this.updateAspectRatioHeight();
249
253
  this.updateStyle();
250
254
  }
251
255
  if (isStateChangeMessage(message)) {
@@ -283,7 +287,7 @@ export class DFrameElement extends HTMLElement {
283
287
  this.dispatchEvent(new CustomEvent('ready'));
284
288
  this.updateStyle();
285
289
  }
286
- if (isMouseEventMessage(message)) {
290
+ if (isMouseEventMessage(message) && this.mouseEvents?.includes(message[2])) {
287
291
  this.applySyncedMouseEvent(message);
288
292
  }
289
293
  if (isAddParentUrlListenerMessage(message)) {
@@ -311,7 +315,8 @@ export class DFrameElement extends HTMLElement {
311
315
  this.parsedSyncParams = this.syncParams !== null ? parseSyncParams(this.syncParams || '*') : undefined;
312
316
  }
313
317
  updateSrc() {
314
- if (!this.connected)
318
+ // an empty src is not known yet (<d-frame :src="url">), resolved against the page it would load the parent itself
319
+ if (!this.connected || !this.src)
315
320
  return;
316
321
  for (const [childHref, existingParentUrl] of Object.entries(this.parentUrlListeners)) {
317
322
  const parentUrl = getParentUrl(this.fullSrc, childHref, window.location.href, this.parsedSyncParams, this.syncPath);
@@ -350,7 +355,7 @@ export class DFrameElement extends HTMLElement {
350
355
  this.log('debug', 'height of iframe based on direct attribute: ' + this.height);
351
356
  wrapperHeight = this.height;
352
357
  }
353
- else if (this.aspectRatio !== null) {
358
+ else if (this.aspectRatio !== null && this.aspectRatioHeight) {
354
359
  this.log('debug', 'height of iframe based on aspect ratio: ' + this.aspectRatioHeight);
355
360
  wrapperHeight = this.aspectRatioHeight + 'px';
356
361
  }
@@ -465,7 +470,13 @@ export class DFrameElement extends HTMLElement {
465
470
  /* standard custom element callbacks */
466
471
  connectedCallback() {
467
472
  if (!this.debug) {
468
- const globalDebug = window.localStorage.getItem('debug');
473
+ let globalDebug = null;
474
+ try {
475
+ globalDebug = window.localStorage.getItem('debug');
476
+ }
477
+ catch {
478
+ // storage is blocked in some contexts (sandboxed parent, blocked cookies)
479
+ }
469
480
  if (globalDebug) {
470
481
  const debugParts = globalDebug.split(',');
471
482
  if (debugParts.includes('d-frame') || debugParts.includes('d-frame:' + this.id))
@@ -481,10 +492,12 @@ export class DFrameElement extends HTMLElement {
481
492
  if (this.adapter.onStateChange) {
482
493
  this.stopOnStateChange = this.adapter.onStateChange(() => { this.updateSrc(); });
483
494
  }
484
- // measure synchronously: the observer only fires after the next layout, and the frame
485
- // would be painted at the height of its loading slot in between
486
- this.width = this.getBoundingClientRect().width;
487
- this.updateAspectRatioHeight();
495
+ if (this.aspectRatio !== null && !this.height) {
496
+ // measure synchronously: the observer only fires after the next layout, and the frame
497
+ // would be painted at the height of its loading slot in between
498
+ this.width = this.getBoundingClientRect().width;
499
+ this.updateAspectRatioHeight();
500
+ }
488
501
  this.resizeObserver.observe(this);
489
502
  window.addEventListener('message', this.boundOnMessage);
490
503
  this.updateParsedSyncParams();
@@ -496,6 +509,12 @@ export class DFrameElement extends HTMLElement {
496
509
  }
497
510
  disconnectedCallback() {
498
511
  this.log('debug', 'disconnected');
512
+ this.connected = false;
513
+ // the iframe is reloaded from its src attribute when the element is connected again:
514
+ // it must be the state reached by the child, then the child does the handshake again
515
+ this.childInitialized = false;
516
+ if (this.currentChildSrc)
517
+ this.iframeElement.setAttribute('src', this.currentChildSrc);
499
518
  this.resizeObserver.disconnect();
500
519
  window.removeEventListener('message', this.boundOnMessage);
501
520
  this.stopOnStateChange?.();
@@ -508,16 +527,22 @@ export class DFrameElement extends HTMLElement {
508
527
  /* reflected attributes */
509
528
  static get observedAttributes() { return ['src', 'aspect-ratio', 'height', 'sync-params', 'sync-path', 'reload']; }
510
529
  attributeChangedCallback(name, oldValue, newValue) {
511
- if (name === 'aspect-ratio')
530
+ if (name === 'aspect-ratio') {
512
531
  this.updateAspectRatioHeight();
532
+ // also when the attribute is removed
533
+ this.updateStyle();
534
+ }
513
535
  // if (name.startsWith('iframe-')) this.updateIframeExtraAttrs()
514
536
  if (!this.connected)
515
537
  return;
516
538
  this.log('debug', 'attribute change', name, oldValue, newValue);
517
539
  if (name === 'src')
518
540
  this.updateSrc();
519
- if (name === 'height')
541
+ if (name === 'height') {
542
+ // the aspect ratio height is not maintained while a height is set
543
+ this.updateAspectRatioHeight();
520
544
  this.updateStyle();
545
+ }
521
546
  if (name === 'sync-params') {
522
547
  this.updateParsedSyncParams();
523
548
  this.updateSrc();
@@ -529,10 +554,6 @@ export class DFrameElement extends HTMLElement {
529
554
  this.log('error', 'reload attribute changed but the iframe has no current src');
530
555
  return;
531
556
  }
532
- if (!this.iframeElement.contentWindow?.location) {
533
- this.log('debug', 'reload attribute changed but no iframe contentWindow, is this a cross domain iframe ?');
534
- return;
535
- }
536
557
  if (!this.iframeLoaded) {
537
558
  this.log('debug', 'reload attribute changed before initial loading was complete, ignore it');
538
559
  return;
@@ -540,7 +561,12 @@ export class DFrameElement extends HTMLElement {
540
561
  this.log('debug', 'reload iframe with current src', this.currentChildSrc);
541
562
  this.childInitialized = false;
542
563
  this.iframeElement.setAttribute('src', this.currentChildSrc);
543
- this.iframeElement.contentWindow.location.reload();
564
+ try {
565
+ this.iframeElement.contentWindow?.location.reload();
566
+ }
567
+ catch {
568
+ // cross-origin child: reload() is forbidden, the src attribute set above already navigates the frame
569
+ }
544
570
  }
545
571
  }
546
572
  }
package/lib/messages.js CHANGED
@@ -1,17 +1,20 @@
1
+ import { isHttpUrl } from './utils/url.js';
1
2
  export function isMouseEventMessage(message) {
2
- return message[0] === 'df-global' && message[1] === 'mouse';
3
+ return message[0] === 'df-global' && message[1] === 'mouse' && typeof message[2] === 'string';
3
4
  }
4
5
  export function isInitChildMessage(message) {
5
6
  return message[0] === 'df-child' && message[1] === 'init';
6
7
  }
7
8
  export function isHeightMessage(message) {
8
- return message[0] === 'df-child' && message[1] === 'height';
9
+ // the height is written in the style of the wrapper, anything else than a number would inject css
10
+ return message[0] === 'df-child' && message[1] === 'height' && typeof message[2] === 'number' && Number.isFinite(message[2]) && message[2] >= 0;
9
11
  }
10
12
  export function isReinitHeightMessage(message) {
11
13
  return message[0] === 'df-child' && message[1] === 'reinit-height';
12
14
  }
15
+ // the URL of the child becomes the src of the iframe when it is reloaded: a javascript: URL would run in the parent
13
16
  export function isStateChangeMessage(message) {
14
- return message[0] === 'df-child' && message[1] === 'stateChange';
17
+ return message[0] === 'df-child' && message[1] === 'stateChange' && (message[2] === 'push' || message[2] === 'replace') && isHttpUrl(message[3]);
15
18
  }
16
19
  export function isCustomMessage(message) {
17
20
  return message[0] === 'df-child' && message[1] === 'custom';
@@ -20,7 +23,7 @@ export function isNotifMessage(message) {
20
23
  return message[0] === 'df-child' && message[1] === 'notif';
21
24
  }
22
25
  export function isAddParentUrlListenerMessage(message) {
23
- return message[0] === 'df-child' && message[1] === 'addParentUrlListener';
26
+ return message[0] === 'df-child' && message[1] === 'addParentUrlListener' && isHttpUrl(message[2]);
24
27
  }
25
28
  export function isRemoveParentUrlListenerMessage(message) {
26
29
  return message[0] === 'df-child' && message[1] === 'removeParentUrlListener';
@@ -3,7 +3,7 @@ const escapeRegExp = (str) => str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
3
3
  export function parseSyncParams(syncParamsStr) {
4
4
  const syncParams = [];
5
5
  for (const syncParam of syncParamsStr.split(',')) {
6
- const [pattern = '*', append] = syncParam.split(':');
6
+ const [pattern = '*', append] = syncParam.trim().split(':');
7
7
  const regexpStr = '^' + escapeRegExp(pattern).replace(/\\\*/g, '(.*)') + '$';
8
8
  syncParams.push({
9
9
  regexp: new RegExp(regexpStr),
@@ -60,8 +60,8 @@ export function getParentUrl(fullSrc, childHref, currentParentHref, syncParams,
60
60
  const childUrl = new URL(childHref);
61
61
  if (syncParams) {
62
62
  // remove existing keys that were deleted from the child
63
- // use toArray instead of iterator as we mutate parentUrl in the loop
64
- for (const key of parentUrl.searchParams.keys().toArray()) {
63
+ // copy the keys as we mutate parentUrl in the loop (not with Iterator.toArray(), missing in Safari < 18.4)
64
+ for (const key of Array.from(parentUrl.searchParams.keys())) {
65
65
  for (const syncParam of syncParams) {
66
66
  if (syncParam.append && !key.startsWith(syncParam.append))
67
67
  continue;
@@ -78,10 +78,12 @@ export function getParentUrl(fullSrc, childHref, currentParentHref, syncParams,
78
78
  for (const syncParam of syncParams) {
79
79
  if (!key.match(syncParam.regexp))
80
80
  continue;
81
- if (srcUrl?.searchParams.get(key) === value)
82
- continue;
83
81
  const parentKey = syncParam.append ? (syncParam.append + key) : key;
84
- parentUrl.searchParams.set(parentKey, value);
82
+ // the value of src is the default one: no need to keep it in the parent, but a previous value must go
83
+ if (srcUrl.searchParams.get(key) === value)
84
+ parentUrl.searchParams.delete(parentKey);
85
+ else
86
+ parentUrl.searchParams.set(parentKey, value);
85
87
  break;
86
88
  }
87
89
  });
@@ -92,8 +94,8 @@ export function getParentUrl(fullSrc, childHref, currentParentHref, syncParams,
92
94
  parentUrl.hash = path;
93
95
  }
94
96
  else if (syncPath.startsWith('/')) {
95
- // a full path is not accepted, only a path child of the base
96
- if (path.startsWith('/'))
97
+ // a full path or another origin is not accepted, only a path child of the base
98
+ if (path.startsWith('/') || childUrl.origin !== srcUrl.origin)
97
99
  return null;
98
100
  if (path === '')
99
101
  parentUrl.pathname = syncPath;
@@ -1,3 +1,4 @@
1
1
  export declare function getPathDirectory(path: string): string;
2
2
  export declare function getPathWithTrailingSlash(path: string): string;
3
3
  export declare function getUrlRelativePath(srcUrl: URL, currentUrl: URL): string;
4
+ export declare function isHttpUrl(value: unknown, base?: string): value is string;
package/lib/utils/url.js CHANGED
@@ -20,3 +20,14 @@ export function getUrlRelativePath(srcUrl, currentUrl) {
20
20
  return currentUrl.pathname;
21
21
  return currentUrl.pathname.replace(srcDirectory, '');
22
22
  }
23
+ // a URL that can be navigated to or used as a link without running code (no javascript:, data:...)
24
+ export function isHttpUrl(value, base) {
25
+ if (typeof value !== 'string')
26
+ return false;
27
+ try {
28
+ return ['http:', 'https:'].includes(new URL(value, base).protocol);
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
@@ -1,5 +1,5 @@
1
1
  export function isVIframeUiNotif(message) {
2
- return message.vIframe === true && message.uiNotification !== undefined;
2
+ return message?.vIframe === true && message.uiNotification !== undefined && message.uiNotification !== null;
3
3
  }
4
4
  export function convertVIframeUiNotif(notif) {
5
5
  if (typeof notif === 'string')
@@ -1,6 +1,6 @@
1
1
  import { onScopeDispose, toRef, reactive } from 'vue';
2
2
  export const useDFrameParentUrls = (dFrameContent, router) => {
3
- console.warn('useDFrameParentUrls is deprecated, it is replaced by useParentUrl from @data-fair/frame/vue-router/use-parent-url.js');
3
+ console.warn('useDFrameParentUrls is deprecated, it is replaced by useParentUrl from @data-fair/frame/lib/vue-router/use-parent-url.js');
4
4
  let parentUrls = reactive({});
5
5
  let listeners = {};
6
6
  const get = (childHref) => {
@@ -1,10 +1,9 @@
1
1
  export const applySearchParams = (reactiveParams, searchParams) => {
2
- const existingKeys = Object.keys(reactiveParams);
3
2
  searchParams.forEach((value, key) => {
4
3
  reactiveParams[key] = value;
5
- existingKeys.splice(existingKeys.indexOf(key), 1);
6
- });
7
- existingKeys.forEach(key => {
8
- delete reactiveParams[key];
9
4
  });
5
+ for (const key of Object.keys(reactiveParams)) {
6
+ if (!searchParams.has(key))
7
+ delete reactiveParams[key];
8
+ }
10
9
  };
@@ -17,13 +17,23 @@ class VueRouterDFrameStateChangeAdapter {
17
17
  if (!newRoute.startsWith('/'))
18
18
  newRoute = '/' + newRoute;
19
19
  element.log('debug', 'apply state change on vue.router', action, newRoute);
20
+ // a guard that throws rejects the navigation without calling afterEach: the parent stays on its URL,
21
+ // the child is brought back to it like after an aborted navigation
22
+ const onError = (err) => {
23
+ element.log('error', 'failed to apply state change on vue.router', err);
24
+ element.updateSrc();
25
+ };
20
26
  if (action === 'push')
21
- this.router.push(newRoute);
27
+ this.router.push(newRoute).catch(onError);
22
28
  if (action === 'replace')
23
- this.router.replace(newRoute);
29
+ this.router.replace(newRoute).catch(onError);
24
30
  }
25
31
  onStateChange(callback) {
26
- return this.router.afterEach(callback);
32
+ // a cancelled navigation is superseded by a newer one: syncing the child from the parent URL
33
+ // would send back stale params while the child has already moved on (fast clicks in the child).
34
+ // An aborted one (4 = NavigationFailureType.aborted) was refused by a guard: the child must go back to the parent URL
35
+ return this.router.afterEach((_to, _from, failure) => { if (!failure || failure.type === 4)
36
+ callback(); });
27
37
  }
28
38
  }
29
39
  export default (router) => new VueRouterDFrameStateChangeAdapter(router);
@@ -17,16 +17,21 @@ export const useParentUrl = (location, router, dFrameContent) => {
17
17
  let listener = null;
18
18
  const parentUrl = ref(null);
19
19
  watch(() => toValue(location), (to) => {
20
+ const href = to === undefined ? undefined : router.resolve(to).href;
21
+ // a getter can return a new location object for the same target: keep the listener and its result
22
+ if (href === resolved)
23
+ return;
20
24
  if (listener && resolved)
21
25
  dFrameContent.removeParentUrlListener(resolved, listener);
22
- if (to === undefined) {
23
- parentUrl.value = to;
26
+ resolved = href;
27
+ listener = null;
28
+ if (href === undefined) {
29
+ parentUrl.value = undefined;
24
30
  }
25
31
  else {
26
32
  parentUrl.value = null;
27
- resolved = router.resolve(to).href;
28
33
  listener = (result) => { parentUrl.value = result; };
29
- dFrameContent.addParentUrlListener(resolved, listener);
34
+ dFrameContent.addParentUrlListener(href, listener);
30
35
  }
31
36
  }, { immediate: true });
32
37
  onScopeDispose(() => {