diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig index fb5b8b7dadaff..d2616f2bf0630 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig @@ -37,7 +37,7 @@ {% endblock %} -{% block head %} +{% block stylesheets %} {{ parent() }} {% endblock %} -{% block panel %} -

Forms

- - {% if collector.data.forms|length %} -
- -
- -
- {% for formName, formData in collector.data.forms %} - {{ _self.form_tree_details(formName, formData, collector.data.forms_by_hash, loop.first) }} - {% endfor %} -
- {% else %} -
-

No forms were submitted for this request.

-
- {% endif %} +{% block javascripts %} + {{ parent() }} +{% endblock %} - e.stopPropagation(); +{% block panel %} +

Forms

- return false; - }); - }; + {% if collector.data.forms|length %} +
+ +
- tabTarget.initTabs(document.querySelectorAll('.tree .tree-inner')); - toggler.initButtons(document.querySelectorAll('.toggle-button')); - +
+ {% for formName, formData in collector.data.forms %} + {{ _self.form_tree_details(formName, formData, collector.data.forms_by_hash, loop.first) }} + {% endfor %} +
+ {% else %} +
+

No forms were submitted for this request.

+
+ {% endif %} {% endblock %} {% macro form_tree_entry(name, data, is_root) %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig index c2fc5dd323ee2..8055016e04259 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig @@ -1,6 +1,6 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} -{% block head %} +{% block stylesheets %} {{ parent() }} {% endblock %} +{% block javascripts %} + +{% endblock %} + {% block toolbar %} {% if collector.counterrors or collector.countdeprecations or collector.countwarnings %} {% set icon %} @@ -405,8 +490,6 @@

There are no log messages.

