Skip to content

Commit 5cc746d

Browse files
committed
fix: duplicate live edit events when tabs are popped out
1 parent 83a41be commit 5cc746d

File tree

5 files changed

+71
-15
lines changed

5 files changed

+71
-15
lines changed

src/LiveDevelopment/BrowserScripts/LivePreviewTransportRemote.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,37 @@
103103
}
104104
}
105105

106+
function createLRU(max = 100) {
107+
const map = new Map();
108+
109+
return {
110+
set: function (key, value = true) {
111+
if (map.has(key)) {
112+
map.delete(key); // refresh order
113+
}
114+
map.set(key, value);
115+
if (map.size > max) {
116+
const oldestKey = map.keys().next().value;
117+
map.delete(oldestKey);
118+
}
119+
},
120+
has: function (key) {
121+
if (!map.has(key)) {
122+
return false;
123+
}
124+
const val = map.get(key);
125+
map.delete(key); // refresh order
126+
map.set(key, val);
127+
return true;
128+
},
129+
size: function() {
130+
return map.size;
131+
}
132+
};
133+
}
134+
135+
const processedMessageIDs = createLRU(1000);
136+
106137
const clientID = "" + Math.round( Math.random()*1000000000);
107138

108139
const worker = new Worker(TRANSPORT_CONFIG.LIVE_DEV_REMOTE_WORKER_SCRIPTS_FILE_NAME);
@@ -218,7 +249,12 @@
218249
case 'MESSAGE_FROM_PHOENIX':
219250
if (self._callbacks && self._callbacks.message) {
220251
const clientIDs = event.data.clientIDs,
221-
message = event.data.message;
252+
message = event.data.message,
253+
messageID = event.data.messageID;
254+
if(messageID && processedMessageIDs.has(messageID)){
255+
return; // we have already processed this message.
256+
}
257+
processedMessageIDs.set(messageID, true);
222258
if(clientIDs.includes(clientID) || clientIDs.length === 0){
223259
// clientIDs.length = 0 if the message is intended for all clients
224260
self._callbacks.message(message);
@@ -264,6 +300,7 @@
264300
_postLivePreviewMessage({
265301
type: 'BROWSER_MESSAGE',
266302
clientID: clientID,
303+
messageID: crypto.randomUUID(),
267304
message: msgStr
268305
});
269306
},

src/LiveDevelopment/BrowserScripts/pageLoaderWorker.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
*/
2626

2727

28-
let _livePreviewNavigationChannel;
28+
let _livePreviewBroadcastChannel;
2929
let _livePreviewWebSocket, _livePreviewWebSocketOpen = false;
3030
let livePreviewDebugModeEnabled = false;
3131
function _debugLog(...args) {
@@ -106,8 +106,8 @@ let messageQueue = [];
106106
function _sendMessage(message) {
107107
if(_livePreviewWebSocket && _livePreviewWebSocketOpen) {
108108
_livePreviewWebSocket.send(mergeMetadataAndArrayBuffer(message));
109-
} else if(_livePreviewNavigationChannel){
110-
_livePreviewNavigationChannel.postMessage(message);
109+
} else if(_livePreviewBroadcastChannel){
110+
_livePreviewBroadcastChannel.postMessage(message);
111111
} else {
112112
livePreviewDebugModeEnabled && console.warn("No Channels available for live preview worker messaging," +
113113
" queueing request, waiting for channel..");
@@ -138,8 +138,8 @@ function _setupHearbeatMessenger(clientID) {
138138
}
139139

140140
function _setupBroadcastChannel(broadcastChannel, clientID) {
141-
_livePreviewNavigationChannel=new BroadcastChannel(broadcastChannel);
142-
_livePreviewNavigationChannel.onmessage = (event) => {
141+
_livePreviewBroadcastChannel=new BroadcastChannel(broadcastChannel);
142+
_livePreviewBroadcastChannel.onmessage = (event) => {
143143
const type = event.data.type;
144144
switch (type) {
145145
case 'TAB_ONLINE': break; // do nothing. This is a loopback message from another live preview tab

src/LiveDevelopment/MultiBrowserImpl/protocol/LiveDevProtocol.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,15 @@ define(function (require, exports, module) {
194194
}
195195
}
196196

197+
const processedMessageIDs = new Phoenix.libs.LRUCache({
198+
max: 1000
199+
// we dont need to set a ttl here as message ids are unique throughout lifetime. And old ids will
200+
// start getting evited from the cache. the message ids are only an issue within a fraction of a seconds when
201+
// a series of messages are sent in quick succession. Eg. user click on a div and there are 3 tabs and due to
202+
// the reflection bug, we almost immediately get 3 messages with the same id. So that will be in this cache
203+
// for a fraction of a second. so a size of 1000 should be more than enough.
204+
});
205+
197206
/**
198207
* @private
199208
* Handles a message received from the remote protocol handler via the transport.
@@ -203,12 +212,21 @@ define(function (require, exports, module) {
203212
* TODO: we should probably have a way of returning the results from all clients, not just the first?
204213
*
205214
* @param {number} clientId ID of the client that sent the message
206-
* @param {string} msg The message that was sent, in JSON string format
215+
* @param {string} msgStr The message that was sent, in JSON string format
216+
* @param {string} messageID The messageID uniquely identifying a message. in browsers, since we use broadcast
217+
* channels, we get reflections echoes when there are multiple tabs open. Ideally those reflections need to
218+
* be fixed, but that was too complex to fix, so we just reply on the message id to guarantee that a message is
219+
* only processed once and not from any reflections.
207220
*/
208-
function _receive(clientId, msgStr) {
221+
function _receive(clientId, msgStr, messageID) {
209222
var msg = JSON.parse(msgStr),
210223
event = msg.method || "event",
211224
deferred;
225+
if(messageID && processedMessageIDs.has(messageID)){
226+
return; // this message is already processed.
227+
} else if (messageID) {
228+
processedMessageIDs.set(messageID, true);
229+
}
212230
if (msg.livePreviewEditEnabled) {
213231
LivePreviewEdit.handleLivePreviewEditOperation(msg);
214232
}
@@ -305,7 +323,7 @@ define(function (require, exports, module) {
305323
_connect(msg[0], msg[1]);
306324
})
307325
.on("message.livedev", function (event, msg) {
308-
_receive(msg[0], msg[1]);
326+
_receive(msg[0], msg[1], msg[2]);
309327
})
310328
.on("close.livedev", function (event, msg) {
311329
_close(msg[0]);

src/LiveDevelopment/MultiBrowserImpl/transports/LivePreviewTransport.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ define(function (require, exports, module) {
111111
_transportBridge && _transportBridge.messageToLivePreviewTabs({
112112
type: 'MESSAGE_FROM_PHOENIX',
113113
clientIDs,
114-
message
114+
message,
115+
messageID: crypto.randomUUID()
115116
});
116117
transportMessagesSendCount ++;
117118
transportMessagesSendSizeB = transportMessagesSendSizeB + message.length;
@@ -135,7 +136,7 @@ define(function (require, exports, module) {
135136
window.logger.livePreview.log(
136137
"Live Preview: Phoenix received event from Browser preview tab/iframe: ", event.data);
137138
const message = event.data.message.message || "";
138-
exports.trigger('message', [event.data.message.clientID, message]);
139+
exports.trigger('message', [event.data.message.clientID, message, event.data.message.messageID]);
139140
transportMessagesRecvSizeB = transportMessagesRecvSizeB + message.length;
140141
transportMessagesRecvCount++;
141142
}

src/live-preview-loader.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@
147147
const LOADER_BROADCAST_ID = `live-preview-loader-${controllingPhoenixInstanceID}`;
148148
const navigatorChannel = new BroadcastChannel(LOADER_BROADCAST_ID);
149149
const LIVE_PREVIEW_MESSENGER_CHANNEL = `live-preview-messenger-${controllingPhoenixInstanceID}`;
150-
const livePreviewChannel = new BroadcastChannel(LIVE_PREVIEW_MESSENGER_CHANNEL);
150+
const phcodeBroadcastMessageChannel = new BroadcastChannel(LIVE_PREVIEW_MESSENGER_CHANNEL);
151151
navigatorChannel.onmessage = (event) => {
152152
_debugLog("Live Preview loader channel: Browser received event from Phoenix: ", JSON.stringify(event.data));
153153
const type = event.data.type;
@@ -224,7 +224,7 @@
224224
type: 'GET_INITIAL_URL',
225225
pageLoaderID: pageLoaderID
226226
});
227-
livePreviewChannel.onmessage = (event) => {
227+
phcodeBroadcastMessageChannel.onmessage = (event) => {
228228
_debugLog("Live Preview message channel: Browser received event from Phoenix: ", JSON.stringify(event.data));
229229
if(event.data.pageLoaderID && event.data.pageLoaderID !== pageLoaderID){
230230
// this message is not for this page loader window.
@@ -241,7 +241,7 @@
241241
}
242242

243243
// this is for phoenix to process, pass it on
244-
livePreviewChannel.postMessage({
244+
phcodeBroadcastMessageChannel.postMessage({
245245
pageLoaderID: pageLoaderID,
246246
data: event.data
247247
});
@@ -322,4 +322,4 @@
322322
sandbox="allow-same-origin allow-scripts allow-popups allow-popups-to-escape-sandbox allow-forms allow-modals allow-pointer-lock allow-downloads">
323323
</iframe>
324324
</body>
325-
</html>
325+
</html>

0 commit comments

Comments
 (0)