From 56041bc1334cdf1ab6d5d364e8295ca3f4cfbfd4 Mon Sep 17 00:00:00 2001 From: Romeo Fragomeli Date: Wed, 26 Mar 2025 13:58:50 +0100 Subject: [PATCH] [REL] v2.7.0 # v2.7.0 - [IMP] runtime/utils: export htmlEscape and add tests - [FIX] utils: Correct validation of mount target in shadow DOM/iframe - [IMP] runtime: add markup tag function --- docs/owl.js | 76 ++++++++++++++++++++++++++++++++++++++++------- package-lock.json | 2 +- package.json | 2 +- src/version.ts | 2 +- 4 files changed, 68 insertions(+), 14 deletions(-) diff --git a/docs/owl.js b/docs/owl.js index 4d536ea0..9b956af3 100644 --- a/docs/owl.js +++ b/docs/owl.js @@ -276,13 +276,39 @@ function inOwnerDocument(el) { const rootNode = el.getRootNode(); return rootNode instanceof ShadowRoot && el.ownerDocument.contains(rootNode.host); } +/** + * Determine whether the given element is contained in a specific root documnet: + * either directly or with a shadow root in between or in an iframe. + */ +function isAttachedToDocument(element, documentElement) { + let current = element; + const shadowRoot = documentElement.defaultView.ShadowRoot; + while (current) { + if (current === documentElement) { + return true; + } + if (current.parentNode) { + current = current.parentNode; + } + else if (current instanceof shadowRoot && current.host) { + current = current.host; + } + else { + return false; + } + } + return false; +} function validateTarget(target) { // Get the document and HTMLElement corresponding to the target to allow mounting in iframes const document = target && target.ownerDocument; if (document) { + if (!document.defaultView) { + throw new OwlError("Cannot mount a component: the target document is not attached to a window (defaultView is missing)"); + } const HTMLElement = document.defaultView.HTMLElement; if (target instanceof HTMLElement || target instanceof ShadowRoot) { - if (!document.body.contains(target instanceof HTMLElement ? target : target.host)) { + if (!isAttachedToDocument(target, document)) { throw new OwlError("Cannot mount a component on a detached dom node"); } return; @@ -319,12 +345,40 @@ async function loadFile(url) { */ class Markup extends String { } -/* - * Marks a value as safe, that is, a value that can be injected as HTML directly. - * It should be used to wrap the value passed to a t-out directive to allow a raw rendering. - */ -function markup(value) { - return new Markup(value); +function htmlEscape(str) { + if (str instanceof Markup) { + return str; + } + if (str === undefined) { + return markup(""); + } + if (typeof str === "number") { + return markup(String(str)); + } + [ + ["&", "&"], + ["<", "<"], + [">", ">"], + ["'", "'"], + ['"', """], + ["`", "`"], + ].forEach((pairs) => { + str = String(str).replace(new RegExp(pairs[0], "g"), pairs[1]); + }); + return markup(str); +} +function markup(valueOrStrings, ...placeholders) { + if (!Array.isArray(valueOrStrings)) { + return new Markup(valueOrStrings); + } + const strings = valueOrStrings; + let acc = ""; + let i = 0; + for (; i < placeholders.length; ++i) { + acc += strings[i] + htmlEscape(placeholders[i]); + } + acc += strings[i]; + return new Markup(acc); } function createEventHandler(rawEvent) { @@ -5704,7 +5758,7 @@ function compile(template, options = { } // do not modify manually. This file is generated by the release script. -const version = "2.6.1"; +const version = "2.7.0"; // ----------------------------------------------------------------------------- // Scheduler @@ -6172,9 +6226,9 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate(name, templat }); }; -export { App, Component, EventBus, OwlError, __info__, batched, blockDom, loadFile, markRaw, markup, mount, onError, onMounted, onPatched, onRendered, onWillDestroy, onWillPatch, onWillRender, onWillStart, onWillUnmount, onWillUpdateProps, reactive, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, validate, validateType, whenReady, xml }; +export { App, Component, EventBus, OwlError, __info__, batched, blockDom, htmlEscape, loadFile, markRaw, markup, mount, onError, onMounted, onPatched, onRendered, onWillDestroy, onWillPatch, onWillRender, onWillStart, onWillUnmount, onWillUpdateProps, reactive, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, validate, validateType, whenReady, xml }; -__info__.date = '2025-03-05T08:37:58.580Z'; -__info__.hash = '2b5cea9'; +__info__.date = '2025-03-26T12:58:40.935Z'; +__info__.hash = 'e788e36'; __info__.url = 'https://github.com/odoo/owl'; diff --git a/package-lock.json b/package-lock.json index 9471f951..bddb5ec1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@odoo/owl", - "version": "2.6.1", + "version": "2.7.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 5bc01423..788751df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@odoo/owl", - "version": "2.6.1", + "version": "2.7.0", "description": "Odoo Web Library (OWL)", "main": "dist/owl.cjs.js", "module": "dist/owl.es.js", diff --git a/src/version.ts b/src/version.ts index 9f2fee00..9c3c7e3d 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,2 +1,2 @@ // do not modify manually. This file is generated by the release script. -export const version = "2.6.1"; +export const version = "2.7.0";