diff --git a/debug_toolbar/panels/history/panel.py b/debug_toolbar/panels/history/panel.py index 541c59136..1f7004e77 100644 --- a/debug_toolbar/panels/history/panel.py +++ b/debug_toolbar/panels/history/panel.py @@ -21,6 +21,18 @@ class HistoryPanel(Panel): nav_title = _("History") template = "debug_toolbar/panels/history.html" + def process_request(self, request): + response = super().process_request(request) + if not self.toolbar.should_render_panels(): + self.toolbar.store() + store_id = self.toolbar.store_id + sig = SignedDataForm( + initial=HistoryStoreForm(initial={"store_id": store_id}).initial + ).initial.get("signed") + response["DJ-TOOLBAR-STORE-ID"] = store_id + response["DJ-TOOLBAR-STORE-ID-SIGNATURE"] = sig + return response + @property def enabled(self): # Do not show the history panel if the panels are rendered on request diff --git a/debug_toolbar/panels/history/views.py b/debug_toolbar/panels/history/views.py index 10b4dcc1a..7488e1ae9 100644 --- a/debug_toolbar/panels/history/views.py +++ b/debug_toolbar/panels/history/views.py @@ -22,8 +22,6 @@ def history_sidebar(request, verified_data): # RESULTS_CACHE_SIZE return JsonResponse(context) for panel in toolbar.panels: - if not panel.is_historical: - continue panel_context = {"panel": panel} context[panel.panel_id] = { "button": render_to_string( diff --git a/debug_toolbar/settings.py b/debug_toolbar/settings.py index aac87e6ba..d329be60e 100644 --- a/debug_toolbar/settings.py +++ b/debug_toolbar/settings.py @@ -37,6 +37,7 @@ "SHOW_TEMPLATE_CONTEXT": True, "SKIP_TEMPLATE_PREFIXES": ("django/forms/widgets/", "admin/widgets/"), "SQL_WARNING_THRESHOLD": 500, # milliseconds + "UPDATE_ON_AJAX": False, } diff --git a/debug_toolbar/static/debug_toolbar/js/history.js b/debug_toolbar/static/debug_toolbar/js/history.js index cc14b2e4f..451e6c989 100644 --- a/debug_toolbar/static/debug_toolbar/js/history.js +++ b/debug_toolbar/static/debug_toolbar/js/history.js @@ -1,51 +1,82 @@ -import { $$, ajaxForm } from "./utils.js"; +import { $$, ajaxForm, pluckData, replaceToolbarState } from "./utils.js"; const djDebug = document.getElementById("djDebug"); -$$.on(djDebug, "click", ".switchHistory", function (event) { - event.preventDefault(); - const newStoreId = this.dataset.storeId; - const tbody = this.closest("tbody"); +function difference(setA, setB) { + const _difference = new Set(setA); + for (const elem of setB) { + _difference.delete(elem); + } + return _difference; +} + +function switchHistory(newStoreId) { + const formTarget = djDebug.querySelector( + ".switchHistory[data-store-id='" + newStoreId + "']" + ); + const tbody = formTarget.closest("tbody"); const highlighted = tbody.querySelector(".djdt-highlighted"); if (highlighted) { highlighted.classList.remove("djdt-highlighted"); } - this.closest("tr").classList.add("djdt-highlighted"); + formTarget.closest("tr").classList.add("djdt-highlighted"); - ajaxForm(this).then(function (data) { - djDebug.setAttribute("data-store-id", newStoreId); - // Check if response is empty, it could be due to an expired store_id. + ajaxForm(formTarget).then(function (data) { if (Object.keys(data).length === 0) { const container = document.getElementById("djdtHistoryRequests"); container.querySelector( 'button[data-store-id="' + newStoreId + '"]' ).innerHTML = "Switch [EXPIRED]"; - } else { - Object.keys(data).forEach(function (panelId) { - const panel = document.getElementById(panelId); - if (panel) { - panel.outerHTML = data[panelId].content; - document.getElementById("djdt-" + panelId).outerHTML = - data[panelId].button; - } - }); } + //we're already in history panel, so handle locally vs replacing active html + delete data.HistoryPanel; + replaceToolbarState(newStoreId, data); }); -}); +} -$$.on(djDebug, "click", ".refreshHistory", function (event) { - event.preventDefault(); +function refreshHistory() { + const formTarget = djDebug.querySelector(".refreshHistory"); const container = document.getElementById("djdtHistoryRequests"); - ajaxForm(this).then(function (data) { - // Remove existing rows first then re-populate with new data - container - .querySelectorAll("tr[data-store-id]") - .forEach(function (node) { - node.remove(); + const oldIds = new Set( + pluckData(container.querySelectorAll("tr[data-store-id]"), "storeId") + ); + + return ajaxForm(formTarget) + .then(function (data) { + // Remove existing rows first then re-populate with new data + container + .querySelectorAll("tr[data-store-id]") + .forEach(function (node) { + node.remove(); + }); + data.requests.forEach(function (request) { + container.innerHTML = request.content + container.innerHTML; }); - data.requests.forEach(function (request) { - container.innerHTML = request.content + container.innerHTML; + }) + .then(function () { + const allIds = new Set( + pluckData( + container.querySelectorAll("tr[data-store-id]"), + "storeId" + ) + ); + const newIds = difference(allIds, oldIds); + const lastRequestId = newIds.values().next().value; + return { + allIds, + newIds, + lastRequestId, + }; }); - }); +} + +$$.on(djDebug, "click", ".switchHistory", function (event) { + event.preventDefault(); + switchHistory(this.dataset.storeId); +}); + +$$.on(djDebug, "click", ".refreshHistory", function (event) { + event.preventDefault(); + refreshHistory(); }); diff --git a/debug_toolbar/static/debug_toolbar/js/toolbar.js b/debug_toolbar/static/debug_toolbar/js/toolbar.js index c17ee3ea2..613f79b7f 100644 --- a/debug_toolbar/static/debug_toolbar/js/toolbar.js +++ b/debug_toolbar/static/debug_toolbar/js/toolbar.js @@ -1,4 +1,10 @@ -import { $$, ajax } from "./utils.js"; +import { + $$, + ajax, + controller, + resetAbortController, + replaceToolbarState, +} from "./utils.js"; function onKeyDown(event) { if (event.keyCode === 27) { @@ -8,6 +14,10 @@ function onKeyDown(event) { const djdt = { handleDragged: false, + abort() { + controller.abort(); + resetAbortController(); + }, init() { const djDebug = document.getElementById("djDebug"); $$.show(djDebug); @@ -142,19 +152,23 @@ const djdt = { }); }); - document - .getElementById("djHideToolBarButton") - .addEventListener("click", function (event) { + document.getElementById("djHideToolBarButton").addEventListener( + "click", + function (event) { event.preventDefault(); djdt.hide_toolbar(); - }); - document - .getElementById("djShowToolBarButton") - .addEventListener("click", function () { + }, + { signal: controller.signal } + ); + document.getElementById("djShowToolBarButton").addEventListener( + "click", + function () { if (!djdt.handleDragged) { djdt.show_toolbar(); } - }); + }, + { signal: controller.signal } + ); let startPageY, baseY; const handle = document.getElementById("djDebugToolbarHandle"); function onHandleMove(event) { @@ -174,25 +188,31 @@ const djdt = { djdt.handleDragged = true; } } - document - .getElementById("djShowToolBarButton") - .addEventListener("mousedown", function (event) { + document.getElementById("djShowToolBarButton").addEventListener( + "mousedown", + function (event) { event.preventDefault(); startPageY = event.pageY; baseY = handle.offsetTop - startPageY; document.addEventListener("mousemove", onHandleMove); - }); - document.addEventListener("mouseup", function (event) { - document.removeEventListener("mousemove", onHandleMove); - if (djdt.handleDragged) { - event.preventDefault(); - localStorage.setItem("djdt.top", handle.offsetTop); - requestAnimationFrame(function () { - djdt.handleDragged = false; - }); - djdt.ensure_handle_visibility(); - } - }); + }, + { signal: controller.signal } + ); + document.addEventListener( + "mouseup", + function (event) { + document.removeEventListener("mousemove", onHandleMove); + if (djdt.handleDragged) { + event.preventDefault(); + localStorage.setItem("djdt.top", handle.offsetTop); + requestAnimationFrame(function () { + djdt.handleDragged = false; + }); + djdt.ensure_handle_visibility(); + } + }, + { signal: controller.signal } + ); const show = localStorage.getItem("djdt.show") || djDebug.dataset.defaultShow; if (show === "true") { @@ -200,6 +220,9 @@ const djdt = { } else { djdt.hide_toolbar(); } + if (djDebug.dataset.sidebarUrl !== undefined) { + djdt.update_on_ajax(); + } }, hide_panels() { const djDebug = document.getElementById("djDebug"); @@ -228,7 +251,9 @@ const djdt = { const handle = document.getElementById("djDebugToolbarHandle"); $$.show(handle); djdt.ensure_handle_visibility(); - window.addEventListener("resize", djdt.ensure_handle_visibility); + window.addEventListener("resize", djdt.ensure_handle_visibility, { + signal: controller.signal, + }); document.removeEventListener("keydown", onKeyDown); localStorage.setItem("djdt.show", "false"); @@ -247,12 +272,43 @@ const djdt = { } }, show_toolbar() { - document.addEventListener("keydown", onKeyDown); + document.addEventListener("keydown", onKeyDown, { + signal: controller.signal, + }); $$.hide(document.getElementById("djDebugToolbarHandle")); $$.show(document.getElementById("djDebugToolbar")); localStorage.setItem("djdt.show", "true"); window.removeEventListener("resize", djdt.ensure_handle_visibility); }, + update_on_ajax() { + const sidebar_url = + document.getElementById("djDebug").dataset.sidebarUrl; + + const origOpen = XMLHttpRequest.prototype.open; + XMLHttpRequest.prototype.open = function () { + this.addEventListener("load", function () { + if ( + this.responseURL !== "" && + this.responseURL.indexOf("__debug__") === -1 + ) { + let signed = this.getResponseHeader( + "dj-toolbar-store-id-signature" + ); + const store_id = this.getResponseHeader( + "dj-toolbar-store-id" + ); + if (signed !== null) { + signed = encodeURIComponent(signed); + const dest = `${sidebar_url}?signed=${signed}`; + ajax(dest).then(function (data) { + replaceToolbarState(store_id, data); + }); + } + } + }); + origOpen.apply(this, arguments); + }; + }, cookie: { get(key) { if (!document.cookie.includes(key)) { @@ -295,10 +351,12 @@ const djdt = { }, }, }; + window.djdt = { show_toolbar: djdt.show_toolbar, hide_toolbar: djdt.hide_toolbar, init: djdt.init, + abort: djdt.abort, close: djdt.hide_one_level, cookie: djdt.cookie, }; @@ -306,5 +364,7 @@ window.djdt = { if (document.readyState !== "loading") { djdt.init(); } else { - document.addEventListener("DOMContentLoaded", djdt.init); + document.addEventListener("DOMContentLoaded", djdt.init, { + signal: controller.signal, + }); } diff --git a/debug_toolbar/static/debug_toolbar/js/utils.js b/debug_toolbar/static/debug_toolbar/js/utils.js index da810aad0..d334a2c21 100644 --- a/debug_toolbar/static/debug_toolbar/js/utils.js +++ b/debug_toolbar/static/debug_toolbar/js/utils.js @@ -1,11 +1,17 @@ +let controller = null; + const $$ = { on(root, eventName, selector, fn) { - root.addEventListener(eventName, function (event) { - const target = event.target.closest(selector); - if (root.contains(target)) { - fn.call(target, event); - } - }); + root.addEventListener( + eventName, + function (event) { + const target = event.target.closest(selector); + if (root.contains(target)) { + fn.call(target, event); + } + }, + { signal: controller.signal } + ); }, onPanelRender(root, panelId, fn) { /* @@ -16,11 +22,15 @@ const $$ = { 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); - } - }); + root.addEventListener( + "djdt.panel.render", + function (event) { + if (event.detail.panelId === panelId) { + fn.call(event); + } + }, + { signal: controller.signal } + ); }, show(element) { element.classList.remove("djdt-hidden"); @@ -69,6 +79,11 @@ const $$ = { }, }; +function resetAbortController() { + controller = new AbortController(); +} +resetAbortController(); + function ajax(url, init) { init = Object.assign({ credentials: "same-origin" }, init); return fetch(url, init) @@ -91,6 +106,14 @@ function ajax(url, init) { }); } +function pluckData(array, key) { + const data = []; + array.forEach(function (obj) { + data.push(obj.dataset[key]); + }); + return data; +} + function ajaxForm(element) { const form = element.closest("form"); const url = new URL(form.action); @@ -104,4 +127,26 @@ function ajaxForm(element) { return ajax(url, ajaxData); } -export { $$, ajax, ajaxForm }; +function replaceToolbarState(newStoreId, data) { + const djDebug = document.getElementById("djDebug"); + djDebug.setAttribute("data-store-id", newStoreId); + // Check if response is empty, it could be due to an expired store_id. + Object.keys(data).forEach(function (panelId) { + const panel = document.getElementById(panelId); + if (panel) { + panel.outerHTML = data[panelId].content; + document.getElementById("djdt-" + panelId).outerHTML = + data[panelId].button; + } + }); +} + +export { + $$, + ajax, + ajaxForm, + controller, + resetAbortController, + pluckData, + replaceToolbarState, +}; diff --git a/debug_toolbar/templates/debug_toolbar/base.html b/debug_toolbar/templates/debug_toolbar/base.html index 7abc5476f..80403c1fd 100644 --- a/debug_toolbar/templates/debug_toolbar/base.html +++ b/debug_toolbar/templates/debug_toolbar/base.html @@ -1,16 +1,23 @@ {% load i18n static %} {% block css %} - - + {% if toolbar.should_render_css %} + + + {% endif %} {% endblock %} {% block js %} - + {% if toolbar.should_render_js %} + + {% endif %} {% endblock %}