Skip to content

Commit 58a5522

Browse files
authored
Bug: Highlighting not working with embedded LiveViews (#797) (#828)
* Fix finding root element id when inspecting embedded LiveView * Send list of all root socket ids via websocket when no main LiveView * Subscribe client channel to all root socket ids * Fix test * Update assets
1 parent b474970 commit 58a5522

File tree

12 files changed

+123
-42
lines changed

12 files changed

+123
-42
lines changed

assets/client/client.js

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,37 @@ import initTooltip from './components/tooltip/tooltip';
1010
import {
1111
getMetaTag,
1212
fetchLiveDebuggerBaseURL,
13-
fetchDebuggedSocketID,
13+
fetchDebuggedSocketIDs,
1414
isDebugButtonEnabled,
1515
isHighlightingEnabled,
1616
} from './utils/meta';
1717

18-
window.document.addEventListener('DOMContentLoaded', function () {
18+
window.document.addEventListener('DOMContentLoaded', async () => {
1919
const metaTag = getMetaTag();
2020
const baseURL = fetchLiveDebuggerBaseURL(metaTag);
21-
const socketID = fetchDebuggedSocketID();
21+
let { mainSocketID, rootSocketIDs } = await fetchDebuggedSocketIDs();
2222

23-
const sessionURL = `${baseURL}/redirect/${socketID}`;
23+
if (!mainSocketID) {
24+
[mainSocketID, ...rootSocketIDs] = rootSocketIDs;
25+
} else {
26+
rootSocketIDs = [];
27+
}
28+
29+
if (mainSocketID) {
30+
const sessionURL = `${baseURL}/redirect/${mainSocketID}`;
2431

25-
if (socketID) {
26-
const { debugChannel } = initDebugSocket(baseURL, socketID);
32+
const { debugChannel } = initDebugSocket(
33+
baseURL,
34+
mainSocketID,
35+
rootSocketIDs
36+
);
2737

2838
debugChannel.on('ping', (resp) => {
2939
console.log('Received ping', resp);
3040
debugChannel.push('pong', resp);
3141
});
3242

33-
initElementInspection({ baseURL, debugChannel, socketID });
43+
initElementInspection({ baseURL, debugChannel, socketID: mainSocketID });
3444
initTooltip();
3545

3646
if (isDebugButtonEnabled(metaTag)) {

assets/client/services/debug_socket.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import pushWindowInitialized from './window_identifier';
22

3-
export default function initDebugSocket(baseURL, socketID) {
3+
export default function initDebugSocket(baseURL, socketID, rootSocketIDs) {
44
const websocketURL = baseURL.replace(/^http/, 'ws') + '/client';
55

66
const debugSocket = new window.Phoenix.Socket(websocketURL, {
7-
params: { socketID },
7+
params: { socketID, rootSocketIDs },
88
});
99

1010
debugSocket.connect();

assets/client/services/inspect.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,21 +209,24 @@ function pushRemoveTooltipEvent() {
209209
function getClosestElementInfo(target) {
210210
const liveViewElement = target.closest('[data-phx-session]');
211211
const componentElement = target.closest('[data-phx-component]');
212-
const rootElement = document.querySelector('[data-phx-main]');
212+
213+
if (!liveViewElement) return null;
214+
215+
const rootElementId = liveViewElement.getAttribute('data-phx-root-id');
213216

214217
if (componentElement && liveViewElement.contains(componentElement)) {
215218
return {
216219
element: componentElement,
217220
type: 'LiveComponent',
218-
phxRootId: rootElement.id,
221+
phxRootId: rootElementId,
219222
phxId: liveViewElement.id,
220223
};
221224
}
222225

223226
return {
224227
element: liveViewElement,
225228
type: 'LiveView',
226-
phxRootId: rootElement.id,
229+
phxRootId: rootElementId,
227230
phxId: liveViewElement.id,
228231
};
229232
}

assets/client/utils/meta.js

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,53 @@
1-
export function fetchDebuggedSocketID() {
2-
let el;
3-
if ((el = document.querySelector('[data-phx-main]'))) {
4-
return el.id;
5-
}
6-
if ((el = document.querySelector('[id^="phx-"]'))) {
7-
return el.id;
8-
}
9-
if ((el = document.querySelector('[data-phx-root-id]'))) {
10-
return el.getAttribute('data-phx-root-id');
11-
}
1+
export function fetchDebuggedSocketIDs() {
2+
return new Promise((resolve) => {
3+
const liveViewElements = document.querySelectorAll('[data-phx-session]');
4+
const rootIDsMapping = {};
5+
const mainID = document.querySelector('[data-phx-main]')?.id;
6+
7+
const handleMutation = (mutation) => {
8+
if (!isRootIDAttributeChanged(mutation)) return;
9+
10+
registerRootID(rootIDsMapping, mutation.target);
11+
12+
if (isAllRootIDsRegistered(rootIDsMapping, liveViewElements)) {
13+
observer.disconnect();
14+
15+
resolve({
16+
mainSocketID: mainID,
17+
rootSocketIDs: getRootSocketIDs(rootIDsMapping, mainID),
18+
});
19+
}
20+
};
21+
22+
const observer = new MutationObserver((mutations) => {
23+
mutations.forEach(handleMutation);
24+
});
25+
26+
liveViewElements.forEach((el) => {
27+
observer.observe(el, { attributes: true });
28+
});
29+
});
30+
}
31+
32+
function isRootIDAttributeChanged(mutation) {
33+
return (
34+
mutation.type === 'attributes' &&
35+
mutation.attributeName === 'data-phx-root-id'
36+
);
37+
}
38+
39+
function registerRootID(rootIDs, target) {
40+
rootIDs[target.id] = target.getAttribute('data-phx-root-id');
41+
}
42+
43+
function isAllRootIDsRegistered(rootIDs, liveViewElements) {
44+
return Object.keys(rootIDs).length >= liveViewElements.length;
45+
}
46+
47+
function getRootSocketIDs(rootIDs, mainID) {
48+
const rootSocketIDs = new Set(Object.values(rootIDs));
49+
rootSocketIDs.delete(mainID);
50+
return [...rootSocketIDs];
1251
}
1352

1453
export function getMetaTag() {

dev/embedded_live_view_controller.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
defmodule LiveDebuggerDev.EmbeddedLiveViewController do
2-
use Phoenix.Controller, layouts: [html: {LiveDebuggerDev.Layout, :app}]
2+
use Phoenix.Controller, layouts: [html: {LiveDebuggerDev.Layout, :embedded}]
33

44
import Phoenix.LiveView.Controller
55

dev/layout.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,14 @@ defmodule LiveDebuggerDev.Layout do
8181
end
8282

8383
def render("embedded.html", assigns) do
84+
assigns = assign(assigns, v: Map.get(assigns, :socket, assigns[:conn]))
85+
8486
~H"""
8587
<main class="p-5">
8688
<.navbar />
8789
<.box title="Embedded Live View [LiveView]" color="purple">
88-
<%= live_render(@socket, LiveDebuggerDev.LiveViews.Nested,
89-
id: "embedded_with_nested",
90+
<%= live_render(@v, LiveDebuggerDev.LiveViews.Nested,
91+
id: "phx-embedded_with_nested",
9092
session: %{"id" => "embedded_with_nested"},
9193
sticky: true
9294
) %>

lib/live_debugger/app/debugger/web/hooks/async_lv_process.ex

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ defmodule LiveDebugger.App.Debugger.Web.Hooks.AsyncLvProcess do
99

1010
alias Phoenix.LiveView.AsyncResult
1111
alias LiveDebugger.App.Debugger.Queries.LvProcess, as: LvProcessQueries
12-
alias LiveDebugger.App.Debugger.Queries.State, as: StateQueries
1312
alias LiveDebugger.Structs.LvProcess
1413
alias LiveDebugger.App.Web.Helpers.Routes, as: RoutesHelper
14+
alias LiveDebugger.API.LiveViewDiscovery
1515

1616
alias LiveDebugger.Bus
1717
alias LiveDebugger.App.Events.DebuggerMounted
@@ -78,16 +78,32 @@ defmodule LiveDebugger.App.Debugger.Web.Hooks.AsyncLvProcess do
7878
end
7979
end
8080

81-
defp get_root_socket_id(lv_process) when lv_process.root_pid == lv_process.pid do
81+
defp get_root_socket_id(%LvProcess{embedded?: false, nested?: false} = lv_process) do
8282
{:ok, lv_process.socket_id}
8383
end
8484

85+
defp get_root_socket_id(%LvProcess{embedded?: true, nested?: false} = lv_process) do
86+
case find_root_lv_process_over_transport_pid(lv_process.transport_pid) do
87+
%LvProcess{socket_id: socket_id} -> {:ok, socket_id}
88+
_ -> {:ok, lv_process.socket_id}
89+
end
90+
end
91+
8592
defp get_root_socket_id(lv_process) do
8693
lv_process.root_pid
87-
|> StateQueries.get_socket()
94+
|> LvProcessQueries.get_lv_process()
8895
|> case do
89-
{:ok, %{id: socket_id}} -> {:ok, socket_id}
96+
%LvProcess{embedded?: false} = lv_process -> {:ok, lv_process.socket_id}
97+
%LvProcess{embedded?: true, nested?: false} = lv_process -> get_root_socket_id(lv_process)
9098
_ -> {:error, :root_socket_id_not_found}
9199
end
92100
end
101+
102+
defp find_root_lv_process_over_transport_pid(transport_pid) do
103+
LiveViewDiscovery.debugged_lv_processes()
104+
|> Enum.find(fn
105+
%LvProcess{transport_pid: ^transport_pid, embedded?: false, nested?: false} -> true
106+
_ -> false
107+
end)
108+
end
93109
end

lib/live_debugger/client/channel.ex

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ defmodule LiveDebugger.Client.Channel do
88

99
@impl true
1010
def join("client:" <> _debugged_socket_id, _params, socket) do
11+
Phoenix.PubSub.subscribe(@pubsub_name, "client:*")
12+
13+
socket.assigns.root_socket_ids
14+
|> Enum.each(fn {_, socket_id} ->
15+
Phoenix.PubSub.subscribe(@pubsub_name, "client:#{socket_id}")
16+
end)
17+
1118
{:ok, socket}
1219
end
1320

lib/live_debugger/client/socket.ex

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@ defmodule LiveDebugger.Client.Socket do
66
channel("client:*", LiveDebugger.Client.Channel)
77

88
@impl true
9-
def connect(%{"socketID" => socket_id}, socket) do
10-
socket = assign(socket, :debugged_socket_id, socket_id)
9+
def connect(%{"socketID" => socket_id} = params, socket) do
10+
socket =
11+
socket
12+
|> assign(:debugged_socket_id, socket_id)
13+
|> assign(:root_socket_ids, params["rootSocketIDs"] || %{})
14+
1115
{:ok, socket}
1216
end
1317

priv/static/client.js

Lines changed: 6 additions & 6 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)