diff --git a/debug_toolbar/panels/__init__.py b/debug_toolbar/panels/__init__.py index ec3445c1e..8fd433c63 100644 --- a/debug_toolbar/panels/__init__.py +++ b/debug_toolbar/panels/__init__.py @@ -107,6 +107,10 @@ def content(self): def scripts(self): """ Scripts used by the HTML content of the panel when it's displayed. + + When a panel is rendered on the frontend, the ``djdt.panel.render`` + JavaScript event will be dispatched. The scripts can listen for + this event to support dynamic functionality. """ return [] diff --git a/debug_toolbar/static/debug_toolbar/js/timer.js b/debug_toolbar/static/debug_toolbar/js/timer.js index 1d4ac19d8..70d3fe5a2 100644 --- a/debug_toolbar/static/debug_toolbar/js/timer.js +++ b/debug_toolbar/static/debug_toolbar/js/timer.js @@ -1,59 +1,75 @@ -const timingOffset = performance.timing.navigationStart, - timingEnd = performance.timing.loadEventEnd, - totalTime = timingEnd - timingOffset; -function getLeft(stat) { - return ((performance.timing[stat] - timingOffset) / totalTime) * 100.0; -} -function getCSSWidth(stat, endStat) { - let width = - ((performance.timing[endStat] - performance.timing[stat]) / totalTime) * - 100.0; - // Calculate relative percent (same as sql panel logic) - width = (100.0 * width) / (100.0 - getLeft(stat)); - return width < 1 ? "2px" : width + "%"; -} -function addRow(tbody, stat, endStat) { - const row = document.createElement("tr"); - if (endStat) { - // Render a start through end bar - row.innerHTML = - "" + - stat.replace("Start", "") + - "" + - '' + - "" + - (performance.timing[stat] - timingOffset) + - " (+" + - (performance.timing[endStat] - performance.timing[stat]) + - ")"; - row.querySelector("rect").setAttribute( - "width", - getCSSWidth(stat, endStat) - ); - } else { - // Render a point in time - row.innerHTML = - "" + - stat + - "" + - '' + - "" + - (performance.timing[stat] - timingOffset) + - ""; - row.querySelector("rect").setAttribute("width", 2); +import { $$ } from "./utils.js"; + +function insertBrowserTiming() { + console.log(["inserted"]); + const timingOffset = performance.timing.navigationStart, + timingEnd = performance.timing.loadEventEnd, + totalTime = timingEnd - timingOffset; + function getLeft(stat) { + return ((performance.timing[stat] - timingOffset) / totalTime) * 100.0; + } + function getCSSWidth(stat, endStat) { + let width = + ((performance.timing[endStat] - performance.timing[stat]) / + totalTime) * + 100.0; + // Calculate relative percent (same as sql panel logic) + width = (100.0 * width) / (100.0 - getLeft(stat)); + return width < 1 ? "2px" : width + "%"; + } + function addRow(tbody, stat, endStat) { + const row = document.createElement("tr"); + if (endStat) { + // Render a start through end bar + row.innerHTML = + "" + + stat.replace("Start", "") + + "" + + '' + + "" + + (performance.timing[stat] - timingOffset) + + " (+" + + (performance.timing[endStat] - performance.timing[stat]) + + ")"; + row.querySelector("rect").setAttribute( + "width", + getCSSWidth(stat, endStat) + ); + } else { + // Render a point in time + row.innerHTML = + "" + + stat + + "" + + '' + + "" + + (performance.timing[stat] - timingOffset) + + ""; + row.querySelector("rect").setAttribute("width", 2); + } + row.querySelector("rect").setAttribute("x", getLeft(stat)); + tbody.appendChild(row); + } + + const browserTiming = document.getElementById("djDebugBrowserTiming"); + // Determine if the browser timing section has already been rendered. + if (browserTiming.classList.contains("djdt-hidden")) { + const tbody = document.getElementById("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"); + addRow(tbody, "requestStart", "responseEnd"); // There is no requestEnd + addRow(tbody, "responseStart", "responseEnd"); + addRow(tbody, "domLoading", "domComplete"); // Spans the events below + addRow(tbody, "domInteractive"); + addRow(tbody, "domContentLoadedEventStart", "domContentLoadedEventEnd"); + addRow(tbody, "loadEventStart", "loadEventEnd"); + browserTiming.classList.remove("djdt-hidden"); } - row.querySelector("rect").setAttribute("x", getLeft(stat)); - tbody.appendChild(row); } -const tbody = document.getElementById("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"); -addRow(tbody, "requestStart", "responseEnd"); // There is no requestEnd -addRow(tbody, "responseStart", "responseEnd"); -addRow(tbody, "domLoading", "domComplete"); // Spans the events below -addRow(tbody, "domInteractive"); -addRow(tbody, "domContentLoadedEventStart", "domContentLoadedEventEnd"); -addRow(tbody, "loadEventStart", "loadEventEnd"); -document.getElementById("djDebugBrowserTiming").classList.remove("djdt-hidden"); +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(); +$$.onPanelRender(djDebug, "TimerPanel", insertBrowserTiming); diff --git a/debug_toolbar/static/debug_toolbar/js/toolbar.js b/debug_toolbar/static/debug_toolbar/js/toolbar.js index d739cbdb3..579548d4e 100644 --- a/debug_toolbar/static/debug_toolbar/js/toolbar.js +++ b/debug_toolbar/static/debug_toolbar/js/toolbar.js @@ -20,7 +20,8 @@ const djdt = { if (!this.className) { return; } - const current = document.getElementById(this.className); + const panelId = this.className; + const current = document.getElementById(panelId); if ($$.visible(current)) { djdt.hide_panels(); } else { @@ -39,13 +40,24 @@ const djdt = { window.location ); url.searchParams.append("store_id", store_id); - url.searchParams.append("panel_id", this.className); + url.searchParams.append("panel_id", panelId); ajax(url).then(function (data) { inner.previousElementSibling.remove(); // Remove AJAX loader inner.innerHTML = data.content; $$.executeScripts(data.scripts); $$.applyStyles(inner); + djDebug.dispatchEvent( + new CustomEvent("djdt.panel.render", { + detail: { panelId: panelId }, + }) + ); }); + } else { + djDebug.dispatchEvent( + new CustomEvent("djdt.panel.render", { + detail: { panelId: panelId }, + }) + ); } } } diff --git a/debug_toolbar/static/debug_toolbar/js/utils.js b/debug_toolbar/static/debug_toolbar/js/utils.js index 4683b319f..da810aad0 100644 --- a/debug_toolbar/static/debug_toolbar/js/utils.js +++ b/debug_toolbar/static/debug_toolbar/js/utils.js @@ -7,6 +7,21 @@ const $$ = { } }); }, + onPanelRender(root, panelId, fn) { + /* + This is a helper function to attach a handler for a `djdt.panel.render` + event of a specific panel. + + root: The container element that the listener should be attached to. + panelId: The Id of the panel. + fn: A function to execute when the event is triggered. + */ + root.addEventListener("djdt.panel.render", function (event) { + if (event.detail.panelId === panelId) { + fn.call(event); + } + }); + }, show(element) { element.classList.remove("djdt-hidden"); }, diff --git a/docs/changes.rst b/docs/changes.rst index bef5d1964..b3da18d49 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -9,8 +9,13 @@ Next version * Added ``PRETTIFY_SQL`` configuration option to support controlling SQL token grouping. By default it's set to True. When set to False, a performance improvement can be seen by the SQL panel. -* Fixed issue with toolbar expecting URL paths to start with `/__debug__/` - while the documentation indicates it's not required. +* Added a JavaScript event when a panel loads of the format + ``djdt.panel.[PanelId]`` where PanelId is the ``panel_id`` property + of the panel's Python class. Listening for this event corrects the bug + in the Timer Panel in which it didn't insert the browser timings + after switching requests in the History Panel. +* Fixed issue with the toolbar expecting URL paths to start with + ``/__debug__/`` while the documentation indicates it's not required. 3.2 (2020-12-03) ---------------- diff --git a/docs/panels.rst b/docs/panels.rst index c21e90801..dfa5ec92a 100644 --- a/docs/panels.rst +++ b/docs/panels.rst @@ -184,9 +184,9 @@ URL: https://github.com/danyi1212/django-windowsauth Path: ``windows_auth.panels.LDAPPanel`` -LDAP Operations performed during the request, including timing, request and response messages, +LDAP Operations performed during the request, including timing, request and response messages, the entries received, write changes list, stack-tracing and error debugging. -This panel also shows connection usage metrics when it is collected. +This panel also shows connection usage metrics when it is collected. `Check out the docs `_. Line Profiler @@ -402,3 +402,32 @@ common methods available. .. js:function:: djdt.show_toolbar Shows the toolbar. + +Events +^^^^^^ + +.. js:attribute:: djdt.panel.render + + This is an event raised when a panel is rendered. It has the property + ``detail.panelId`` which identifies which panel has been loaded. This + event can be useful when creating custom scripts to process the HTML + further. + + An example of this for the ``CustomPanel`` would be: + +.. code-block:: javascript + + import { $$ } 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"); + $$.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 + // been raised. To account for that, the rendering function should be + // called here as well. + addCustomMetrics();