Skip to content

Commit 46c901e

Browse files
committed
Use shadow dom to render the toolbar
1 parent 417b361 commit 46c901e

File tree

8 files changed

+93
-81
lines changed

8 files changed

+93
-81
lines changed

debug_toolbar/static/debug_toolbar/css/toolbar.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* Variable definitions */
2-
:root {
2+
:host {
33
/* Font families are the same as in Django admin/css/base.css */
44
--djdt-font-family-primary:
55
"Segoe UI", system-ui, Roboto, "Helvetica Neue", Arial, sans-serif,
@@ -13,7 +13,7 @@
1313
"Noto Color Emoji";
1414
}
1515

16-
:root,
16+
:host,
1717
#djDebug[data-theme="light"] {
1818
--djdt-font-color: black;
1919
--djdt-background-color: white;

debug_toolbar/static/debug_toolbar/js/history.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { $$, ajaxForm, replaceToolbarState } from "./utils.js";
1+
import { $$, ajaxForm, getDebugElement, replaceToolbarState } from "./utils.js";
22

3-
const djDebug = document.getElementById("djDebug");
3+
const djDebug = getDebugElement();
44

55
function difference(setA, setB) {
66
const _difference = new Set(setA);
@@ -19,7 +19,7 @@ function pluckData(nodes, key) {
1919

2020
function refreshHistory() {
2121
const formTarget = djDebug.querySelector(".refreshHistory");
22-
const container = document.getElementById("djdtHistoryRequests");
22+
const container = djDebug.querySelector("#djdtHistoryRequests");
2323
const oldIds = new Set(
2424
pluckData(
2525
container.querySelectorAll("tr[data-request-id]"),
@@ -85,7 +85,7 @@ function switchHistory(newRequestId) {
8585

8686
ajaxForm(formTarget).then((data) => {
8787
if (Object.keys(data).length === 0) {
88-
const container = document.getElementById("djdtHistoryRequests");
88+
const container = djDebug.querySelector("#djdtHistoryRequests");
8989
container.querySelector(
9090
`button[data-request-id="${newRequestId}"]`
9191
).innerHTML = "Switch [EXPIRED]";

debug_toolbar/static/debug_toolbar/js/timer.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { $$ } from "./utils.js";
1+
import { $$, getDebugElement } from "./utils.js";
2+
3+
const djDebug = getDebugElement();
24

35
function insertBrowserTiming() {
46
const timingOffset = performance.timing.navigationStart;
@@ -58,10 +60,10 @@ function insertBrowserTiming() {
5860
tbody.appendChild(row);
5961
}
6062

61-
const browserTiming = document.getElementById("djDebugBrowserTiming");
63+
const browserTiming = djDebug.querySelector("#djDebugBrowserTiming");
6264
// Determine if the browser timing section has already been rendered.
6365
if (browserTiming.classList.contains("djdt-hidden")) {
64-
const tbody = document.getElementById("djDebugBrowserTimingTableBody");
66+
const tbody = djDebug.querySelector("#djDebugBrowserTimingTableBody");
6567
// This is a reasonably complete and ordered set of timing periods (2 params) and events (1 param)
6668
addRow(tbody, "domainLookupStart", "domainLookupEnd");
6769
addRow(tbody, "connectStart", "connectEnd");
@@ -75,7 +77,6 @@ function insertBrowserTiming() {
7577
}
7678
}
7779

78-
const djDebug = document.getElementById("djDebug");
7980
// Insert the browser timing now since it's possible for this
8081
// script to miss the initial panel load event.
8182
insertBrowserTiming();

debug_toolbar/static/debug_toolbar/js/toolbar.js

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,11 @@
1-
import { $$, ajax, debounce, replaceToolbarState } from "./utils.js";
1+
import { $$, getDebugElement, ajax, debounce, replaceToolbarState } from "./utils.js";
22

33
function onKeyDown(event) {
44
if (event.keyCode === 27) {
55
djdt.hideOneLevel();
66
}
77
}
88

9-
function getDebugElement() {
10-
// Fetch the debug element from the DOM.
11-
// This is used to avoid writing the element's id
12-
// everywhere the element is being selected. A fixed reference
13-
// to the element should be avoided because the entire DOM could
14-
// be reloaded such as via HTMX boosting.
15-
return document.getElementById("djDebug");
16-
}
17-
189
const djdt = {
1910
handleDragged: false,
2011
needUpdateOnFetch: false,
@@ -27,7 +18,7 @@ const djdt = {
2718
return;
2819
}
2920
const panelId = this.className;
30-
const current = document.getElementById(panelId);
21+
const current = djDebug.querySelector(`#${panelId}`);
3122
if ($$.visible(current)) {
3223
djdt.hidePanels();
3324
} else {
@@ -103,7 +94,7 @@ const djdt = {
10394
}
10495

10596
ajax(url, ajaxData).then((data) => {
106-
const win = document.getElementById("djDebugWindow");
97+
const win = djDebug.querySelector("#djDebugWindow");
10798
win.innerHTML = data.content;
10899
$$.show(win);
109100
});
@@ -116,7 +107,7 @@ const djdt = {
116107
const toggleClose = "-";
117108
const openMe = this.textContent === toggleOpen;
118109
const name = this.dataset.toggleName;
119-
const container = document.getElementById(`${name}_${id}`);
110+
const container = djDebug.querySelector(`#${name}_${id}`);
120111
for (const el of container.querySelectorAll(".djDebugCollapsed")) {
121112
$$.toggle(el, openMe);
122113
}
@@ -156,7 +147,7 @@ const djdt = {
156147
});
157148
let startPageY;
158149
let baseY;
159-
const handle = document.getElementById("djDebugToolbarHandle");
150+
const handle = djDebug.querySelector("#djDebugToolbarHandle");
160151
function onHandleMove(event) {
161152
// Chrome can send spurious mousemove events, so don't do anything unless the
162153
// cursor really moved. Otherwise, it will be impossible to expand the toolbar
@@ -240,16 +231,17 @@ const djdt = {
240231
},
241232
hidePanels() {
242233
const djDebug = getDebugElement();
243-
$$.hide(document.getElementById("djDebugWindow"));
234+
$$.hide(djDebug.querySelector("#djDebugWindow"));
244235
for (const el of djDebug.querySelectorAll(".djdt-panelContent")) {
245236
$$.hide(el);
246237
}
247-
for (const el of document.querySelectorAll("#djDebugToolbar li")) {
238+
for (const el of djDebug.querySelectorAll("#djDebugToolbar li")) {
248239
el.classList.remove("djdt-active");
249240
}
250241
},
251242
ensureHandleVisibility() {
252-
const handle = document.getElementById("djDebugToolbarHandle");
243+
const djDebug = getDebugElement();
244+
const handle = djDebug.querySelector("#djDebugToolbarHandle");
253245
// set handle position
254246
const handleTop = Math.min(
255247
localStorage.getItem("djdt.top") || 265,
@@ -258,11 +250,12 @@ const djdt = {
258250
handle.style.top = `${handleTop}px`;
259251
},
260252
hideToolbar() {
253+
const djDebug = getDebugElement();
261254
djdt.hidePanels();
262255

263-
$$.hide(document.getElementById("djDebugToolbar"));
256+
$$.hide(djDebug.querySelector("#djDebugToolbar"));
264257

265-
const handle = document.getElementById("djDebugToolbarHandle");
258+
const handle = djDebug.querySelector("#djDebugToolbarHandle");
266259
$$.show(handle);
267260
djdt.ensureHandleVisibility();
268261
window.addEventListener("resize", djdt.ensureHandleVisibility);
@@ -271,11 +264,12 @@ const djdt = {
271264
localStorage.setItem("djdt.show", "false");
272265
},
273266
hideOneLevel() {
274-
const win = document.getElementById("djDebugWindow");
267+
const djDebug = getDebugElement();
268+
const win = djDebug.querySelector("#djDebugWindow");
275269
if ($$.visible(win)) {
276270
$$.hide(win);
277271
} else {
278-
const toolbar = document.getElementById("djDebugToolbar");
272+
const toolbar = djDebug.querySelector("#djDebugToolbar");
279273
if (toolbar.querySelector("li.djdt-active")) {
280274
djdt.hidePanels();
281275
} else {
@@ -284,16 +278,17 @@ const djdt = {
284278
}
285279
},
286280
showToolbar() {
281+
const djDebug = getDebugElement();
287282
document.addEventListener("keydown", onKeyDown);
288-
$$.show(document.getElementById("djDebug"));
289-
$$.hide(document.getElementById("djDebugToolbarHandle"));
290-
$$.show(document.getElementById("djDebugToolbar"));
283+
$$.show(djDebug);
284+
$$.hide(djDebug.querySelector("#djDebugToolbarHandle"));
285+
$$.show(djDebug.querySelector("#djDebugToolbar"));
291286
localStorage.setItem("djdt.show", "true");
292287
window.removeEventListener("resize", djdt.ensureHandleVisibility);
293288
},
294289
updateOnAjax() {
295290
const sidebarUrl =
296-
document.getElementById("djDebug").dataset.sidebarUrl;
291+
getDebugElement().dataset.sidebarUrl;
297292
const slowjax = debounce(ajax, 200);
298293

299294
function handleAjaxResponse(requestId) {

debug_toolbar/static/debug_toolbar/js/utils.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,16 @@ const $$ = {
7070
},
7171
};
7272

73+
function getDebugElement() {
74+
// Fetch the debug element from the DOM.
75+
// This is used to avoid writing the element's id
76+
// everywhere the element is being selected. A fixed reference
77+
// to the element should be avoided because the entire DOM could
78+
// be reloaded such as via HTMX boosting.
79+
const root = document.getElementById("djDebugRoot").shadowRoot;
80+
return root.querySelector("#djDebug");
81+
}
82+
7383
function ajax(url, init) {
7484
return fetch(url, Object.assign({ credentials: "same-origin" }, init))
7585
.then((response) => {
@@ -89,7 +99,8 @@ function ajax(url, init) {
8999
);
90100
})
91101
.catch((error) => {
92-
const win = document.getElementById("djDebugWindow");
102+
const djDebug = getDebugElement();
103+
const win = djDebug.querySelector("#djDebugWindow");
93104
win.innerHTML = `<div class="djDebugPanelTitle"><h3>${error.message}</h3><button type="button" class="djDebugClose">»</button></div>`;
94105
$$.show(win);
95106
throw error;
@@ -110,14 +121,14 @@ function ajaxForm(element) {
110121
}
111122

112123
function replaceToolbarState(newRequestId, data) {
113-
const djDebug = document.getElementById("djDebug");
124+
const djDebug = getDebugElement();
114125
djDebug.setAttribute("data-request-id", newRequestId);
115126
// Check if response is empty, it could be due to an expired requestId.
116127
for (const panelId of Object.keys(data)) {
117-
const panel = document.getElementById(panelId);
128+
const panel = djDebug.querySelector(`#${panelId}`);
118129
if (panel) {
119130
panel.outerHTML = data[panelId].content;
120-
document.getElementById(`djdt-${panelId}`).outerHTML =
131+
djDebug.querySelector(`#djdt-${panelId}`).outerHTML =
121132
data[panelId].button;
122133
}
123134
}
@@ -141,4 +152,4 @@ function debounce(func, delay) {
141152
};
142153
}
143154

144-
export { $$, ajax, ajaxForm, replaceToolbarState, debounce };
155+
export { $$, getDebugElement, ajax, ajaxForm, replaceToolbarState, debounce };
Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,48 @@
11
{% load i18n static %}
2-
{% block css %}
3-
<link{% if toolbar.csp_nonce %} nonce="{{ toolbar.csp_nonce }}"{% endif %} rel="stylesheet" href="{% static 'debug_toolbar/css/print.css' %}" media="print">
4-
<link{% if toolbar.csp_nonce %} nonce="{{ toolbar.csp_nonce }}"{% endif %} rel="stylesheet" href="{% static 'debug_toolbar/css/toolbar.css' %}">
5-
{% endblock css %}
6-
{% block js %}
7-
<script{% if toolbar.csp_nonce %} nonce="{{ toolbar.csp_nonce }}"{% endif %} type="module" src="{% static 'debug_toolbar/js/toolbar.js' %}" async></script>
8-
{% endblock js %}
9-
<div id="djDebug" class="djdt-hidden" dir="ltr"
10-
{% if not toolbar.should_render_panels %}
11-
data-request-id="{{ toolbar.request_id }}"
12-
data-render-panel-url="{% url 'djdt:render_panel' %}"
13-
{% endif %}
14-
{% url 'djdt:history_sidebar' as history_url %}
15-
{% if history_url %}
16-
data-sidebar-url="{{ history_url }}"
17-
{% endif %}
18-
data-default-show="{% if toolbar.config.SHOW_COLLAPSED %}false{% else %}true{% endif %}"
19-
{{ toolbar.config.ROOT_TAG_EXTRA_ATTRS|safe }} data-update-on-fetch="{{ toolbar.config.UPDATE_ON_FETCH }}">
20-
<div class="djdt-hidden" id="djDebugToolbar">
21-
<ul id="djDebugPanelList">
22-
<li><a id="djHideToolBarButton" href="#" title="{% translate 'Hide toolbar' %}">{% translate "Hide" %} »</a></li>
23-
<li>
24-
<a id="djToggleThemeButton" href="#" title="{% translate 'Toggle Theme' %}">
25-
{% translate "Toggle Theme" %} {% include "debug_toolbar/includes/theme_selector.html" %}
26-
</a>
27-
</li>
2+
<div id="djDebugRoot" {{ toolbar.config.ROOT_TAG_EXTRA_ATTRS|safe }}>
3+
<template shadowrootmode="open">
4+
{% block css %}
5+
<link{% if toolbar.csp_nonce %} nonce="{{ toolbar.csp_nonce }}"{% endif %} rel="stylesheet" href="{% static 'debug_toolbar/css/print.css' %}" media="print">
6+
<link{% if toolbar.csp_nonce %} nonce="{{ toolbar.csp_nonce }}"{% endif %} rel="stylesheet" href="{% static 'debug_toolbar/css/toolbar.css' %}">
7+
{% endblock css %}
8+
{% block js %}
9+
<script{% if toolbar.csp_nonce %} nonce="{{ toolbar.csp_nonce }}"{% endif %} type="module" src="{% static 'debug_toolbar/js/toolbar.js' %}" async></script>
10+
{% endblock js %}
11+
<div id="djDebug" class="djdt-hidden" dir="ltr"
12+
{% if not toolbar.should_render_panels %}
13+
data-request-id="{{ toolbar.request_id }}"
14+
data-render-panel-url="{% url 'djdt:render_panel' %}"
15+
{% endif %}
16+
{% url 'djdt:history_sidebar' as history_url %}
17+
{% if history_url %}
18+
data-sidebar-url="{{ history_url }}"
19+
{% endif %}
20+
data-default-show="{% if toolbar.config.SHOW_COLLAPSED %}false{% else %}true{% endif %}"
21+
data-update-on-fetch="{{ toolbar.config.UPDATE_ON_FETCH }}">
22+
<link{% if toolbar.csp_nonce %} nonce="{{ toolbar.csp_nonce }}"{% endif %} rel="stylesheet" href="{% static 'debug_toolbar/css/toolbar.css' %}">
23+
24+
<div class="djdt-hidden" id="djDebugToolbarHandle">
25+
<div title="{% translate 'Show toolbar' %}" id="djShowToolBarButton">
26+
<span id="djShowToolBarD">D</span><span id="djShowToolBarJ">J</span>DT
27+
</div>
28+
</div>
29+
<div class="djdt-hidden" id="djDebugToolbar">
30+
<ul id="djDebugPanelList">
31+
<li><a id="djHideToolBarButton" href="#" title="{% translate 'Hide toolbar' %}">{% translate "Hide" %} »</a></li>
32+
<li>
33+
<a id="djToggleThemeButton" href="#" title="{% translate 'Toggle Theme' %}">
34+
{% translate "Toggle Theme" %} {% include "debug_toolbar/includes/theme_selector.html" %}
35+
</a>
36+
</li>
37+
{% for panel in toolbar.panels %}
38+
{% include "debug_toolbar/includes/panel_button.html" %}
39+
{% endfor %}
40+
</ul>
41+
</div>
2842
{% for panel in toolbar.panels %}
29-
{% include "debug_toolbar/includes/panel_button.html" %}
43+
{% include "debug_toolbar/includes/panel_content.html" %}
3044
{% endfor %}
31-
</ul>
32-
</div>
33-
<div class="djdt-hidden" id="djDebugToolbarHandle">
34-
<div title="{% translate 'Show toolbar' %}" id="djShowToolBarButton">
35-
<span id="djShowToolBarD">D</span><span id="djShowToolBarJ">J</span>DT
45+
<div id="djDebugWindow" class="djdt-panelContent djdt-hidden"></div>
3646
</div>
37-
</div>
38-
39-
{% for panel in toolbar.panels %}
40-
{% include "debug_toolbar/includes/panel_content.html" %}
41-
{% endfor %}
42-
<div id="djDebugWindow" class="djdt-panelContent djdt-hidden"></div>
47+
</template>
4348
</div>

docs/configuration.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ could add a **debug_toolbar/base.html** template override to your project:
439439
440440
{% block css %}{{ block.super }}
441441
<style>
442-
:root {
442+
:host {
443443
--djdt-font-family-primary: 'Roboto', sans-serif;
444444
}
445445
</style>

docs/panels.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -444,14 +444,14 @@ Events
444444

445445
.. code-block:: javascript
446446
447-
import { $$ } from "./utils.js";
447+
import { $$, getDebugElement } from "./utils.js";
448448
function addCustomMetrics() {
449449
// Logic to process/add custom metrics here.
450450
451451
// Be sure to cover the case of this function being called twice
452452
// due to file being loaded asynchronously.
453453
}
454-
const djDebug = document.getElementById("djDebug");
454+
const djDebug = getDebugElement();
455455
$$.onPanelRender(djDebug, "CustomPanel", addCustomMetrics);
456456
// Since a panel's scripts are loaded asynchronously, it's possible that
457457
// the above statement would occur after the djdt.panel.render event has

0 commit comments

Comments
 (0)