Skip to content

Commit 9ccff96

Browse files
authored
Enhancement: isolate LiveDebugger injected assets from debugged app (#964)
* Migrate to shadow DOM * Remove CSS injection * Format * Remove comment * Remove Phoenix LiveReload
1 parent ff06519 commit 9ccff96

9 files changed

Lines changed: 148 additions & 64 deletions

File tree

assets/client/client.css

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
position: fixed;
33
height: 40px;
44
width: 40px;
5+
box-sizing: border-box;
56
padding-left: 5px;
67
padding-right: 5px;
78
border-radius: 10px;
@@ -40,11 +41,26 @@
4041
background: white;
4142
border: 1px solid #e2e8f0;
4243
border-radius: 8px;
44+
box-sizing: border-box;
4345
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
4446
min-width: 160px;
4547
z-index: 10000;
4648
display: none;
4749
font-size: 14px;
50+
font-family:
51+
ui-sans-serif,
52+
system-ui,
53+
-apple-system,
54+
BlinkMacSystemFont,
55+
'Segoe UI',
56+
Roboto,
57+
Helvetica,
58+
Arial,
59+
sans-serif;
60+
}
61+
62+
#live-debugger-debug-tooltip p {
63+
margin: 0;
4864
}
4965

5066
#live-debugger-debug-tooltip .live-debugger-tooltip-option {
@@ -82,6 +98,16 @@
8298
pointer-events: none;
8399
padding: 8px 12px;
84100
color: #333;
101+
font-family:
102+
ui-sans-serif,
103+
system-ui,
104+
-apple-system,
105+
BlinkMacSystemFont,
106+
'Segoe UI',
107+
Roboto,
108+
Helvetica,
109+
Arial,
110+
sans-serif;
85111
}
86112

87113
.live-debugger-tooltip-content {

assets/client/client.js

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,36 @@ window.document.addEventListener('DOMContentLoaded', async () => {
3838
debugChannel.push('pong', resp);
3939
});
4040

41-
initElementInspection({ baseURL, debugChannel, socketID: mainSocketID });
42-
initTooltip();
43-
initDebugMenu(metaTag, sessionURL, debugChannel);
44-
initHighlight(debugChannel);
41+
const shadowHost = document.createElement('div');
42+
shadowHost.style.position = 'absolute';
43+
shadowHost.style.width = '0px';
44+
shadowHost.style.height = '0px';
45+
shadowHost.style.left = '0px';
46+
shadowHost.style.top = '0px';
47+
shadowHost.style.zIndex = '2147483647';
48+
document.body.appendChild(shadowHost);
49+
50+
const shadowRoot = shadowHost.attachShadow({ mode: 'closed' });
51+
52+
const cssLink = document.createElement('link');
53+
cssLink.rel = 'stylesheet';
54+
cssLink.href = `${baseURL}/assets/live_debugger/client.css`;
55+
shadowRoot.appendChild(cssLink);
56+
57+
const { debugButton } = initDebugMenu(
58+
metaTag,
59+
sessionURL,
60+
debugChannel,
61+
shadowRoot
62+
);
63+
initElementInspection({
64+
baseURL,
65+
debugChannel,
66+
socketID: mainSocketID,
67+
debugButton,
68+
});
69+
initTooltip(shadowRoot);
70+
initHighlight(debugChannel, shadowRoot);
4571
}
4672

4773
console.info(`LiveDebugger available at: ${baseURL}`);

assets/client/components/debug_menu.js

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,57 @@ import initDebugOptions from './debug_options/debug_options';
33
import { dispatchCustomEvent } from '../utils/dom';
44
import { isDebugButtonEnabled } from '../utils/meta';
55

