mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] component: validate mounting target at patch time
This commit is contained in:
+2
-10
@@ -4,6 +4,7 @@ import { MountOptions } from "../component/fibers";
|
|||||||
import { Scheduler } from "../component/scheduler";
|
import { Scheduler } from "../component/scheduler";
|
||||||
import { TemplateSet, TemplateSetConfig } from "./template_set";
|
import { TemplateSet, TemplateSetConfig } from "./template_set";
|
||||||
import { nodeErrorHandlers } from "../component/error_handling";
|
import { nodeErrorHandlers } from "../component/error_handling";
|
||||||
|
import { validateTarget } from "../utils";
|
||||||
|
|
||||||
// reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f
|
// reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f
|
||||||
|
|
||||||
@@ -48,22 +49,13 @@ export class App<
|
|||||||
}
|
}
|
||||||
|
|
||||||
mount(target: HTMLElement, options?: MountOptions): Promise<Component<P, E> & InstanceType<T>> {
|
mount(target: HTMLElement, options?: MountOptions): Promise<Component<P, E> & InstanceType<T>> {
|
||||||
this.checkTarget(target);
|
validateTarget(target);
|
||||||
const node = this.makeNode(this.Root, this.props);
|
const node = this.makeNode(this.Root, this.props);
|
||||||
const prom = this.mountNode(node, target, options);
|
const prom = this.mountNode(node, target, options);
|
||||||
this.root = node;
|
this.root = node;
|
||||||
return prom;
|
return prom;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkTarget(target: HTMLElement) {
|
|
||||||
if (!(target instanceof HTMLElement)) {
|
|
||||||
throw new Error("Cannot mount component: the target is not a valid DOM element");
|
|
||||||
}
|
|
||||||
if (!document.body.contains(target)) {
|
|
||||||
throw new Error("Cannot mount a component on a detached dom node");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
makeNode(Component: ComponentConstructor, props: any): ComponentNode {
|
makeNode(Component: ComponentConstructor, props: any): ComponentNode {
|
||||||
return new ComponentNode(Component, props, this);
|
return new ComponentNode(Component, props, this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { BDom, mount } from "../blockdom";
|
import { BDom, mount } from "../blockdom";
|
||||||
|
import { validateTarget } from "../utils";
|
||||||
import type { ComponentNode } from "./component_node";
|
import type { ComponentNode } from "./component_node";
|
||||||
import { fibersInError, handleError } from "./error_handling";
|
import { fibersInError, handleError } from "./error_handling";
|
||||||
import { STATUS } from "./status";
|
import { STATUS } from "./status";
|
||||||
@@ -158,6 +159,7 @@ export class MountFiber extends RootFiber {
|
|||||||
complete() {
|
complete() {
|
||||||
let current: Fiber | undefined = this;
|
let current: Fiber | undefined = this;
|
||||||
try {
|
try {
|
||||||
|
validateTarget(this.target);
|
||||||
const node = this.node;
|
const node = this.node;
|
||||||
if (node.bdom) {
|
if (node.bdom) {
|
||||||
// this is a complicated situation: if we mount a fiber with an existing
|
// this is a complicated situation: if we mount a fiber with an existing
|
||||||
|
|||||||
@@ -24,6 +24,15 @@ export function batched(callback: Callback): Callback {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function validateTarget(target: HTMLElement) {
|
||||||
|
if (!(target instanceof HTMLElement)) {
|
||||||
|
throw new Error("Cannot mount component: the target is not a valid DOM element");
|
||||||
|
}
|
||||||
|
if (!document.body.contains(target)) {
|
||||||
|
throw new Error("Cannot mount a component on a detached dom node");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class EventBus extends EventTarget {
|
export class EventBus extends EventTarget {
|
||||||
trigger(name: string, payload?: any) {
|
trigger(name: string, payload?: any) {
|
||||||
this.dispatchEvent(new CustomEvent(name, { detail: payload }));
|
this.dispatchEvent(new CustomEvent(name, { detail: payload }));
|
||||||
|
|||||||
@@ -81,6 +81,19 @@ exports[`basics a class component inside a class component, no external dom 2`]
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`basics a component cannot be mounted in a detached node (even if node is detached later 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div/>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return block1();
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`basics a component inside a component 1`] = `
|
exports[`basics a component inside a component 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -171,6 +171,27 @@ describe("basics", () => {
|
|||||||
expect(error!.message).toBe("Cannot mount a component on a detached dom node");
|
expect(error!.message).toBe("Cannot mount a component on a detached dom node");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("a component cannot be mounted in a detached node (even if node is detached later)", async () => {
|
||||||
|
const warn = console.warn;
|
||||||
|
console.warn = jest.fn();
|
||||||
|
class Test extends Component {
|
||||||
|
static template = xml`<div/>`;
|
||||||
|
}
|
||||||
|
let error: Error;
|
||||||
|
const prom = mount(Test, fixture);
|
||||||
|
await Promise.resolve();
|
||||||
|
fixture.remove();
|
||||||
|
try {
|
||||||
|
await prom;
|
||||||
|
} catch (e) {
|
||||||
|
error = e as Error;
|
||||||
|
}
|
||||||
|
expect(error!).toBeDefined();
|
||||||
|
expect(error!.message).toBe("Cannot mount a component on a detached dom node");
|
||||||
|
expect(console.warn).toBeCalledTimes(1);
|
||||||
|
console.warn = warn;
|
||||||
|
});
|
||||||
|
|
||||||
test("crashes if it cannot find a template", async () => {
|
test("crashes if it cannot find a template", async () => {
|
||||||
class Test extends Component {
|
class Test extends Component {
|
||||||
static template = "wrongtemplate";
|
static template = "wrongtemplate";
|
||||||
|
|||||||
Reference in New Issue
Block a user