From 2e310bf24d139c9c35ef33ed091b533b6a1b5a75 Mon Sep 17 00:00:00 2001 From: Ben Beecher Date: Tue, 28 Dec 2021 15:52:10 -0500 Subject: [PATCH 01/12] adding abort signal to turn off the js --- .../static/debug_toolbar/js/toolbar.js | 21 ++++++++++++------- .../static/debug_toolbar/js/utils.js | 13 +++++++++--- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/debug_toolbar/static/debug_toolbar/js/toolbar.js b/debug_toolbar/static/debug_toolbar/js/toolbar.js index c17ee3ea2..b7a0050ea 100644 --- a/debug_toolbar/static/debug_toolbar/js/toolbar.js +++ b/debug_toolbar/static/debug_toolbar/js/toolbar.js @@ -1,4 +1,4 @@ -import { $$, ajax } from "./utils.js"; +import { $$, ajax, controller, resetAbortController } from "./utils.js"; function onKeyDown(event) { if (event.keyCode === 27) { @@ -8,6 +8,10 @@ function onKeyDown(event) { const djdt = { handleDragged: false, + abort(){ + controller.abort(); + resetAbortController(); + }, init() { const djDebug = document.getElementById("djDebug"); $$.show(djDebug); @@ -147,14 +151,14 @@ const djdt = { .addEventListener("click", function (event) { event.preventDefault(); djdt.hide_toolbar(); - }); + }, {'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) { @@ -181,7 +185,7 @@ const djdt = { startPageY = event.pageY; baseY = handle.offsetTop - startPageY; document.addEventListener("mousemove", onHandleMove); - }); + }, {'signal': controller.signal}); document.addEventListener("mouseup", function (event) { document.removeEventListener("mousemove", onHandleMove); if (djdt.handleDragged) { @@ -192,7 +196,7 @@ const djdt = { }); djdt.ensure_handle_visibility(); } - }); + }, {'signal': controller.signal}); const show = localStorage.getItem("djdt.show") || djDebug.dataset.defaultShow; if (show === "true") { @@ -228,7 +232,7 @@ 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,7 +251,7 @@ 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"); @@ -299,6 +303,7 @@ 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 +311,5 @@ 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..66dad284c 100644 --- a/debug_toolbar/static/debug_toolbar/js/utils.js +++ b/debug_toolbar/static/debug_toolbar/js/utils.js @@ -1,3 +1,5 @@ +let controller = null; + const $$ = { on(root, eventName, selector, fn) { root.addEventListener(eventName, function (event) { @@ -5,7 +7,7 @@ const $$ = { if (root.contains(target)) { fn.call(target, event); } - }); + }, {'signal': controller.signal}); }, onPanelRender(root, panelId, fn) { /* @@ -20,7 +22,7 @@ const $$ = { if (event.detail.panelId === panelId) { fn.call(event); } - }); + }, {'signal': controller.signal}); }, show(element) { element.classList.remove("djdt-hidden"); @@ -69,6 +71,11 @@ const $$ = { }, }; +function resetAbortController(){ + controller = new AbortController(); +}; +resetAbortController(); + function ajax(url, init) { init = Object.assign({ credentials: "same-origin" }, init); return fetch(url, init) @@ -104,4 +111,4 @@ function ajaxForm(element) { return ajax(url, ajaxData); } -export { $$, ajax, ajaxForm }; +export { $$, ajax, ajaxForm, controller, resetAbortController }; From 3cb6f714d6502a74e8832d3992b7a444aaeb0d79 Mon Sep 17 00:00:00 2001 From: Ben Beecher Date: Tue, 28 Dec 2021 16:01:19 -0500 Subject: [PATCH 02/12] moving decision to insert into function to allow overriding --- debug_toolbar/middleware.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/debug_toolbar/middleware.py b/debug_toolbar/middleware.py index df4516b4f..b057d6b84 100644 --- a/debug_toolbar/middleware.py +++ b/debug_toolbar/middleware.py @@ -83,6 +83,14 @@ def __call__(self, request): ): return response + response, did_insert = self.insert_toolbar(request, response, rendered) + if did_insert: + if "Content-Length" in response: + response["Content-Length"] = len(response.content) + return response + + @staticmethod + def insert_toolbar(request, response, rendered): # Insert the toolbar in the response. content = response.content.decode(response.charset) insert_before = dt_settings.get_config()["INSERT_BEFORE"] @@ -91,9 +99,8 @@ def __call__(self, request): if len(bits) > 1: bits[-2] += rendered response.content = insert_before.join(bits) - if "Content-Length" in response: - response["Content-Length"] = len(response.content) - return response + return response, True + return response, False @staticmethod def generate_server_timing_header(response, panels): From 97330eedc6615bc9f43831998e1316540b6c9c54 Mon Sep 17 00:00:00 2001 From: Ben Beecher Date: Tue, 28 Dec 2021 16:27:28 -0500 Subject: [PATCH 03/12] adding slot to dynamically configure toolbar --- debug_toolbar/middleware.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/debug_toolbar/middleware.py b/debug_toolbar/middleware.py index b057d6b84..205b19cff 100644 --- a/debug_toolbar/middleware.py +++ b/debug_toolbar/middleware.py @@ -49,6 +49,8 @@ def __call__(self, request): toolbar = DebugToolbar(request, self.get_response) + self.configure_toolbar(request, toolbar) + # Activate instrumentation ie. monkey-patch. for panel in toolbar.enabled_panels: panel.enable_instrumentation() @@ -89,6 +91,9 @@ def __call__(self, request): response["Content-Length"] = len(response.content) return response + def configure_toolbar(self, request, toolbar): + pass + @staticmethod def insert_toolbar(request, response, rendered): # Insert the toolbar in the response. From 4068382ebb10e4386f0f24ea023196b45819a633 Mon Sep 17 00:00:00 2001 From: Ben Beecher Date: Tue, 28 Dec 2021 17:50:47 -0500 Subject: [PATCH 04/12] Optionally render js/css --- debug_toolbar/templates/debug_toolbar/base.html | 10 +++++++--- debug_toolbar/toolbar.py | 1 + tests/test_integration.py | 16 ++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/debug_toolbar/templates/debug_toolbar/base.html b/debug_toolbar/templates/debug_toolbar/base.html index 7abc5476f..d8b8c6135 100644 --- a/debug_toolbar/templates/debug_toolbar/base.html +++ b/debug_toolbar/templates/debug_toolbar/base.html @@ -1,10 +1,14 @@ {% load i18n static %} {% block css %} - - + {% if toolbar.should_render_css %} + + + {% endif %} {% endblock %} {% block js %} - + {% if toolbar.should_render_js %} + + {% endif %} {% endblock %}
") + def test_optional_js_css(self): + def get_response(request): + return regular_view(request, "tuturu") + + class NoStaticMiddleware(DebugToolbarMiddleware): + def configure_toolbar(self, request, toolbar): + toolbar.should_render_js = toolbar.should_render_css = False + + response = DebugToolbarMiddleware(get_response)(self.request) + self.assertContains(response, b" Date: Tue, 28 Dec 2021 17:51:21 -0500 Subject: [PATCH 05/12] Dynamic values for root_tag_extra_attrs --- .../templates/debug_toolbar/base.html | 2 +- debug_toolbar/toolbar.py | 1 + tests/test_integration.py | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/debug_toolbar/templates/debug_toolbar/base.html b/debug_toolbar/templates/debug_toolbar/base.html index d8b8c6135..7efa92a9a 100644 --- a/debug_toolbar/templates/debug_toolbar/base.html +++ b/debug_toolbar/templates/debug_toolbar/base.html @@ -16,7 +16,7 @@ data-render-panel-url="{% url 'djdt:render_panel' %}" {% endif %} data-default-show="{% if toolbar.config.SHOW_COLLAPSED %}false{% else %}true{% endif %}" - {{ toolbar.config.ROOT_TAG_EXTRA_ATTRS|safe }}> + {{ toolbar.root_tag_extra_attrs|safe }}>
  • {% trans "Hide" %} »
  • diff --git a/debug_toolbar/toolbar.py b/debug_toolbar/toolbar.py index 40aed8339..9037d6b86 100644 --- a/debug_toolbar/toolbar.py +++ b/debug_toolbar/toolbar.py @@ -34,6 +34,7 @@ def __init__(self, request, get_response): self.stats = {} self.server_timing_stats = {} self.store_id = None + self.root_tag_extra_attrs = self.config["ROOT_TAG_EXTRA_ATTRS"] self.should_render_css = self.should_render_js = True # Manage panels diff --git a/tests/test_integration.py b/tests/test_integration.py index a02d2ff19..c06590067 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -121,6 +121,25 @@ def configure_toolbar(self, request, toolbar): self.assertNotContains(response, b" Date: Fri, 14 Jan 2022 17:46:11 -0500 Subject: [PATCH 06/12] allowing history refresh and switch to be scripted --- .../static/debug_toolbar/js/history.js | 80 ++++++++++++++----- .../static/debug_toolbar/js/utils.js | 10 ++- 2 files changed, 70 insertions(+), 20 deletions(-) diff --git a/debug_toolbar/static/debug_toolbar/js/history.js b/debug_toolbar/static/debug_toolbar/js/history.js index cc14b2e4f..846fdc841 100644 --- a/debug_toolbar/static/debug_toolbar/js/history.js +++ b/debug_toolbar/static/debug_toolbar/js/history.js @@ -1,19 +1,28 @@ -import { $$, ajaxForm } from "./utils.js"; +import { $$, ajaxForm, pluckData } 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) { + ajaxForm(formTarget).then(function (data) { djDebug.setAttribute("data-store-id", newStoreId); // Check if response is empty, it could be due to an expired store_id. if (Object.keys(data).length === 0) { @@ -32,20 +41,53 @@ $$.on(djDebug, "click", ".switchHistory", function (event) { }); } }); -}); +} -$$.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(); }); + +window.djdt.refreshHistory = refreshHistory; +window.djdt.switchHistory = switchHistory; diff --git a/debug_toolbar/static/debug_toolbar/js/utils.js b/debug_toolbar/static/debug_toolbar/js/utils.js index 66dad284c..7c5a125bd 100644 --- a/debug_toolbar/static/debug_toolbar/js/utils.js +++ b/debug_toolbar/static/debug_toolbar/js/utils.js @@ -98,6 +98,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); @@ -111,4 +119,4 @@ function ajaxForm(element) { return ajax(url, ajaxData); } -export { $$, ajax, ajaxForm, controller, resetAbortController }; +export { $$, ajax, ajaxForm, controller, resetAbortController, pluckData }; From a931fed53ef611242ced6a425350ae4e21ac5e2b Mon Sep 17 00:00:00 2001 From: Ben Beecher Date: Sat, 15 Jan 2022 18:14:34 -0500 Subject: [PATCH 07/12] Work in preperation of an autorefresh adding view that will return the toolbar for a given store id Adding header containing toolbar url Refactoring history javascript to allow switching state by store id --- debug_toolbar/panels/history/panel.py | 11 +++++- .../static/debug_toolbar/js/history.js | 38 ++++++++++--------- debug_toolbar/toolbar.py | 3 +- debug_toolbar/views.py | 16 +++++++- 4 files changed, 48 insertions(+), 20 deletions(-) diff --git a/debug_toolbar/panels/history/panel.py b/debug_toolbar/panels/history/panel.py index 541c59136..0c8b65bb5 100644 --- a/debug_toolbar/panels/history/panel.py +++ b/debug_toolbar/panels/history/panel.py @@ -4,7 +4,7 @@ from django.http.request import RawPostDataException from django.template.loader import render_to_string from django.templatetags.static import static -from django.urls import path +from django.urls import path, reverse from django.utils import timezone from django.utils.translation import gettext_lazy as _ @@ -21,6 +21,15 @@ 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() + response["DJ-TOOLBAR-URL"] = ( + reverse("djdt:render_base") + f"?store_id={self.toolbar.store_id}" + ) + return response + @property def enabled(self): # Do not show the history panel if the panels are rendered on request diff --git a/debug_toolbar/static/debug_toolbar/js/history.js b/debug_toolbar/static/debug_toolbar/js/history.js index 846fdc841..b624e75b8 100644 --- a/debug_toolbar/static/debug_toolbar/js/history.js +++ b/debug_toolbar/static/debug_toolbar/js/history.js @@ -10,6 +10,26 @@ function difference(setA, setB) { return _difference; } +function replaceToolbarState(newStoreId, data) { + djDebug.setAttribute("data-store-id", newStoreId); + // Check if response is empty, it could be due to an expired store_id. + 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; + } + }); + } +} + function switchHistory(newStoreId) { const formTarget = djDebug.querySelector( ".switchHistory[data-store-id='" + newStoreId + "']" @@ -23,23 +43,7 @@ function switchHistory(newStoreId) { formTarget.closest("tr").classList.add("djdt-highlighted"); ajaxForm(formTarget).then(function (data) { - djDebug.setAttribute("data-store-id", newStoreId); - // Check if response is empty, it could be due to an expired store_id. - 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; - } - }); - } + replaceToolbarState(newStoreId, data); }); } diff --git a/debug_toolbar/toolbar.py b/debug_toolbar/toolbar.py index 9037d6b86..9e5f66f47 100644 --- a/debug_toolbar/toolbar.py +++ b/debug_toolbar/toolbar.py @@ -132,7 +132,8 @@ def get_urls(cls): # Load URLs in a temporary variable for thread safety. # Global URLs urlpatterns = [ - path("render_panel/", views.render_panel, name="render_panel") + path("render_panel/", views.render_panel, name="render_panel"), + path("render_base/", views.render_base, name="render_base"), ] # Per-panel URLs for panel_class in cls.get_panel_classes(): diff --git a/debug_toolbar/views.py b/debug_toolbar/views.py index 1d319027d..8838bbf67 100644 --- a/debug_toolbar/views.py +++ b/debug_toolbar/views.py @@ -1,4 +1,4 @@ -from django.http import JsonResponse +from django.http import HttpResponse, JsonResponse from django.utils.html import escape from django.utils.translation import gettext as _ @@ -22,3 +22,17 @@ def render_panel(request): content = panel.content scripts = panel.scripts return JsonResponse({"content": content, "scripts": scripts}) + + +@require_show_toolbar +def render_base(request): + """Render the contents of a panel""" + toolbar = DebugToolbar.fetch(request.GET["store_id"]) + if toolbar is None: + content = _( + "Data for this toolbar isn't available anymore. " + "Please reload the page and retry." + ) + content = "

    %s

    " % escape(content) + content = toolbar.render_toolbar() + return HttpResponse(content) From 32806b8cb20806f31537ef38b058ba2cc506be69 Mon Sep 17 00:00:00 2001 From: Ben Beecher Date: Sun, 16 Jan 2022 11:34:19 -0500 Subject: [PATCH 08/12] Adding initial autorefresh mode --- debug_toolbar/panels/history/panel.py | 11 +- debug_toolbar/panels/history/views.py | 2 - .../static/debug_toolbar/js/history.js | 28 ++--- .../static/debug_toolbar/js/toolbar.js | 103 +++++++++++++----- .../static/debug_toolbar/js/utils.js | 58 +++++++--- 5 files changed, 135 insertions(+), 67 deletions(-) diff --git a/debug_toolbar/panels/history/panel.py b/debug_toolbar/panels/history/panel.py index 0c8b65bb5..d43290d74 100644 --- a/debug_toolbar/panels/history/panel.py +++ b/debug_toolbar/panels/history/panel.py @@ -25,9 +25,16 @@ def process_request(self, request): response = super().process_request(request) if not self.toolbar.should_render_panels(): self.toolbar.store() - response["DJ-TOOLBAR-URL"] = ( - reverse("djdt:render_base") + f"?store_id={self.toolbar.store_id}" + store_id = self.toolbar.store_id + response["DJ-TOOLBAR-BASE-URL"] = ( + reverse("djdt:render_base") + f"?store_id={store_id}" ) + sig = SignedDataForm( + initial=HistoryStoreForm(initial={"store_id": store_id}).initial + ).initial.get("signed") + response["dj-history-sidebar-url"] = reverse("djdt:history_sidebar") + response["DJ-TOOLBAR-STORE-ID"] = store_id + response["DJ-TOOLBAR-SIGNATURE"] = sig return response @property 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/static/debug_toolbar/js/history.js b/debug_toolbar/static/debug_toolbar/js/history.js index b624e75b8..3e19e3aea 100644 --- a/debug_toolbar/static/debug_toolbar/js/history.js +++ b/debug_toolbar/static/debug_toolbar/js/history.js @@ -1,4 +1,4 @@ -import { $$, ajaxForm, pluckData } from "./utils.js"; +import { $$, ajaxForm, pluckData, replaceToolbarState } from "./utils.js"; const djDebug = document.getElementById("djDebug"); @@ -10,26 +10,6 @@ function difference(setA, setB) { return _difference; } -function replaceToolbarState(newStoreId, data) { - djDebug.setAttribute("data-store-id", newStoreId); - // Check if response is empty, it could be due to an expired store_id. - 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; - } - }); - } -} - function switchHistory(newStoreId) { const formTarget = djDebug.querySelector( ".switchHistory[data-store-id='" + newStoreId + "']" @@ -43,6 +23,12 @@ function switchHistory(newStoreId) { formTarget.closest("tr").classList.add("djdt-highlighted"); 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]"; + } replaceToolbarState(newStoreId, data); }); } diff --git a/debug_toolbar/static/debug_toolbar/js/toolbar.js b/debug_toolbar/static/debug_toolbar/js/toolbar.js index b7a0050ea..912ad93ef 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, controller, resetAbortController } from "./utils.js"; +import { + $$, + ajax, + controller, + resetAbortController, + replaceToolbarState, +} from "./utils.js"; function onKeyDown(event) { if (event.keyCode === 27) { @@ -8,7 +14,7 @@ function onKeyDown(event) { const djdt = { handleDragged: false, - abort(){ + abort() { controller.abort(); resetAbortController(); }, @@ -146,19 +152,23 @@ const djdt = { }); }); - document - .getElementById("djHideToolBarButton") - .addEventListener("click", function (event) { + document.getElementById("djHideToolBarButton").addEventListener( + "click", + function (event) { event.preventDefault(); djdt.hide_toolbar(); - }, {'signal': controller.signal}); - document - .getElementById("djShowToolBarButton") - .addEventListener("click", function () { + }, + { signal: controller.signal } + ); + document.getElementById("djShowToolBarButton").addEventListener( + "click", + function () { if (!djdt.handleDragged) { djdt.show_toolbar(); } - }, {'signal': controller.signal}); + }, + { signal: controller.signal } + ); let startPageY, baseY; const handle = document.getElementById("djDebugToolbarHandle"); function onHandleMove(event) { @@ -178,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); - }, {'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}); + }, + { 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") { @@ -232,7 +248,9 @@ const djdt = { const handle = document.getElementById("djDebugToolbarHandle"); $$.show(handle); djdt.ensure_handle_visibility(); - window.addEventListener("resize", djdt.ensure_handle_visibility, {'signal': controller.signal}); + window.addEventListener("resize", djdt.ensure_handle_visibility, { + signal: controller.signal, + }); document.removeEventListener("keydown", onKeyDown); localStorage.setItem("djdt.show", "false"); @@ -251,7 +269,9 @@ const djdt = { } }, show_toolbar() { - document.addEventListener("keydown", onKeyDown, {'signal': controller.signal}); + document.addEventListener("keydown", onKeyDown, { + signal: controller.signal, + }); $$.hide(document.getElementById("djDebugToolbarHandle")); $$.show(document.getElementById("djDebugToolbar")); localStorage.setItem("djdt.show", "true"); @@ -299,6 +319,31 @@ const djdt = { }, }, }; + +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-signature"); + const store_id = this.getResponseHeader("dj-toolbar-store-id"); + const history_sidebar_url = this.getResponseHeader( + "dj-history-sidebar-url" + ); + if (signed !== null) { + signed = encodeURIComponent(signed); + const dest = `${history_sidebar_url}?signed=${signed}`; + ajax(dest).then(function (data) { + replaceToolbarState(store_id, data); + }); + } + } + }); + origOpen.apply(this, arguments); +}; + window.djdt = { show_toolbar: djdt.show_toolbar, hide_toolbar: djdt.hide_toolbar, @@ -311,5 +356,7 @@ window.djdt = { if (document.readyState !== "loading") { djdt.init(); } else { - document.addEventListener("DOMContentLoaded", djdt.init, {'signal': controller.signal}); + 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 7c5a125bd..d334a2c21 100644 --- a/debug_toolbar/static/debug_toolbar/js/utils.js +++ b/debug_toolbar/static/debug_toolbar/js/utils.js @@ -2,12 +2,16 @@ 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); - } - }, {'signal': controller.signal}); + 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) { /* @@ -18,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); - } - }, {'signal': controller.signal}); + 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"); @@ -71,9 +79,9 @@ const $$ = { }, }; -function resetAbortController(){ +function resetAbortController() { controller = new AbortController(); -}; +} resetAbortController(); function ajax(url, init) { @@ -119,4 +127,26 @@ function ajaxForm(element) { return ajax(url, ajaxData); } -export { $$, ajax, ajaxForm, controller, resetAbortController, pluckData }; +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, +}; From a47ae06afecd909684cf4933c5494011306a22c3 Mon Sep 17 00:00:00 2001 From: Ben Beecher Date: Sun, 16 Jan 2022 16:30:48 -0500 Subject: [PATCH 09/12] removing some features we don't need --- debug_toolbar/middleware.py | 18 +++--------------- .../templates/debug_toolbar/base.html | 2 +- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/debug_toolbar/middleware.py b/debug_toolbar/middleware.py index 205b19cff..df4516b4f 100644 --- a/debug_toolbar/middleware.py +++ b/debug_toolbar/middleware.py @@ -49,8 +49,6 @@ def __call__(self, request): toolbar = DebugToolbar(request, self.get_response) - self.configure_toolbar(request, toolbar) - # Activate instrumentation ie. monkey-patch. for panel in toolbar.enabled_panels: panel.enable_instrumentation() @@ -85,17 +83,6 @@ def __call__(self, request): ): return response - response, did_insert = self.insert_toolbar(request, response, rendered) - if did_insert: - if "Content-Length" in response: - response["Content-Length"] = len(response.content) - return response - - def configure_toolbar(self, request, toolbar): - pass - - @staticmethod - def insert_toolbar(request, response, rendered): # Insert the toolbar in the response. content = response.content.decode(response.charset) insert_before = dt_settings.get_config()["INSERT_BEFORE"] @@ -104,8 +91,9 @@ def insert_toolbar(request, response, rendered): if len(bits) > 1: bits[-2] += rendered response.content = insert_before.join(bits) - return response, True - return response, False + if "Content-Length" in response: + response["Content-Length"] = len(response.content) + return response @staticmethod def generate_server_timing_header(response, panels): diff --git a/debug_toolbar/templates/debug_toolbar/base.html b/debug_toolbar/templates/debug_toolbar/base.html index 7efa92a9a..d8b8c6135 100644 --- a/debug_toolbar/templates/debug_toolbar/base.html +++ b/debug_toolbar/templates/debug_toolbar/base.html @@ -16,7 +16,7 @@ data-render-panel-url="{% url 'djdt:render_panel' %}" {% endif %} data-default-show="{% if toolbar.config.SHOW_COLLAPSED %}false{% else %}true{% endif %}" - {{ toolbar.root_tag_extra_attrs|safe }}> + {{ toolbar.config.ROOT_TAG_EXTRA_ATTRS|safe }}>
    • {% trans "Hide" %} »
    • From 74689064003d082205bc72e9e0769f0632fcd44a Mon Sep 17 00:00:00 2001 From: Ben Beecher Date: Sun, 16 Jan 2022 16:40:03 -0500 Subject: [PATCH 10/12] removing render_base since we're using json not html --- debug_toolbar/panels/history/panel.py | 3 --- debug_toolbar/toolbar.py | 2 -- debug_toolbar/views.py | 16 +--------------- 3 files changed, 1 insertion(+), 20 deletions(-) diff --git a/debug_toolbar/panels/history/panel.py b/debug_toolbar/panels/history/panel.py index d43290d74..24ec432b7 100644 --- a/debug_toolbar/panels/history/panel.py +++ b/debug_toolbar/panels/history/panel.py @@ -26,9 +26,6 @@ def process_request(self, request): if not self.toolbar.should_render_panels(): self.toolbar.store() store_id = self.toolbar.store_id - response["DJ-TOOLBAR-BASE-URL"] = ( - reverse("djdt:render_base") + f"?store_id={store_id}" - ) sig = SignedDataForm( initial=HistoryStoreForm(initial={"store_id": store_id}).initial ).initial.get("signed") diff --git a/debug_toolbar/toolbar.py b/debug_toolbar/toolbar.py index 9e5f66f47..364bba9f2 100644 --- a/debug_toolbar/toolbar.py +++ b/debug_toolbar/toolbar.py @@ -34,7 +34,6 @@ def __init__(self, request, get_response): self.stats = {} self.server_timing_stats = {} self.store_id = None - self.root_tag_extra_attrs = self.config["ROOT_TAG_EXTRA_ATTRS"] self.should_render_css = self.should_render_js = True # Manage panels @@ -133,7 +132,6 @@ def get_urls(cls): # Global URLs urlpatterns = [ path("render_panel/", views.render_panel, name="render_panel"), - path("render_base/", views.render_base, name="render_base"), ] # Per-panel URLs for panel_class in cls.get_panel_classes(): diff --git a/debug_toolbar/views.py b/debug_toolbar/views.py index 8838bbf67..1d319027d 100644 --- a/debug_toolbar/views.py +++ b/debug_toolbar/views.py @@ -1,4 +1,4 @@ -from django.http import HttpResponse, JsonResponse +from django.http import JsonResponse from django.utils.html import escape from django.utils.translation import gettext as _ @@ -22,17 +22,3 @@ def render_panel(request): content = panel.content scripts = panel.scripts return JsonResponse({"content": content, "scripts": scripts}) - - -@require_show_toolbar -def render_base(request): - """Render the contents of a panel""" - toolbar = DebugToolbar.fetch(request.GET["store_id"]) - if toolbar is None: - content = _( - "Data for this toolbar isn't available anymore. " - "Please reload the page and retry." - ) - content = "

      %s

      " % escape(content) - content = toolbar.render_toolbar() - return HttpResponse(content) From 359bb0d96720d063c03b3f696349a91fa827bec9 Mon Sep 17 00:00:00 2001 From: Ben Beecher Date: Sun, 16 Jan 2022 17:47:21 -0500 Subject: [PATCH 11/12] Adds a setting value to toggle observing ajax requests --- debug_toolbar/panels/history/panel.py | 5 +- debug_toolbar/settings.py | 1 + .../static/debug_toolbar/js/toolbar.js | 56 +++++++++++-------- .../templates/debug_toolbar/base.html | 3 + debug_toolbar/toolbar.py | 5 ++ 5 files changed, 43 insertions(+), 27 deletions(-) diff --git a/debug_toolbar/panels/history/panel.py b/debug_toolbar/panels/history/panel.py index 24ec432b7..1f7004e77 100644 --- a/debug_toolbar/panels/history/panel.py +++ b/debug_toolbar/panels/history/panel.py @@ -4,7 +4,7 @@ from django.http.request import RawPostDataException from django.template.loader import render_to_string from django.templatetags.static import static -from django.urls import path, reverse +from django.urls import path from django.utils import timezone from django.utils.translation import gettext_lazy as _ @@ -29,9 +29,8 @@ def process_request(self, request): sig = SignedDataForm( initial=HistoryStoreForm(initial={"store_id": store_id}).initial ).initial.get("signed") - response["dj-history-sidebar-url"] = reverse("djdt:history_sidebar") response["DJ-TOOLBAR-STORE-ID"] = store_id - response["DJ-TOOLBAR-SIGNATURE"] = sig + response["DJ-TOOLBAR-STORE-ID-SIGNATURE"] = sig return response @property 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/toolbar.js b/debug_toolbar/static/debug_toolbar/js/toolbar.js index 912ad93ef..613f79b7f 100644 --- a/debug_toolbar/static/debug_toolbar/js/toolbar.js +++ b/debug_toolbar/static/debug_toolbar/js/toolbar.js @@ -220,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"); @@ -277,6 +280,35 @@ const djdt = { 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)) { @@ -320,30 +352,6 @@ const djdt = { }, }; -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-signature"); - const store_id = this.getResponseHeader("dj-toolbar-store-id"); - const history_sidebar_url = this.getResponseHeader( - "dj-history-sidebar-url" - ); - if (signed !== null) { - signed = encodeURIComponent(signed); - const dest = `${history_sidebar_url}?signed=${signed}`; - ajax(dest).then(function (data) { - replaceToolbarState(store_id, data); - }); - } - } - }); - origOpen.apply(this, arguments); -}; - window.djdt = { show_toolbar: djdt.show_toolbar, hide_toolbar: djdt.hide_toolbar, diff --git a/debug_toolbar/templates/debug_toolbar/base.html b/debug_toolbar/templates/debug_toolbar/base.html index d8b8c6135..80403c1fd 100644 --- a/debug_toolbar/templates/debug_toolbar/base.html +++ b/debug_toolbar/templates/debug_toolbar/base.html @@ -15,6 +15,9 @@ data-store-id="{{ toolbar.store_id }}" data-render-panel-url="{% url 'djdt:render_panel' %}" {% endif %} + {% if toolbar.should_update_on_ajax %} + data-sidebar-url="{% url 'djdt:history_sidebar' %}" + {% endif %} data-default-show="{% if toolbar.config.SHOW_COLLAPSED %}false{% else %}true{% endif %}" {{ toolbar.config.ROOT_TAG_EXTRA_ATTRS|safe }}>
      diff --git a/debug_toolbar/toolbar.py b/debug_toolbar/toolbar.py index 364bba9f2..1e9f935bd 100644 --- a/debug_toolbar/toolbar.py +++ b/debug_toolbar/toolbar.py @@ -89,6 +89,11 @@ def should_render_panels(self): render_panels = self.request.META["wsgi.multiprocess"] return render_panels + def should_update_on_ajax(self): + """Determine whether the toolbar should consider ajax requests as updates or not""" + should_watch = self.config["UPDATE_ON_AJAX"] + return should_watch + # Handle storing toolbars in memory and fetching them later on _store = OrderedDict() From dfc6aa69f0395add2249f535523fa153e4437d11 Mon Sep 17 00:00:00 2001 From: Ben Beecher Date: Sun, 16 Jan 2022 18:04:25 -0500 Subject: [PATCH 12/12] Fix bug with switching inside the history panel --- debug_toolbar/static/debug_toolbar/js/history.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/debug_toolbar/static/debug_toolbar/js/history.js b/debug_toolbar/static/debug_toolbar/js/history.js index 3e19e3aea..451e6c989 100644 --- a/debug_toolbar/static/debug_toolbar/js/history.js +++ b/debug_toolbar/static/debug_toolbar/js/history.js @@ -29,6 +29,8 @@ function switchHistory(newStoreId) { 'button[data-store-id="' + newStoreId + '"]' ).innerHTML = "Switch [EXPIRED]"; } + //we're already in history panel, so handle locally vs replacing active html + delete data.HistoryPanel; replaceToolbarState(newStoreId, data); }); } @@ -78,6 +80,3 @@ $$.on(djDebug, "click", ".refreshHistory", function (event) { event.preventDefault(); refreshHistory(); }); - -window.djdt.refreshHistory = refreshHistory; -window.djdt.switchHistory = switchHistory;