6-
export default function initDebugMenu(metaTag, liveDebuggerURL, debugChannel) {
6+
export default function initDebugMenu(
7+
metaTag,
8+
liveDebuggerURL,
9+
debugChannel,
10+
shadowRoot
11+
) {
712
const debugButton = initDebugButton();
813
const debugMenu = initDebugOptions({ liveDebuggerURL, debugChannel });
14+
let suppressOutsideClick = false;
915

10-
if (isDebugButtonEnabled(metaTag)) {
11-
document.body.appendChild(debugButton);
12-
document.body.appendChild(debugMenu);
13-
}
16+
const suppressNext = () => {
17+
suppressOutsideClick = true;
18+
setTimeout(() => {
19+
suppressOutsideClick = false;
20+
}, 0);
21+
};
22+
23+
debugButton.addEventListener('click', suppressNext, true);
24+
debugMenu.addEventListener('click', suppressNext, true);
25+
26+
const mount = () => {
27+
shadowRoot.appendChild(debugButton);
28+
shadowRoot.appendChild(debugMenu);
29+
};
30+
31+
const unmount = () => {
32+
debugButton.remove();
33+
debugMenu.remove();
34+
};
35+
36+
if (isDebugButtonEnabled(metaTag)) mount();
1437

1538
debugChannel.on('toggle-debug-button', ({ enabled }) => {
1639
if (enabled) {
17-
document.body.appendChild(debugButton);
18-
document.body.appendChild(debugMenu);
40+
mount();
1941
} else {
20-
debugButton.remove();
21-
debugMenu.remove();
42+
unmount();
2243
}
2344
});
2445

2546
// Hide menu when clicking outside
2647
document.addEventListener('click', (event) => {
27-
if (
28-
!debugButton.contains(event.target) &&
29-
!debugMenu.contains(event.target)
30-
) {
48+
const path = event.composedPath?.() ?? [event.target];
49+
const clickedInside =
50+
path.includes(debugButton) ||
51+
path.includes(debugMenu) ||
52+
path.some((node) => node?.getRootNode?.() === shadowRoot);
53+
54+
if (suppressOutsideClick) return;
55+
56+
if (!clickedInside) {
3157
dispatchCustomEvent('lvdbg:click-outside-debug-menu');
3258
}
3359
});

assets/client/components/tooltip/tooltip.js

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,25 @@ import { addTooltipArrow } from './tooltip_arrow';
55
const tooltipID = 'live-debugger-tooltip';
66
const highlightElementID = 'live-debugger-highlight-element';
77

8-
function getHighlightElement() {
9-
return document.getElementById(highlightElementID);
8+
function getHighlightElement(shadowRoot) {
9+
return shadowRoot.querySelector(`#${highlightElementID}`);
1010
}
1111

12-
function removeTooltip() {
13-
const existingTooltip = document.getElementById(tooltipID);
12+
function removeTooltip(shadowRoot) {
13+
const existingTooltip = shadowRoot.querySelector(`#${tooltipID}`);
1414
if (existingTooltip) {
1515
existingTooltip.remove();
1616
}
1717
}
1818

19-
function showTooltip(data) {
20-
const highlightElement = getHighlightElement();
19+
function showTooltip(data, shadowRoot) {
20+
const highlightElement = getHighlightElement(shadowRoot);
2121
if (!highlightElement) {
2222
return;
2323
}
2424

25-
removeTooltip();
26-
const tooltip = createTooltip(data);
25+
removeTooltip(shadowRoot);
26+
const tooltip = createTooltip(data, shadowRoot);
2727
const positionData = positionTooltip(tooltip, highlightElement);
2828

2929
if (positionData) {
@@ -36,9 +36,9 @@ function showTooltip(data) {
3636
}
3737
}
3838

39-
function handleTooltipResize() {
40-
const tooltip = document.getElementById(tooltipID);
41-
const highlightElement = getHighlightElement();
39+
function handleTooltipResize(shadowRoot) {
40+
const tooltip = shadowRoot.querySelector(`#${tooltipID}`);
41+
const highlightElement = getHighlightElement(shadowRoot);
4242

4343
if (tooltip && highlightElement) {
4444
const positionData = positionTooltip(tooltip, highlightElement);
@@ -54,22 +54,26 @@ function handleTooltipResize() {
5454
}
5555
}
5656

57-
function handleShowTooltipEvent(event) {
58-
showTooltip(event.detail);
57+
function handleShowTooltipEvent(event, shadowRoot) {
58+
showTooltip(event.detail, shadowRoot);
5959
}
6060

61-
function handleRemoveTooltipEvent() {
62-
removeTooltip();
61+
function handleRemoveTooltipEvent(shadowRoot) {
62+
removeTooltip(shadowRoot);
6363
}
6464

65-
function setupEventListeners() {
66-
window.addEventListener('resize', handleTooltipResize);
67-
window.addEventListener('scroll', handleTooltipResize);
65+
function setupEventListeners(shadowRoot) {
66+
window.addEventListener('resize', () => handleTooltipResize(shadowRoot));
67+
window.addEventListener('scroll', () => handleTooltipResize(shadowRoot));
6868

69-
document.addEventListener('lvdbg:show-tooltip', handleShowTooltipEvent);
70-
document.addEventListener('lvdbg:remove-tooltip', handleRemoveTooltipEvent);
69+
document.addEventListener('lvdbg:show-tooltip', (event) =>
70+
handleShowTooltipEvent(event, shadowRoot)
71+
);
72+
document.addEventListener('lvdbg:remove-tooltip', () =>
73+
handleRemoveTooltipEvent(shadowRoot)
74+
);
7175
}
7276

73-
export default function initTooltip() {
74-
setupEventListeners();
77+
export default function initTooltip(shadowRoot) {
78+
setupEventListeners(shadowRoot);
7579
}

assets/client/components/tooltip/tooltip_creator.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@ function setModuleName(tooltip, data) {
4141
moduleName.textContent = data.module || 'Element';
4242
}
4343

44-
export function createTooltip(data) {
44+
export function createTooltip(data, shadowRoot) {
4545
const tooltip = createElement(tooltipHtml);
4646

4747
setModuleName(tooltip, data);
4848
setTypeIcon(tooltip, data);
4949
populateInfoSection(tooltip, data);
5050

51-
document.body.appendChild(tooltip);
51+
shadowRoot.appendChild(tooltip);
5252
return tooltip;
5353
}

assets/client/services/highlight.js

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ function createHighlightElement(activeElement, detail, id) {
5959
return highlight;
6060
}
6161

62-
function removeHighlightElement() {
63-
const highlightElement = document.getElementById(highlightElementID);
62+
function removeHighlightElement(shadowRoot) {
63+
const highlightElement = shadowRoot.querySelector(`#${highlightElementID}`);
6464

6565
if (highlightElement) {
6666
highlightElement.remove();
@@ -69,8 +69,8 @@ function removeHighlightElement() {
6969
dispatchCustomEvent('lvdbg:remove-tooltip');
7070
}
7171

72-
function handleHighlight({ detail }) {
73-
let highlightElement = document.getElementById(highlightElementID);
72+
function handleHighlight({ detail }, shadowRoot) {
73+
let highlightElement = shadowRoot.querySelector(`#${highlightElementID}`);
7474

7575
if (highlightElement) {
7676
highlightElement.remove();
@@ -95,13 +95,13 @@ function handleHighlight({ detail }) {
9595
highlightElementID
9696
);
9797

98-
document.body.appendChild(highlightElement);
98+
shadowRoot.appendChild(highlightElement);
9999
showTooltip(detail);
100100
}
101101
}
102102

103-
function handleHighlightResize() {
104-
const highlight = document.getElementById(highlightElementID);
103+
function handleHighlightResize(shadowRoot) {
104+
const highlight = shadowRoot.querySelector(`#${highlightElementID}`);
105105
if (highlight) {
106106
const activeElement = document.querySelector(
107107
`[${highlight.dataset.attr}="${highlight.dataset.val}"]`
@@ -115,7 +115,7 @@ function handleHighlightResize() {
115115
}
116116
}
117117

118-
function handlePulse({ detail }) {
118+
function handlePulse({ detail }, shadowRoot) {
119119
const activeElement = document.querySelector(
120120
`[${detail.attr}="${detail.val}"]`
121121
);
@@ -127,7 +127,7 @@ function handlePulse({ detail }) {
127127
highlightPulseElementID
128128
);
129129

130-
document.body.appendChild(highlightPulse);
130+
shadowRoot.appendChild(highlightPulse);
131131

132132
const w = highlightPulse.offsetWidth;
133133
const h = highlightPulse.offsetHeight;
@@ -186,13 +186,21 @@ function showTooltip(detail) {
186186
dispatchCustomEvent('lvdbg:show-tooltip', props);
187187
}
188188

189-
export default function initHighlight(debugChannel) {
190-
document.addEventListener('lvdbg:inspect-highlight', handleHighlight);
191-
document.addEventListener('lvdbg:inspect-pulse', handlePulse);
192-
document.addEventListener('lvdbg:inspect-clear', removeHighlightElement);
189+
export default function initHighlight(debugChannel, shadowRoot) {
190+
document.addEventListener('lvdbg:inspect-highlight', (event) =>
191+
handleHighlight(event, shadowRoot)
192+
);
193+
document.addEventListener('lvdbg:inspect-pulse', (event) =>
194+
handlePulse(event, shadowRoot)
195+
);
196+
document.addEventListener('lvdbg:inspect-clear', () =>
197+
removeHighlightElement(shadowRoot)
198+
);
193199

194-
debugChannel.on('highlight', (e) => handleHighlight({ detail: e }));
195-
debugChannel.on('pulse', (e) => handlePulse({ detail: e }));
200+
debugChannel.on('highlight', (e) =>
201+
handleHighlight({ detail: e }, shadowRoot)
202+
);
203+
debugChannel.on('pulse', (e) => handlePulse({ detail: e }, shadowRoot));
196204

197-
window.addEventListener('resize', handleHighlightResize);
205+
window.addEventListener('resize', () => handleHighlightResize(shadowRoot));
198206
}

assets/client/services/inspect.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export default function initElementInspection({
44
baseURL,
55
debugChannel,
66
socketID,
7+
debugButton,
78
}) {
89
let inspectMode = false;
910
let lastID = null;
@@ -141,7 +142,6 @@ export default function initElementInspection({
141142
inspectMode = false;
142143
lastID = null;
143144

144-
const debugButton = document.getElementById('live-debugger-debug-button');
145145
if (debugButton) debugButton.classList.remove('live-debugger-inspect-mode');
146146

147147
pushClearEvent();
@@ -162,7 +162,6 @@ export default function initElementInspection({
162162

163163
inspectMode = true;
164164

165-
const debugButton = document.getElementById('live-debugger-debug-button');
166165
if (debugButton) debugButton.classList.add('live-debugger-inspect-mode');
167166

168167
document.body.classList.add('live-debugger-inspect-mode');

lib/live_debugger.ex

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ defmodule LiveDebugger do
1717
@default_drainer [shutdown: 1000]
1818

1919
@js_path "assets/live_debugger/client.js"
20-
@css_path "assets/live_debugger/client.css"
2120
@phoenix_path "assets/phoenix/phoenix.js"
2221

2322
def start(_type, _args) do
@@ -121,13 +120,11 @@ defmodule LiveDebugger do
121120
version = Application.spec(@app_name)[:vsn] |> to_string()
122121

123122
live_debugger_js_url = "#{live_debugger_url}/#{@js_path}"
124-
live_debugger_css_url = "#{live_debugger_url}/#{@css_path}"
125123
live_debugger_phoenix_url = "#{live_debugger_url}/#{@phoenix_path}"
126124

127125
assigns = %{
128126
url: live_debugger_url,
129127
js_url: live_debugger_js_url,
130-
css_url: live_debugger_css_url,
131128
phoenix_url: live_debugger_phoenix_url,
132129
browser_features?: browser_features?,
133130
version: version,

0 commit comments

Comments
 (0)