Skip to content
This repository was archived by the owner on May 5, 2021. It is now read-only.

Commit 8ce18ff

Browse files
Goamandmo-odoo
authored andcommitted
[FIX] Iframe: discriminate IframeContainerNode
1 parent 52ffe6c commit 8ce18ff

File tree

9 files changed

+298
-256
lines changed

9 files changed

+298
-256
lines changed

packages/plugin-iframe/src/Iframe.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import { Parser } from '../../plugin-parser/src/Parser';
44
import { Renderer } from '../../plugin-renderer/src/Renderer';
55
import { IframeHtmlDomParser } from './IframeHtmlDomParser';
66
import { IframeXmlDomParser } from './IframeXmlDomParser';
7-
import { IframeDomObjectRenderer } from './IframeDomObjectRenderer';
7+
import { IframeContainerDomObjectRenderer } from './IframeContainerDomObjectRenderer';
88

99
export class Iframe<T extends JWPluginConfig = JWPluginConfig> extends JWPlugin<T> {
1010
static dependencies = [Parser, Renderer];
1111
readonly loadables: Loadables<Parser & Renderer> = {
1212
parsers: [IframeXmlDomParser, IframeHtmlDomParser],
13-
renderers: [IframeDomObjectRenderer],
13+
renderers: [IframeContainerDomObjectRenderer],
1414
};
1515
}
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import {
2+
DomObjectRenderingEngine,
3+
DomObject,
4+
} from '../../plugin-renderer-dom-object/src/DomObjectRenderingEngine';
5+
import { NodeRenderer } from '../../plugin-renderer/src/NodeRenderer';
6+
import { IframeContainerNode } from './IframeContainerNode';
7+
import { MetadataNode } from '../../plugin-metadata/src/MetadataNode';
8+
import { VNode } from '../../core/src/VNodes/VNode';
9+
import { nodeName, isInstanceOf } from '../../utils/src/utils';
10+
11+
const EventForwarded = ['selectionchange', 'blur', 'focus', 'mousedown', 'touchstart', 'keydown'];
12+
const forwardEventOutsideIframe = (ev: UIEvent): void => {
13+
const target = ev.target;
14+
let customEvent: Event;
15+
let win: Window;
16+
if (isInstanceOf(target, Document)) {
17+
win = target.defaultView;
18+
} else if (isInstanceOf(target, Node)) {
19+
win = target.ownerDocument.defaultView;
20+
} else if (isInstanceOf(ev.currentTarget, Node)) {
21+
win = ev.currentTarget.ownerDocument.defaultView;
22+
} else if (
23+
isInstanceOf(ev.currentTarget, Window) &&
24+
ev.currentTarget.self === ev.currentTarget
25+
) {
26+
win = ev.currentTarget;
27+
} else {
28+
win = ev.view || (ev.target as Window);
29+
}
30+
31+
const iframe = win.frameElement;
32+
if (ev.type === 'mousedown') {
33+
const rect = iframe.getBoundingClientRect();
34+
customEvent = new MouseEvent(ev.type + '-iframe', {
35+
bubbles: true,
36+
composed: true,
37+
cancelable: true,
38+
clientX: (ev as MouseEvent).clientX + rect.x,
39+
clientY: (ev as MouseEvent).clientY + rect.y,
40+
});
41+
} else if (ev.type === 'touchstart') {
42+
const rect = iframe.getBoundingClientRect();
43+
customEvent = new MouseEvent('mousedown-iframe', {
44+
bubbles: true,
45+
composed: true,
46+
cancelable: true,
47+
clientX: (ev as TouchEvent).touches[0].clientX + rect.x,
48+
clientY: (ev as TouchEvent).touches[0].clientY + rect.y,
49+
});
50+
} else if (ev.type === 'keydown') {
51+
customEvent = new KeyboardEvent('keydown-iframe', {
52+
bubbles: true,
53+
composed: true,
54+
cancelable: true,
55+
altKey: (ev as KeyboardEvent).altKey,
56+
ctrlKey: (ev as KeyboardEvent).ctrlKey,
57+
shiftKey: (ev as KeyboardEvent).shiftKey,
58+
metaKey: (ev as KeyboardEvent).metaKey,
59+
key: (ev as KeyboardEvent).key,
60+
code: (ev as KeyboardEvent).code,
61+
});
62+
} else {
63+
customEvent = new CustomEvent(ev.type + '-iframe', {
64+
bubbles: true,
65+
composed: true,
66+
cancelable: true,
67+
});
68+
}
69+
70+
const preventDefault = customEvent.preventDefault.bind(customEvent);
71+
customEvent.preventDefault = (): void => {
72+
ev.preventDefault();
73+
preventDefault();
74+
};
75+
76+
const stopPropagation = customEvent.stopPropagation.bind(customEvent);
77+
customEvent.stopPropagation = (): void => {
78+
ev.stopPropagation();
79+
stopPropagation();
80+
};
81+
82+
const stopImmediatePropagation = customEvent.stopImmediatePropagation.bind(customEvent);
83+
customEvent.stopImmediatePropagation = (): void => {
84+
ev.stopImmediatePropagation();
85+
stopImmediatePropagation();
86+
};
87+
88+
iframe.dispatchEvent(customEvent);
89+
};
90+
91+
export class IframeContainerDomObjectRenderer extends NodeRenderer<DomObject> {
92+
static id = DomObjectRenderingEngine.id;
93+
engine: DomObjectRenderingEngine;
94+
predicate = IframeContainerNode;
95+
96+
async render(iframeNode: IframeContainerNode): Promise<DomObject> {
97+
let onload: () => void;
98+
const children: VNode[] = [];
99+
iframeNode.childVNodes.forEach(child => {
100+
if (child.tangible || child instanceof MetadataNode) {
101+
children.push(child);
102+
}
103+
});
104+
let wrap: HTMLElement;
105+
const domObject: DomObject = {
106+
children: [
107+
{
108+
tag: 'JW-IFRAME',
109+
shadowRoot: true,
110+
children: children,
111+
},
112+
{
113+
tag: 'IFRAME',
114+
attributes: {
115+
// Can not use the default href loading in testing mode because the port is
116+
// used for the log, and the iframe are never loaded.
117+
// Use the window.location.href to keep style, link and meta to load some
118+
// data like the font-face. The style are not really used into the shadow
119+
// container but we need the real url to load font-face with relative path.
120+
src: window.location.href,
121+
name: 'jw-iframe',
122+
},
123+
attach: (iframe: HTMLIFrameElement): void => {
124+
const prev = iframe.previousElementSibling as HTMLElement;
125+
if (nodeName(prev) === 'JW-IFRAME') {
126+
if (wrap) {
127+
wrap.replaceWith(prev);
128+
} else {
129+
prev.style.display = 'none';
130+
}
131+
wrap = prev;
132+
}
133+
134+
iframe.addEventListener('load', onload);
135+
(function loadWithPreloadedMeta(): void {
136+
// Remove all scripts, keep style, link and meta to load some
137+
// data like the font-face. The style are not used into the
138+
// shadow container.
139+
if (iframe.previousElementSibling !== wrap) {
140+
return;
141+
} else {
142+
const doc = iframe.contentWindow?.document;
143+
if (doc && (doc.head || doc.body)) {
144+
for (const meta of wrap.shadowRoot.querySelectorAll(
145+
'style, link, meta',
146+
)) {
147+
doc.write(meta.outerHTML);
148+
}
149+
doc.write('<body id="jw-iframe"></body>');
150+
doc.write("<script type='application/x-suppress'>");
151+
iframe.contentWindow.close();
152+
153+
setTimeout((): void => {
154+
const win = iframe.contentWindow;
155+
const doc = win.document;
156+
// Remove all attribute from the shadow container.
157+
for (const attr of [...wrap.attributes]) {
158+
wrap.removeAttribute(attr.name);
159+
}
160+
doc.body.style.margin = '0px';
161+
doc.body.innerHTML = '';
162+
doc.body.append(wrap);
163+
164+
// Bubbles up the load-iframe event.
165+
const customEvent = new CustomEvent('load-iframe', {
166+
bubbles: true,
167+
composed: true,
168+
cancelable: true,
169+
});
170+
iframe.dispatchEvent(customEvent);
171+
EventForwarded.forEach(eventName => {
172+
win.addEventListener(
173+
eventName,
174+
forwardEventOutsideIframe,
175+
true,
176+
);
177+
win.addEventListener(
178+
eventName + '-iframe',
179+
forwardEventOutsideIframe,
180+
true,
181+
);
182+
});
183+
});
184+
} else {
185+
setTimeout(loadWithPreloadedMeta);
186+
}
187+
}
188+
})();
189+
},
190+
detach: (iframe: HTMLIFrameElement): void => {
191+
if (iframe.contentWindow) {
192+
const win = iframe.contentWindow;
193+
EventForwarded.forEach(eventName => {
194+
win.removeEventListener(eventName, forwardEventOutsideIframe, true);
195+
win.removeEventListener(
196+
eventName + '-iframe',
197+
forwardEventOutsideIframe,
198+
true,
199+
);
200+
});
201+
}
202+
iframe.removeEventListener('load', onload);
203+
},
204+
},
205+
],
206+
};
207+
return domObject;
208+
}
209+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { ContainerNode } from '../../core/src/VNodes/ContainerNode';
2+
3+
export interface IframeNodeParams {
4+
src?: string;
5+
}
6+
7+
export class IframeContainerNode extends ContainerNode {
8+
editable = false;
9+
breakable = false;
10+
src?: string;
11+
constructor(params?: IframeNodeParams) {
12+
super();
13+
if (params?.src) {
14+
this.src = params.src;
15+
}
16+
}
17+
}

0 commit comments

Comments
 (0)