Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Use shadow dom to render the toolbar
  • Loading branch information
federicobond committed Apr 22, 2026
commit 89960a582371f0a6964c96fccb6698ef311fa6d7
4 changes: 2 additions & 2 deletions debug_toolbar/static/debug_toolbar/css/toolbar.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* Variable definitions */
:root {
:host {
/* Font families are the same as in Django admin/css/base.css */
--djdt-font-family-primary:
"Segoe UI", system-ui, Roboto, "Helvetica Neue", Arial, sans-serif,
Expand All @@ -13,7 +13,7 @@
"Noto Color Emoji";
}

:root,
:host,
#djDebug[data-theme="light"] {
--djdt-font-color: black;
--djdt-background-color: white;
Expand Down
8 changes: 4 additions & 4 deletions debug_toolbar/static/debug_toolbar/js/history.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { $$, ajaxForm, replaceToolbarState } from "./utils.js";
import { $$, ajaxForm, getDebugElement, replaceToolbarState } from "./utils.js";

const djDebug = document.getElementById("djDebug");
const djDebug = getDebugElement();

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

function refreshHistory() {
const formTarget = djDebug.querySelector(".refreshHistory");
const container = document.getElementById("djdtHistoryRequests");
const container = djDebug.querySelector("#djdtHistoryRequests");
const oldIds = new Set(
pluckData(
container.querySelectorAll("tr[data-request-id]"),
Expand Down Expand Up @@ -85,7 +85,7 @@ function switchHistory(newRequestId) {

ajaxForm(formTarget).then((data) => {
if (Object.keys(data).length === 0) {
const container = document.getElementById("djdtHistoryRequests");
const container = djDebug.querySelector("#djdtHistoryRequests");
container.querySelector(
`button[data-request-id="${newRequestId}"]`
).innerHTML = "Switch [EXPIRED]";
Expand Down
9 changes: 5 additions & 4 deletions debug_toolbar/static/debug_toolbar/js/timer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { $$ } from "./utils.js";
import { $$, getDebugElement } from "./utils.js";

const djDebug = getDebugElement();

function insertBrowserTiming() {
const timingOffset = performance.timing.navigationStart;
Expand Down Expand Up @@ -58,10 +60,10 @@ function insertBrowserTiming() {
tbody.appendChild(row);
}

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

const djDebug = document.getElementById("djDebug");
// Insert the browser timing now since it's possible for this
// script to miss the initial panel load event.
insertBrowserTiming();
Expand Down
52 changes: 26 additions & 26 deletions debug_toolbar/static/debug_toolbar/js/toolbar.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import { $$, ajax, debounce, replaceToolbarState } from "./utils.js";
import {
$$,
ajax,
debounce,
getDebugElement,
replaceToolbarState,
} from "./utils.js";

function onKeyDown(event) {
if (event.keyCode === 27) {
djdt.hideOneLevel();
}
}

function getDebugElement() {
// Fetch the debug element from the DOM.
// This is used to avoid writing the element's id
// everywhere the element is being selected. A fixed reference
// to the element should be avoided because the entire DOM could
// be reloaded such as via HTMX boosting.
return document.getElementById("djDebug");
}

const djdt = {
handleDragged: false,
needUpdateOnFetch: false,
Expand All @@ -27,7 +24,7 @@ const djdt = {
return;
}
const panelId = this.className;
const current = document.getElementById(panelId);
const current = djDebug.querySelector(`#${panelId}`);
if ($$.visible(current)) {
djdt.hidePanels();
} else {
Expand Down Expand Up @@ -103,7 +100,7 @@ const djdt = {
}

ajax(url, ajaxData).then((data) => {
const win = document.getElementById("djDebugWindow");
const win = djDebug.querySelector("#djDebugWindow");
win.innerHTML = data.content;
$$.show(win);
});
Expand All @@ -116,7 +113,7 @@ const djdt = {
const toggleClose = "-";
const openMe = this.textContent === toggleOpen;
const name = this.dataset.toggleName;
const container = document.getElementById(`${name}_${id}`);
const container = djDebug.querySelector(`#${name}_${id}`);
for (const el of container.querySelectorAll(".djDebugCollapsed")) {
$$.toggle(el, openMe);
}
Expand Down Expand Up @@ -156,7 +153,7 @@ const djdt = {
});
let startPageY;
let baseY;
const handle = document.getElementById("djDebugToolbarHandle");
const handle = djDebug.querySelector("#djDebugToolbarHandle");
function onHandleMove(event) {
// Chrome can send spurious mousemove events, so don't do anything unless the
// cursor really moved. Otherwise, it will be impossible to expand the toolbar
Expand Down Expand Up @@ -240,16 +237,17 @@ const djdt = {
},
hidePanels() {
const djDebug = getDebugElement();
$$.hide(document.getElementById("djDebugWindow"));
$$.hide(djDebug.querySelector("#djDebugWindow"));
for (const el of djDebug.querySelectorAll(".djdt-panelContent")) {
$$.hide(el);
}
for (const el of document.querySelectorAll("#djDebugToolbar li")) {
for (const el of djDebug.querySelectorAll("#djDebugToolbar li")) {
el.classList.remove("djdt-active");
}
},
ensureHandleVisibility() {
const handle = document.getElementById("djDebugToolbarHandle");
const djDebug = getDebugElement();
const handle = djDebug.querySelector("#djDebugToolbarHandle");
// set handle position
const handleTop = Math.min(
localStorage.getItem("djdt.top") || 265,
Expand All @@ -258,11 +256,12 @@ const djdt = {
handle.style.top = `${handleTop}px`;
},
hideToolbar() {
const djDebug = getDebugElement();
djdt.hidePanels();

$$.hide(document.getElementById("djDebugToolbar"));
$$.hide(djDebug.querySelector("#djDebugToolbar"));

const handle = document.getElementById("djDebugToolbarHandle");
const handle = djDebug.querySelector("#djDebugToolbarHandle");
$$.show(handle);
djdt.ensureHandleVisibility();
window.addEventListener("resize", djdt.ensureHandleVisibility);
Expand All @@ -271,11 +270,12 @@ const djdt = {
localStorage.setItem("djdt.show", "false");
},
hideOneLevel() {
const win = document.getElementById("djDebugWindow");
const djDebug = getDebugElement();
const win = djDebug.querySelector("#djDebugWindow");
if ($$.visible(win)) {
$$.hide(win);
} else {
const toolbar = document.getElementById("djDebugToolbar");
const toolbar = djDebug.querySelector("#djDebugToolbar");
if (toolbar.querySelector("li.djdt-active")) {
djdt.hidePanels();
} else {
Expand All @@ -284,17 +284,17 @@ const djdt = {
}
},
showToolbar() {
const djDebug = getDebugElement();
document.addEventListener("keydown", onKeyDown);
$$.show(document.getElementById("djDebug"));
$$.hide(document.getElementById("djDebugToolbarHandle"));
$$.show(document.getElementById("djDebugToolbar"));
$$.show(djDebug);
$$.hide(djDebug.querySelector("#djDebugToolbarHandle"));
$$.show(djDebug.querySelector("#djDebugToolbar"));
localStorage.setItem("djdt.show", "true");
window.removeEventListener("resize", djdt.ensureHandleVisibility);
},
updateOnAjax() {
const handleAjaxResponse = debounce(async (requestId) => {
const sidebarUrl =
document.getElementById("djDebug").dataset.sidebarUrl;
const sidebarUrl = getDebugElement().dataset.sidebarUrl;

const encodedRequestId = encodeURIComponent(requestId);
const dest = `${sidebarUrl}?request_id=${encodedRequestId}`;
Expand Down
21 changes: 16 additions & 5 deletions debug_toolbar/static/debug_toolbar/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ const $$ = {
},
};

function getDebugElement() {
// Fetch the debug element from the DOM.
Comment thread
federicobond marked this conversation as resolved.
Outdated
// This is used to avoid writing the element's id
// everywhere the element is being selected. A fixed reference
// to the element should be avoided because the entire DOM could
// be reloaded such as via HTMX boosting.
const root = document.getElementById("djDebugRoot").shadowRoot;
return root.querySelector("#djDebug");
}

function ajax(url, init) {
return fetch(url, Object.assign({ credentials: "same-origin" }, init))
.then((response) => {
Expand All @@ -89,7 +99,8 @@ function ajax(url, init) {
);
})
.catch((error) => {
const win = document.getElementById("djDebugWindow");
const djDebug = getDebugElement();
const win = djDebug.querySelector("#djDebugWindow");
win.innerHTML = `<div class="djDebugPanelTitle"><h3>${error.message}</h3><button type="button" class="djDebugClose">»</button></div>`;
$$.show(win);
throw error;
Expand All @@ -110,14 +121,14 @@ function ajaxForm(element) {
}

function replaceToolbarState(newRequestId, data) {
const djDebug = document.getElementById("djDebug");
const djDebug = getDebugElement();
djDebug.setAttribute("data-request-id", newRequestId);
// Check if response is empty, it could be due to an expired requestId.
for (const panelId of Object.keys(data)) {
const panel = document.getElementById(panelId);
const panel = djDebug.querySelector(`#${panelId}`);
if (panel) {
panel.outerHTML = data[panelId].content;
document.getElementById(`djdt-${panelId}`).outerHTML =
djDebug.querySelector(`#djdt-${panelId}`).outerHTML =
data[panelId].button;
}
}
Expand All @@ -140,4 +151,4 @@ export function debounce(func, timeout) {
};
}

export { $$, ajax, ajaxForm, replaceToolbarState };
export { $$, ajax, ajaxForm, getDebugElement, replaceToolbarState };
79 changes: 41 additions & 38 deletions debug_toolbar/templates/debug_toolbar/base.html
Original file line number Diff line number Diff line change
@@ -1,43 +1,46 @@
{% load i18n static %}
{% block css %}
<link{% if toolbar.csp_nonce %} nonce="{{ toolbar.csp_nonce }}"{% endif %} rel="stylesheet" href="{% static 'debug_toolbar/css/print.css' %}" media="print">
<link{% if toolbar.csp_nonce %} nonce="{{ toolbar.csp_nonce }}"{% endif %} rel="stylesheet" href="{% static 'debug_toolbar/css/toolbar.css' %}">
{% endblock css %}
{% block js %}
<script{% if toolbar.csp_nonce %} nonce="{{ toolbar.csp_nonce }}"{% endif %} type="module" src="{% static 'debug_toolbar/js/toolbar.js' %}" async></script>
{% endblock js %}
<div id="djDebug" class="djdt-hidden" dir="ltr"
{% if not toolbar.should_render_panels %}
data-request-id="{{ toolbar.request_id }}"
data-render-panel-url="{% url 'djdt:render_panel' %}"
{% endif %}
{% url 'djdt:history_sidebar' as history_url %}
{% if history_url %}
data-sidebar-url="{{ history_url }}"
{% endif %}
data-default-show="{% if toolbar.config.SHOW_COLLAPSED %}false{% else %}true{% endif %}"
{{ toolbar.config.ROOT_TAG_EXTRA_ATTRS|safe }} data-update-on-fetch="{{ toolbar.config.UPDATE_ON_FETCH }}">
<div class="djdt-hidden" id="djDebugToolbar">
<ul id="djDebugPanelList">
<li><a id="djHideToolBarButton" href="#" title="{% translate 'Hide toolbar' %}">{% translate "Hide" %} »</a></li>
<li>
<a id="djToggleThemeButton" href="#" title="{% translate 'Toggle Theme' %}">
{% translate "Toggle Theme" %} {% include "debug_toolbar/includes/theme_selector.html" %}
</a>
</li>
<div id="djDebugShadowRootContainer" {{ toolbar.config.ROOT_TAG_EXTRA_ATTRS|safe }}>
<template shadowrootmode="open">
{% block css %}
<link{% if toolbar.csp_nonce %} nonce="{{ toolbar.csp_nonce }}"{% endif %} rel="stylesheet" href="{% static 'debug_toolbar/css/print.css' %}" media="print">
<link{% if toolbar.csp_nonce %} nonce="{{ toolbar.csp_nonce }}"{% endif %} rel="stylesheet" href="{% static 'debug_toolbar/css/toolbar.css' %}">
{% endblock css %}
{% block js %}
<script{% if toolbar.csp_nonce %} nonce="{{ toolbar.csp_nonce }}"{% endif %} type="module" src="{% static 'debug_toolbar/js/toolbar.js' %}" async></script>
{% endblock js %}
<div id="djDebug" class="djdt-hidden" dir="ltr"
{% if not toolbar.should_render_panels %}
data-request-id="{{ toolbar.request_id }}"
data-render-panel-url="{% url 'djdt:render_panel' %}"
{% endif %}
{% url 'djdt:history_sidebar' as history_url %}
{% if history_url %}
data-sidebar-url="{{ history_url }}"
{% endif %}
data-default-show="{% if toolbar.config.SHOW_COLLAPSED %}false{% else %}true{% endif %}"
data-update-on-fetch="{{ toolbar.config.UPDATE_ON_FETCH }}">
<div class="djdt-hidden" id="djDebugToolbarHandle">
<div title="{% translate 'Show toolbar' %}" id="djShowToolBarButton">
<span id="djShowToolBarD">D</span><span id="djShowToolBarJ">J</span>DT
</div>
</div>
<div class="djdt-hidden" id="djDebugToolbar">
<ul id="djDebugPanelList">
<li><a id="djHideToolBarButton" href="#" title="{% translate 'Hide toolbar' %}">{% translate "Hide" %} »</a></li>
<li>
<a id="djToggleThemeButton" href="#" title="{% translate 'Toggle Theme' %}">
{% translate "Toggle Theme" %} {% include "debug_toolbar/includes/theme_selector.html" %}
</a>
</li>
{% for panel in toolbar.panels %}
{% include "debug_toolbar/includes/panel_button.html" %}
{% endfor %}
</ul>
</div>
{% for panel in toolbar.panels %}
{% include "debug_toolbar/includes/panel_button.html" %}
{% include "debug_toolbar/includes/panel_content.html" %}
{% endfor %}
</ul>
</div>
<div class="djdt-hidden" id="djDebugToolbarHandle">
<div title="{% translate 'Show toolbar' %}" id="djShowToolBarButton">
<span id="djShowToolBarD">D</span><span id="djShowToolBarJ">J</span>DT
<div id="djDebugWindow" class="djdt-panelContent djdt-hidden"></div>
</div>
</div>

{% for panel in toolbar.panels %}
{% include "debug_toolbar/includes/panel_content.html" %}
{% endfor %}
<div id="djDebugWindow" class="djdt-panelContent djdt-hidden"></div>
</template>
</div>
2 changes: 1 addition & 1 deletion docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ could add a **debug_toolbar/base.html** template override to your project:

{% block css %}{{ block.super }}
<style>
:root {
:host {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work because the #djDebug selector takes priority. Regardless though, we should call this out in the release notes. The original :root selector won't work after this change.

--djdt-font-family-primary: 'Roboto', sans-serif;
}
</style>
Expand Down
4 changes: 2 additions & 2 deletions docs/panels.rst
Original file line number Diff line number Diff line change
Expand Up @@ -444,14 +444,14 @@ Events

.. code-block:: javascript

import { $$ } from "./utils.js";
import { $$, getDebugElement } from "./utils.js";
function addCustomMetrics() {
// Logic to process/add custom metrics here.

// Be sure to cover the case of this function being called twice
// due to file being loaded asynchronously.
}
const djDebug = document.getElementById("djDebug");
const djDebug = getDebugElement();
$$.onPanelRender(djDebug, "CustomPanel", addCustomMetrics);
// Since a panel's scripts are loaded asynchronously, it's possible that
// the above statement would occur after the djdt.panel.render event has
Expand Down