Skip to content

Commit 56041bc

Browse files
committed
[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
1 parent e788e36 commit 56041bc

File tree

4 files changed

+68
-14
lines changed

4 files changed

+68
-14
lines changed

docs/owl.js

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -276,13 +276,39 @@ function inOwnerDocument(el) {
276276
const rootNode = el.getRootNode();
277277
return rootNode instanceof ShadowRoot && el.ownerDocument.contains(rootNode.host);
278278
}
279+
/**
280+
* Determine whether the given element is contained in a specific root documnet:
281+
* either directly or with a shadow root in between or in an iframe.
282+
*/
283+
function isAttachedToDocument(element, documentElement) {
284+
let current = element;
285+
const shadowRoot = documentElement.defaultView.ShadowRoot;
286+
while (current) {
287+
if (current === documentElement) {
288+
return true;
289+
}
290+
if (current.parentNode) {
291+
current = current.parentNode;
292+
}
293+
else if (current instanceof shadowRoot && current.host) {
294+
current = current.host;
295+
}
296+
else {
297+
return false;
298+
}
299+
}
300+
return false;
301+
}
279302
function validateTarget(target) {
280303
// Get the document and HTMLElement corresponding to the target to allow mounting in iframes
281304
const document = target && target.ownerDocument;
282305
if (document) {
306+
if (!document.defaultView) {
307+
throw new OwlError("Cannot mount a component: the target document is not attached to a window (defaultView is missing)");
308+
}
283309
const HTMLElement = document.defaultView.HTMLElement;
284310
if (target instanceof HTMLElement || target instanceof ShadowRoot) {
285-
if (!document.body.contains(target instanceof HTMLElement ? target : target.host)) {
311+
if (!isAttachedToDocument(target, document)) {
286312
throw new OwlError("Cannot mount a component on a detached dom node");
287313
}
288314
return;
@@ -319,12 +345,40 @@ async function loadFile(url) {
319345
*/
320346
class Markup extends String {
321347
}
322-
/*
323-
* Marks a value as safe, that is, a value that can be injected as HTML directly.
324-
* It should be used to wrap the value passed to a t-out directive to allow a raw rendering.
325-
*/
326-
function markup(value) {
327-
return new Markup(value);
348+
function htmlEscape(str) {
349+
if (str instanceof Markup) {
350+
return str;
351+
}
352+
if (str === undefined) {
353+
return markup("");
354+
}
355+
if (typeof str === "number") {
356+
return markup(String(str));
357+
}
358+
[
359+
["&", "&"],
360+
["<", "&lt;"],
361+
[">", "&gt;"],
362+
["'", "&#x27;"],
363+
['"', "&quot;"],
364+
["`", "&#x60;"],
365+
].forEach((pairs) => {
366+
str = String(str).replace(new RegExp(pairs[0], "g"), pairs[1]);
367+
});
368+
return markup(str);
369+
}
370+
function markup(valueOrStrings, ...placeholders) {
371+
if (!Array.isArray(valueOrStrings)) {
372+
return new Markup(valueOrStrings);
373+
}
374+
const strings = valueOrStrings;
375+
let acc = "";
376+
let i = 0;
377+
for (; i < placeholders.length; ++i) {
378+
acc += strings[i] + htmlEscape(placeholders[i]);
379+
}
380+
acc += strings[i];
381+
return new Markup(acc);
328382
}
329383

330384
function createEventHandler(rawEvent) {
@@ -5704,7 +5758,7 @@ function compile(template, options = {
57045758
}
57055759

57065760
// do not modify manually. This file is generated by the release script.
5707-
const version = "2.6.1";
5761+
const version = "2.7.0";
57085762

57095763
// -----------------------------------------------------------------------------
57105764
// Scheduler
@@ -6172,9 +6226,9 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate(name, templat
61726226
});
61736227
};
61746228

6175-
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 };
6229+
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 };
61766230

61776231

6178-
__info__.date = '2025-03-05T08:37:58.580Z';
6179-
__info__.hash = '2b5cea9';
6232+
__info__.date = '2025-03-26T12:58:40.935Z';
6233+
__info__.hash = 'e788e36';
61806234
__info__.url = 'https://github.com/odoo/owl';

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@odoo/owl",
3-
"version": "2.6.1",
3+
"version": "2.7.0",
44
"description": "Odoo Web Library (OWL)",
55
"main": "dist/owl.cjs.js",
66
"module": "dist/owl.es.js",

src/version.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
// do not modify manually. This file is generated by the release script.
2-
export const version = "2.6.1";
2+
export const version = "2.7.0";

0 commit comments

Comments
 (0)