- - {% endif %} {% set compilerLogTotal = collector.compilerLogs|reduce((total, logs) => total + logs|length, 0) %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig index 11b5ae07f53b1..386438b8256ff 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig @@ -1,6 +1,6 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} -{% block head %} +{% block stylesheets %} {{ parent() }} {% endblock %} +{% block javascripts %} + {{ parent() }} + + +{% endblock %} + {% block toolbar %} {% set events = collector.events %} @@ -262,8 +292,6 @@ {{ _self.render_email_details(collector, transport, event.message, event.isQueued, show_transport_name) }} {% endfor %} - - {% else %} {% set event = (collector.events.events(transport)|first) %} {{ _self.render_email_details(collector, transport, event.message, event.isQueued, show_transport_name) }} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig index c037c07ec94bc..00b3dba37a0da 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig @@ -12,9 +12,14 @@ {% block head %} - - {{ include('@WebProfiler/Profiler/profiler.css.twig') }} - + {% block stylesheets %} + + {{ include('@WebProfiler/Profiler/profiler.css.twig') }} + + {% endblock %} + + {% block javascripts %} + {% endblock %} {% endblock %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig index 4b4a114045483..e501ebe58bc5d 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig @@ -1,958 +1,212 @@ {# This file is partially duplicated in src/Symfony/Component/ErrorHandler/Resources/assets/js/exception.js. If you make any change in this file, verify the same change is needed in the other file. #} -/* + window.addEventListener('DOMContentLoaded', () => { + new SymfonyProfiler(); + }); - var el = document.createElement('div'); - if (!('addEventListener' in el)) { - addEventListener = function (element, eventName, callback) { - element.attachEvent('on' + eventName, callback); - }; - } else { - addEventListener = function (element, eventName, callback) { - element.addEventListener(eventName, callback, false); - }; + class SymfonyProfiler { + constructor() { + this.#createTabs(); + this.#createToggles(); + this.#convertDateTimesToUserTimezone(); } - if (navigator.clipboard) { - document.addEventListener('readystatechange', () => { - if (document.readyState !== 'complete') { - return; - } - - document.querySelectorAll('[data-clipboard-text]').forEach(function (element) { - removeClass(element, 'hidden'); - element.addEventListener('click', function () { - navigator.clipboard.writeText(element.getAttribute('data-clipboard-text')); - - if (element.classList.contains("label")) { - let oldContent = element.textContent; - - element.textContent = "✅ Copied!"; - element.classList.add("status-success"); - - setTimeout(() => { - element.textContent = oldContent; - element.classList.remove("status-success"); - }, 7000); - } - }); + #createTabs() { + /* the accessibility options of this component have been defined according to: */ + /* www.w3.org/WAI/ARIA/apg/example-index/tabs/tabs-manual.html */ + const tabGroups = document.querySelectorAll('.sf-tabs:not([data-processed=true])'); + + /* create the tab navigation for each group of tabs */ + tabGroups.forEach((tabGroup, i) => { + const tabs = tabGroup.querySelectorAll(':scope > .tab'); + const tabNavigation = document.createElement('div'); + tabNavigation.classList.add('tab-navigation'); + tabNavigation.setAttribute('role', 'tablist'); + + let selectedTabId = `tab-${i}-0`; /* select the first tab by default */ + tabs.forEach((tab, j) => { + const tabId = `tab-${i}-${j}`; + const tabTitle = tab.querySelector('.tab-title').innerHTML; + + const tabNavigationItem = document.createElement('button'); + tabNavigationItem.classList.add('tab-control'); + tabNavigationItem.setAttribute('data-tab-id', tabId); + tabNavigationItem.setAttribute('role', 'tab'); + tabNavigationItem.setAttribute('aria-controls', tabId); + if (tab.classList.contains('active')) { selectedTabId = tabId; } + if (tab.classList.contains('disabled')) { + tabNavigationItem.classList.add('disabled'); + } + tabNavigationItem.innerHTML = tabTitle; + tabNavigation.appendChild(tabNavigationItem); + + const tabContent = tab.querySelector('.tab-content'); + tabContent.parentElement.setAttribute('id', tabId); }); - }); - } - - var request = function(url, onSuccess, onError, payload, options, tries) { - var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'); - options = options || {}; - options.retry = options.retry || false; - tries = tries || 1; - /* this delays for 125, 375, 625, 875, and 1000, ... */ - var delay = tries < 5 ? (tries - 0.5) * 250 : 1000; - - xhr.open(options.method || 'GET', url, true); - xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); - xhr.onreadystatechange = function(state) { - if (4 !== xhr.readyState) { - return null; - } - if (xhr.status == 404 && options.retry && !options.stop) { - setTimeout(function() { - if (options.stop) { - return; - } - request(url, onSuccess, onError, payload, options, tries + 1); - }, delay); - - return null; - } - - if (200 === xhr.status) { - (onSuccess || noop)(xhr); - } else { - (onError || noop)(xhr); - } - }; - - if (options.onSend) { - options.onSend(tries); - } - - xhr.send(payload || ''); - }; - - var getPreference = function(name) { - if (!window.localStorage) { - return null; - } - - return localStorage.getItem(profilerStorageKey + name); - }; - - var setPreference = function(name, value) { - if (!window.localStorage) { - return null; - } - - localStorage.setItem(profilerStorageKey + name, value); - }; - - var requestStack = []; - - var extractHeaders = function(xhr, stackElement) { - /* Here we avoid to call xhr.getResponseHeader in order to */ - /* prevent polluting the console with CORS security errors */ - var allHeaders = xhr.getAllResponseHeaders(); - var ret; - - if (ret = allHeaders.match(/^x-debug-token:\s+(.*)$/im)) { - stackElement.profile = ret[1]; - } - if (ret = allHeaders.match(/^x-debug-token-link:\s+(.*)$/im)) { - stackElement.profilerUrl = ret[1]; - } - if (ret = allHeaders.match(/^Symfony-Debug-Toolbar-Replace:\s+(.*)$/im)) { - stackElement.toolbarReplaceFinished = false; - stackElement.toolbarReplace = '1' === ret[1]; - } - }; - - var successStreak = 4; - var pendingRequests = 0; - var renderAjaxRequests = function() { - var requestCounter = document.querySelector('.sf-toolbar-ajax-request-counter'); - if (!requestCounter) { - return; - } - requestCounter.textContent = requestStack.length; - - var infoSpan = document.querySelector(".sf-toolbar-ajax-info"); - if (infoSpan) { - infoSpan.textContent = requestStack.length + ' AJAX request' + (requestStack.length !== 1 ? 's' : ''); - } - - var ajaxToolbarPanel = document.querySelector('.sf-toolbar-block-ajax'); - if (requestStack.length) { - ajaxToolbarPanel.style.display = 'block'; - } else { - ajaxToolbarPanel.style.display = 'none'; - } - if (pendingRequests > 0) { - addClass(ajaxToolbarPanel, 'sf-ajax-request-loading'); - } else if (successStreak < 4) { - addClass(ajaxToolbarPanel, 'sf-toolbar-status-red'); - removeClass(ajaxToolbarPanel, 'sf-ajax-request-loading'); - } else { - removeClass(ajaxToolbarPanel, 'sf-ajax-request-loading'); - removeClass(ajaxToolbarPanel, 'sf-toolbar-status-red'); - } - }; - - var startAjaxRequest = function(index) { - var tbody = document.querySelector('.sf-toolbar-ajax-request-list'); - if (!tbody) { - return; - } - - var nbOfAjaxRequest = tbody.rows.length; - if (nbOfAjaxRequest >= 100) { - tbody.deleteRow(0); - } - - var request = requestStack[index]; - pendingRequests++; - var row = document.createElement('tr'); - request.DOMNode = row; - - var requestNumberCell = document.createElement('td'); - requestNumberCell.textContent = index + 1; - row.appendChild(requestNumberCell); - - var profilerCell = document.createElement('td'); - profilerCell.textContent = 'n/a'; - row.appendChild(profilerCell); - - var methodCell = document.createElement('td'); - methodCell.textContent = request.method; - row.appendChild(methodCell); - - var typeCell = document.createElement('td'); - typeCell.textContent = request.type; - row.appendChild(typeCell); - - var statusCodeCell = document.createElement('td'); - var statusCode = document.createElement('span'); - statusCode.textContent = 'n/a'; - statusCodeCell.appendChild(statusCode); - row.appendChild(statusCodeCell); - - var pathCell = document.createElement('td'); - pathCell.className = 'sf-ajax-request-url'; - if ('GET' === request.method) { - var pathLink = document.createElement('a'); - pathLink.setAttribute('href', request.url); - pathLink.textContent = request.url; - pathCell.appendChild(pathLink); - } else { - pathCell.textContent = request.url; - } - pathCell.setAttribute('title', request.url); - row.appendChild(pathCell); - - var durationCell = document.createElement('td'); - durationCell.className = 'sf-ajax-request-duration'; - durationCell.textContent = 'n/a'; - row.appendChild(durationCell); - - request.liveDurationHandle = setInterval(function() { - durationCell.textContent = (new Date() - request.start) + ' ms'; - }, 100); - - row.className = 'sf-ajax-request sf-ajax-request-loading'; - tbody.insertBefore(row, null); - - var toolbarInfo = document.querySelector('.sf-toolbar-block-ajax .sf-toolbar-info'); - toolbarInfo.scrollTop = toolbarInfo.scrollHeight; - - renderAjaxRequests(); - }; - - var finishAjaxRequest = function(index) { - var request = requestStack[index]; - clearInterval(request.liveDurationHandle); - - if (!request.DOMNode) { - return; - } - - if (request.toolbarReplace && !request.toolbarReplaceFinished && request.profile) { - /* Flag as complete because finishAjaxRequest can be called multiple times. */ - request.toolbarReplaceFinished = true; - /* Search up through the DOM to find the toolbar's container ID. */ - for (var elem = request.DOMNode; elem && elem !== document; elem = elem.parentNode) { - if (elem.id.match(/^sfwdt/)) { - Sfjs.loadToolbar(elem.id.replace(/^sfwdt/, ''), request.profile); - break; - } - } - } - - pendingRequests--; - var row = request.DOMNode; - /* Unpack the children from the row */ - var profilerCell = row.children[1]; - var methodCell = row.children[2]; - var statusCodeCell = row.children[4]; - var statusCodeElem = statusCodeCell.children[0]; - var durationCell = row.children[6]; - - if (request.error) { - row.className = 'sf-ajax-request sf-ajax-request-error'; - methodCell.className = 'sf-ajax-request-error'; - successStreak = 0; - } else { - row.className = 'sf-ajax-request sf-ajax-request-ok'; - successStreak++; - } - - if (request.statusCode) { - if (request.statusCode < 300) { - statusCodeElem.setAttribute('class', 'sf-toolbar-status'); - } else if (request.statusCode < 400) { - statusCodeElem.setAttribute('class', 'sf-toolbar-status sf-toolbar-status-yellow'); - } else { - statusCodeElem.setAttribute('class', 'sf-toolbar-status sf-toolbar-status-red'); - } - statusCodeElem.textContent = request.statusCode; - } else { - statusCodeElem.setAttribute('class', 'sf-toolbar-status sf-toolbar-status-red'); - } - - if (request.duration) { - durationCell.textContent = request.duration + ' ms'; - } - - if (request.profilerUrl) { - profilerCell.textContent = ''; - var profilerLink = document.createElement('a'); - profilerLink.setAttribute('href', request.profilerUrl); - profilerLink.textContent = request.profile; - profilerCell.appendChild(profilerLink); - } - - renderAjaxRequests(); - }; - - {% if excluded_ajax_paths is defined %} - if (window.fetch && window.fetch.polyfill === undefined) { - var oldFetch = window.fetch; - window.fetch = function () { - var promise = oldFetch.apply(this, arguments); - var url = arguments[0]; - var params = arguments[1]; - var paramType = Object.prototype.toString.call(arguments[0]); - if (paramType === '[object Request]') { - url = arguments[0].url; - params = { - method: arguments[0].method, - credentials: arguments[0].credentials, - headers: arguments[0].headers, - mode: arguments[0].mode, - redirect: arguments[0].redirect - }; - } else { - url = String(url); - } - if (!url.match(new RegExp({{ excluded_ajax_paths|json_encode|raw }}))) { - var method = 'GET'; - if (params && params.method !== undefined) { - method = params.method; - } - - var stackElement = { - error: false, - url: url, - method: method, - type: 'fetch', - start: new Date() - }; - - var idx = requestStack.push(stackElement) - 1; - promise.then(function (r) { - stackElement.duration = new Date() - stackElement.start; - stackElement.error = r.status < 200 || r.status >= 400; - stackElement.statusCode = r.status; - stackElement.profile = r.headers.get('x-debug-token'); - stackElement.profilerUrl = r.headers.get('x-debug-token-link'); - stackElement.toolbarReplaceFinished = false; - stackElement.toolbarReplace = '1' === r.headers.get('Symfony-Debug-Toolbar-Replace'); - finishAjaxRequest(idx); - }, function (e){ - stackElement.error = true; - finishAjaxRequest(idx); - }); - startAjaxRequest(idx); - } - - return promise; - }; - } - if (window.XMLHttpRequest && XMLHttpRequest.prototype.addEventListener) { - var proxied = XMLHttpRequest.prototype.open; - - XMLHttpRequest.prototype.open = function(method, url, async, user, pass) { - var self = this; - - /* prevent logging AJAX calls to static and inline files, like templates */ - var path = url; - if (url.slice(0, 1) === '/') { - if (0 === url.indexOf('{{ request.basePath|e('js') }}')) { - path = url.slice({{ request.basePath|length }}); - } - } - else if (0 === url.indexOf('{{ (request.schemeAndHttpHost ~ request.basePath)|e('js') }}')) { - path = url.slice({{ (request.schemeAndHttpHost ~ request.basePath)|length }}); - } - - if (!path.match(new RegExp({{ excluded_ajax_paths|json_encode|raw }}))) { - var stackElement = { - error: false, - url: url, - method: method, - type: 'xhr', - start: new Date() - }; - - var idx = requestStack.push(stackElement) - 1; - - this.addEventListener('readystatechange', function() { - if (self.readyState == 4) { - stackElement.duration = new Date() - stackElement.start; - stackElement.error = self.status < 200 || self.status >= 400; - stackElement.statusCode = self.status; - extractHeaders(self, stackElement); - - finishAjaxRequest(idx); - } - }, false); - - startAjaxRequest(idx); - } - - proxied.apply(this, Array.prototype.slice.call(arguments)); - }; - } - {% endif %} - - return { - hasClass: hasClass, - - removeClass: removeClass, - - addClass: addClass, - - toggleClass: toggleClass, - - getPreference: getPreference, - - setPreference: setPreference, - - addEventListener: addEventListener, - - request: request, - - renderAjaxRequests: renderAjaxRequests, - - getSfwdt: function(token) { - if (!this.sfwdt) { - this.sfwdt = document.getElementById('sfwdt' + token); - } - - return this.sfwdt; - }, - - load: function(selector, url, onSuccess, onError, options) { - var el = document.getElementById(selector); - - if (el && el.getAttribute('data-sfurl') !== url) { - request( - url, - function(xhr) { - el.innerHTML = xhr.responseText; - el.setAttribute('data-sfurl', url); - removeClass(el, 'loading'); - var pending = pendingRequests; - for (var i = 0; i < requestStack.length; i++) { - startAjaxRequest(i); - if (requestStack[i].duration) { - finishAjaxRequest(i); - } - } - /* Revert the pending state in case there was a start called without a finish above. */ - pendingRequests = pending; - (onSuccess || noop)(xhr, el); - }, - function(xhr) { (onError || noop)(xhr, el); }, - '', - options - ); - } - - return this; - }, - - showToolbar: function(token) { - var sfwdt = this.getSfwdt(token); - removeClass(sfwdt, 'sf-display-none'); - - if (getPreference('toolbar/displayState') == 'none') { - document.getElementById('sfToolbarMainContent-' + token).style.display = 'none'; - document.getElementById('sfToolbarClearer-' + token).style.display = 'none'; - document.getElementById('sfMiniToolbar-' + token).style.display = 'block'; - } else { - document.getElementById('sfToolbarMainContent-' + token).style.display = 'block'; - document.getElementById('sfToolbarClearer-' + token).style.display = 'block'; - document.getElementById('sfMiniToolbar-' + token).style.display = 'none'; - } - }, - - hideToolbar: function(token) { - var sfwdt = this.getSfwdt(token); - addClass(sfwdt, 'sf-display-none'); - }, - - initToolbar: function(token) { - this.showToolbar(token); - - var hideButton = document.getElementById('sfToolbarHideButton-' + token); - var hideButtonSvg = hideButton.querySelector('svg'); - hideButtonSvg.setAttribute('aria-hidden', 'true'); - hideButtonSvg.setAttribute('focusable', 'false'); - addEventListener(hideButton, 'click', function (event) { - event.preventDefault(); - - var p = this.parentNode; - p.style.display = 'none'; - (p.previousElementSibling || p.previousSibling).style.display = 'none'; - document.getElementById('sfMiniToolbar-' + token).style.display = 'block'; - setPreference('toolbar/displayState', 'none'); - }); + tabGroup.insertBefore(tabNavigation, tabGroup.firstChild); + document.querySelector('[data-tab-id="' + selectedTabId + '"]').classList.add('active'); + }); - var showButton = document.getElementById('sfToolbarMiniToggler-' + token); - var showButtonSvg = showButton.querySelector('svg'); - showButtonSvg.setAttribute('aria-hidden', 'true'); - showButtonSvg.setAttribute('focusable', 'false'); - addEventListener(showButton, 'click', function (event) { - event.preventDefault(); - - var elem = this.parentNode; - if (elem.style.display == 'none') { - document.getElementById('sfToolbarMainContent-' + token).style.display = 'none'; - document.getElementById('sfToolbarClearer-' + token).style.display = 'none'; - elem.style.display = 'block'; + /* display the active tab and add the 'click' event listeners */ + tabGroups.forEach((tabGroup) => { + const tabs = tabGroup.querySelectorAll(':scope > .tab-navigation .tab-control'); + tabs.forEach((tab) => { + const tabId = tab.getAttribute('data-tab-id'); + const tabPanel = document.getElementById(tabId); + tabPanel.setAttribute('role', 'tabpanel'); + tabPanel.setAttribute('aria-labelledby', tabId); + tabPanel.querySelector('.tab-title').className = 'hidden'; + + if (tab.classList.contains('active')) { + tabPanel.className = 'block'; + tab.setAttribute('aria-selected', 'true'); + tab.removeAttribute('tabindex'); } else { - document.getElementById('sfToolbarMainContent-' + token).style.display = 'block'; - document.getElementById('sfToolbarClearer-' + token).style.display = 'block'; - elem.style.display = 'none' + tabPanel.className = 'hidden'; + tab.removeAttribute('aria-selected'); + tab.setAttribute('tabindex', '-1'); } - setPreference('toolbar/displayState', 'block'); - }); - }, - - loadToolbar: function(token, newToken) { - var that = this; - var triesCounter = document.getElementById('sfLoadCounter-' + token); - - var options = { - retry: true, - onSend: function (count) { - if (count === 3) { - that.initToolbar(token); - } + tab.addEventListener('click', function(e) { + const activeTab = e.target || e.srcElement; - if (triesCounter) { - triesCounter.textContent = count; + /* needed because when the tab contains HTML contents, user can click */ + /* on any of those elements instead of their parent '