[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
This commit is contained in:
Romeo Fragomeli
2025-03-26 13:58:50 +01:00
parent e788e361c7
commit 56041bc133
4 changed files with 68 additions and 14 deletions
+65 -11
View File
@@ -276,13 +276,39 @@ function inOwnerDocument(el) {
const rootNode = el.getRootNode(); const rootNode = el.getRootNode();
return rootNode instanceof ShadowRoot && el.ownerDocument.contains(rootNode.host); 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) { function validateTarget(target) {
// Get the document and HTMLElement corresponding to the target to allow mounting in iframes // Get the document and HTMLElement corresponding to the target to allow mounting in iframes
const document = target && target.ownerDocument; const document = target && target.ownerDocument;
if (document) { 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; const HTMLElement = document.defaultView.HTMLElement;
if (target instanceof HTMLElement || target instanceof ShadowRoot) { 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"); throw new OwlError("Cannot mount a component on a detached dom node");
} }
return; return;
@@ -319,12 +345,40 @@ async function loadFile(url) {
*/ */
class Markup extends String { class Markup extends String {
} }
/* function htmlEscape(str) {
* Marks a value as safe, that is, a value that can be injected as HTML directly. if (str instanceof Markup) {
* It should be used to wrap the value passed to a t-out directive to allow a raw rendering. return str;
*/ }
function markup(value) { if (str === undefined) {
return new Markup(value); return markup("");
}
if (typeof str === "number") {
return markup(String(str));
}
[
["&", "&"],
["<", "&lt;"],
[">", "&gt;"],
["'", "&#x27;"],
['"', "&quot;"],
["`", "&#x60;"],
].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) { function createEventHandler(rawEvent) {
@@ -5704,7 +5758,7 @@ function compile(template, options = {
} }
// do not modify manually. This file is generated by the release script. // do not modify manually. This file is generated by the release script.
const version = "2.6.1"; const version = "2.7.0";
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Scheduler // 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__.date = '2025-03-26T12:58:40.935Z';
__info__.hash = '2b5cea9'; __info__.hash = 'e788e36';
__info__.url = 'https://github.com/odoo/owl'; __info__.url = 'https://github.com/odoo/owl';
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@odoo/owl", "name": "@odoo/owl",
"version": "2.6.1", "version": "2.7.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@odoo/owl", "name": "@odoo/owl",
"version": "2.6.1", "version": "2.7.0",
"description": "Odoo Web Library (OWL)", "description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js", "main": "dist/owl.cjs.js",
"module": "dist/owl.es.js", "module": "dist/owl.es.js",
+1 -1
View File
@@ -1,2 +1,2 @@
// do not modify manually. This file is generated by the release script. // do not modify manually. This file is generated by the release script.
export const version = "2.6.1"; export const version = "2.7.0";