Skip to content

Commit 1eb6a6d

Browse files
authored
Merge pull request #2372 from tf/double-del
Prevent duplicate listeners in editor preview controller
2 parents 2dd2256 + ba28621 commit 1eb6a6d

File tree

4 files changed

+172
-143
lines changed

4 files changed

+172
-143
lines changed

entry_types/scrolled/package/spec/editor/controllers/PreviewMessageController-spec.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,32 @@ describe('PreviewMessageController', () => {
628628
}).not.toThrowError();
629629
});
630630

631+
it('ignores second READY message', async () => {
632+
const entry = factories.entry(ScrolledEntry, {}, {
633+
entryTypeSeed: normalizeSeed({
634+
contentElements: [{id: 1}]
635+
})
636+
});
637+
const iframeWindow = createIframeWindow();
638+
controller = new PreviewMessageController({entry, iframeWindow});
639+
640+
await postReadyMessageAndWaitForAcknowledgement(iframeWindow);
641+
await postReadyMessageAndWaitForAcknowledgement(iframeWindow);
642+
643+
const actions = [];
644+
iframeWindow.addEventListener('message', event => {
645+
if (event.data.type === 'ACTION') {
646+
actions.push(event.data.payload);
647+
}
648+
});
649+
650+
entry.contentElements.first().configuration.set({title: 'update'});
651+
652+
return expect(new Promise(resolve => {
653+
setTimeout(() => resolve(actions.length), 100);
654+
})).resolves.toEqual(1);
655+
});
656+
631657
it('sends CHANGE_EMULATION_MODE message to iframe on change:emulation_mode event on model', async () => {
632658
const entry = factories.entry(ScrolledEntry, {}, {
633659
entryTypeSeed: normalizeSeed({

entry_types/scrolled/package/src/editor/controllers/PreviewMessageController.js

Lines changed: 100 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -24,106 +24,110 @@ export const PreviewMessageController = Object.extend({
2424

2525
if (window.location.href.indexOf(message.origin) === 0) {
2626
if (message.data.type === 'READY') {
27-
watchCollections(this.entry, {
28-
dispatch: action => {
29-
postMessage({type: 'ACTION', payload: action})
30-
}
31-
});
32-
33-
this.listenTo(this.entry, 'scrollToSection', (section, options) =>
34-
postMessage({
35-
type: 'SCROLL_TO_SECTION',
36-
payload: {
37-
id: section.id,
38-
...options
39-
}
40-
})
41-
);
27+
if (!this.ready) {
28+
this.ready = true;
4229

43-
this.listenTo(this.entry.contentElements, 'postCommand', (contentElementId, command) =>
44-
postMessage({
45-
type: 'CONTENT_ELEMENT_EDITOR_COMMAND',
46-
payload: {
47-
contentElementId,
48-
command
30+
watchCollections(this.entry, {
31+
dispatch: action => {
32+
postMessage({type: 'ACTION', payload: action})
4933
}
50-
})
51-
);
34+
});
5235

53-
this.listenTo(this.entry, 'selectSection', section =>
54-
postMessage({
55-
type: 'SELECT',
56-
payload: {
57-
id: section.id,
58-
type: 'section'
59-
}
60-
})
61-
);
62-
63-
this.listenTo(this.entry, 'selectSectionSettings', section =>
64-
postMessage({
65-
type: 'SELECT',
66-
payload: {
67-
id: section.id,
68-
type: 'sectionSettings'
69-
}
70-
})
71-
);
72-
73-
this.listenTo(this.entry, 'selectSectionTransition', section =>
74-
postMessage({
75-
type: 'SELECT',
76-
payload: {
77-
id: section.id,
78-
type: 'sectionTransition'
79-
}
80-
})
81-
);
82-
83-
this.listenTo(this.entry, 'selectSectionPaddings', section =>
84-
postMessage({
85-
type: 'SELECT',
86-
payload: {
87-
id: section.id,
88-
type: 'sectionPaddings'
89-
}
90-
})
91-
);
92-
93-
this.listenTo(this.entry, 'selectContentElement', (contentElement, options) => {
94-
postMessage({
95-
type: 'SELECT',
96-
payload: {
97-
id: contentElement.id,
98-
range: options?.range,
99-
type: 'contentElement'
100-
}
101-
})
102-
});
103-
104-
this.listenTo(this.entry, 'selectWidget', widget => {
105-
postMessage({
106-
type: 'SELECT',
107-
payload: {
108-
id: widget.get('role'),
109-
type: 'widget'
110-
}
111-
})
112-
});
113-
114-
this.listenTo(this.entry, 'resetSelection', contentElement =>
115-
postMessage({
116-
type: 'SELECT',
117-
payload: null
118-
})
119-
);
36+
this.listenTo(this.entry, 'scrollToSection', (section, options) =>
37+
postMessage({
38+
type: 'SCROLL_TO_SECTION',
39+
payload: {
40+
id: section.id,
41+
...options
42+
}
43+
})
44+
);
45+
46+
this.listenTo(this.entry.contentElements, 'postCommand', (contentElementId, command) =>
47+
postMessage({
48+
type: 'CONTENT_ELEMENT_EDITOR_COMMAND',
49+
payload: {
50+
contentElementId,
51+
command
52+
}
53+
})
54+
);
55+
56+
this.listenTo(this.entry, 'selectSection', section =>
57+
postMessage({
58+
type: 'SELECT',
59+
payload: {
60+
id: section.id,
61+
type: 'section'
62+
}
63+
})
64+
);
65+
66+
this.listenTo(this.entry, 'selectSectionSettings', section =>
67+
postMessage({
68+
type: 'SELECT',
69+
payload: {
70+
id: section.id,
71+
type: 'sectionSettings'
72+
}
73+
})
74+
);
75+
76+
this.listenTo(this.entry, 'selectSectionTransition', section =>
77+
postMessage({
78+
type: 'SELECT',
79+
payload: {
80+
id: section.id,
81+
type: 'sectionTransition'
82+
}
83+
})
84+
);
85+
86+
this.listenTo(this.entry, 'selectSectionPaddings', section =>
87+
postMessage({
88+
type: 'SELECT',
89+
payload: {
90+
id: section.id,
91+
type: 'sectionPaddings'
92+
}
93+
})
94+
);
95+
96+
this.listenTo(this.entry, 'selectContentElement', (contentElement, options) => {
97+
postMessage({
98+
type: 'SELECT',
99+
payload: {
100+
id: contentElement.id,
101+
range: options?.range,
102+
type: 'contentElement'
103+
}
104+
})
105+
});
106+
107+
this.listenTo(this.entry, 'selectWidget', widget => {
108+
postMessage({
109+
type: 'SELECT',
110+
payload: {
111+
id: widget.get('role'),
112+
type: 'widget'
113+
}
114+
})
115+
});
116+
117+
this.listenTo(this.entry, 'resetSelection', contentElement =>
118+
postMessage({
119+
type: 'SELECT',
120+
payload: null
121+
})
122+
);
120123

121-
this.listenTo(this.entry, 'change:emulation_mode', entry =>
122-
postMessage({
123-
type: 'CHANGE_EMULATION_MODE',
124-
payload: this.entry.get('emulation_mode')
125-
})
126-
);
124+
this.listenTo(this.entry, 'change:emulation_mode', entry =>
125+
postMessage({
126+
type: 'CHANGE_EMULATION_MODE',
127+
payload: this.entry.get('emulation_mode')
128+
})
129+
);
130+
}
127131

128132
postMessage({type: 'ACK'})
129133
}
Lines changed: 4 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,16 @@
1-
import React, {useEffect, useCallback} from 'react';
1+
import React from 'react';
22
import {DndProvider} from 'react-dnd';
33
import {HTML5Backend} from 'react-dnd-html5-backend';
44

5-
import {useEntryStateDispatch} from '../../entryState';
6-
import {usePostMessageListener} from '../usePostMessageListener';
7-
import {useEditorSelection} from './EditorState';
8-
import {
9-
useContentElementEditorCommandEmitter,
10-
ContentElementEditorCommandSubscriptionProvider
11-
} from './ContentElementEditorCommandSubscriptionProvider';
125
import {ScrollPointMessageHandler} from './scrollPoints';
136

147
export function ContentDecorator(props) {
15-
const contentElementEditorCommandEmitter = useContentElementEditorCommandEmitter();
16-
178
return (
189
<>
19-
<MessageHandler contentElementEditorCommandEmitter={contentElementEditorCommandEmitter} />
2010
<ScrollPointMessageHandler />
21-
<ContentElementEditorCommandSubscriptionProvider emitter={contentElementEditorCommandEmitter}>
22-
<DndProvider backend={HTML5Backend}>
23-
{props.children}
24-
</DndProvider>
25-
</ContentElementEditorCommandSubscriptionProvider>
11+
<DndProvider backend={HTML5Backend}>
12+
{props.children}
13+
</DndProvider>
2614
</>
2715
);
2816
}
29-
30-
function MessageHandler({contentElementEditorCommandEmitter}) {
31-
const {select} = useEditorSelection()
32-
const dispatch = useEntryStateDispatch();
33-
34-
const receiveMessage = useCallback(data => {
35-
if (data.type === 'ACTION') {
36-
dispatch(data.payload);
37-
}
38-
else if (data.type === 'SELECT') {
39-
select(data.payload);
40-
}
41-
else if (data.type === 'CONTENT_ELEMENT_EDITOR_COMMAND') {
42-
contentElementEditorCommandEmitter.trigger(`command:${data.payload.contentElementId}`,
43-
data.payload.command);
44-
}
45-
}, [dispatch, select, contentElementEditorCommandEmitter]);
46-
47-
usePostMessageListener(receiveMessage);
48-
49-
useEffect(() => {
50-
if (window.parent !== window) {
51-
window.parent.postMessage({type: 'READY'}, window.location.origin);
52-
}
53-
}, []);
54-
55-
return null;
56-
}
Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,50 @@
1-
import React from 'react';
1+
import React, {useEffect, useCallback} from 'react';
22

3-
import {EditorStateProvider} from './EditorState';
3+
import {useEntryStateDispatch} from '../../entryState';
4+
import {usePostMessageListener} from '../usePostMessageListener';
5+
import {EditorStateProvider, useEditorSelection} from './EditorState';
6+
import {
7+
useContentElementEditorCommandEmitter,
8+
ContentElementEditorCommandSubscriptionProvider
9+
} from './ContentElementEditorCommandSubscriptionProvider';
410

511
export function EntryDecorator({children}) {
12+
const contentElementEditorCommandEmitter = useContentElementEditorCommandEmitter();
13+
614
return (
715
<EditorStateProvider>
8-
{children}
16+
<MessageHandler contentElementEditorCommandEmitter={contentElementEditorCommandEmitter} />
17+
<ContentElementEditorCommandSubscriptionProvider emitter={contentElementEditorCommandEmitter}>
18+
{children}
19+
</ContentElementEditorCommandSubscriptionProvider>
920
</EditorStateProvider>
1021
);
1122
}
23+
24+
function MessageHandler({contentElementEditorCommandEmitter}) {
25+
const {select} = useEditorSelection()
26+
const dispatch = useEntryStateDispatch();
27+
28+
const receiveMessage = useCallback(data => {
29+
if (data.type === 'ACTION') {
30+
dispatch(data.payload);
31+
}
32+
else if (data.type === 'SELECT') {
33+
select(data.payload);
34+
}
35+
else if (data.type === 'CONTENT_ELEMENT_EDITOR_COMMAND') {
36+
contentElementEditorCommandEmitter.trigger(`command:${data.payload.contentElementId}`,
37+
data.payload.command);
38+
}
39+
}, [dispatch, select, contentElementEditorCommandEmitter]);
40+
41+
usePostMessageListener(receiveMessage);
42+
43+
useEffect(() => {
44+
if (window.parent !== window) {
45+
window.parent.postMessage({type: 'READY'}, window.location.origin);
46+
}
47+
}, []);
48+
49+
return null;
50+
}

0 commit comments

Comments
 (0)