diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/twig.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/twig.html.twig index 28df9d26ea792..7bf290efbda07 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/twig.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/twig.html.twig @@ -25,6 +25,13 @@ Macro Calls
+ + {% endset %} {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url }) }} 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 fb266c554bf9e..663d3f0a044da 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 @@ -362,6 +362,103 @@ } {% endif %} + var twigDebugEnabled = function () { + return !!document.getElementById('__twig-debug-bar'); + }; + + var twigDebugEnable = function () { + if (twigDebugEnabled()) { + return; + } + var comments = document.createNodeIterator(document.body, NodeFilter.SHOW_COMMENT), MAX_ZINDEX = 2147483647; + while (comments.nextNode()) { + var comment = comments.referenceNode, match = comment.data.match(/^TWIG-START: (.+)$/); + if (!match) { + continue; + } + + var parts = match[1].split(' '), el = comment.nextSibling, stack = []; + while (!(!el || (el.nodeType === 8 && el.data === ('TWIG-END: ' + parts[0] + ' ' + parts[1])))) { + if (el.nodeType === 3 || (el.nodeType === 1 && el.tagName !== 'SCRIPT')) { + stack.push(el); + } + el = el.nextSibling; + } + + var bbox = {top: Number.MAX_SAFE_INTEGER, left: Number.MAX_SAFE_INTEGER, right: 0, bottom: 0}, i, c; + for (i = 0, c = stack.length; i < c; ++i) { + var target = stack[i], rect; + if (target.nodeType === 3) { + target = document.createRange(); + target.selectNodeContents(stack[i]); + } + rect = target.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) { + continue; + } + bbox.top = Math.min(bbox.top, rect.top); + bbox.left = Math.min(bbox.left, rect.left); + bbox.right = Math.max(bbox.right, rect.right); + bbox.bottom = Math.max(bbox.bottom, rect.bottom); + } + bbox.width = bbox.right - bbox.left; + bbox.height = bbox.bottom - bbox.top; + if (bbox.width <= 0 || bbox.height <= 0) { + continue; + } + + var visual = document.createElement('DIV'); + visual.dataset.twigDebug = parts.join(' '); + visual.style.top = parseInt(bbox.top + document.documentElement.scrollTop || document.body.scrollTop) + 'px'; + visual.style.left = parseInt(bbox.left + document.documentElement.scrollLeft || document.body.scrollLeft) + 'px'; + visual.style.width = parseInt(bbox.width) + 'px'; + visual.style.height = parseInt(bbox.height) + 'px'; + visual.style.zIndex = String(MAX_ZINDEX - Math.min((MAX_ZINDEX - 99999), parseInt(bbox.width * bbox.height))); + addEventListener(visual, 'mouseenter', function () { + var bar = document.getElementById('__twig-debug-bar'), rect = this.getBoundingClientRect(), data = this.dataset.twigDebug.split(' '), label; + switch (data[0] || null) { + case 'MACRO': + label = 'Macro "' + data[2] +'" in "' + data[3] + '" line ' + data[4]; + break; + case 'BLOCK': + label = 'Block "' + data[2] +'" in "' + data[3] + '" line ' + data[4]; + break; + case 'TEMPLATE': + label = 'Template "' + data[2] +'"'; + break; + default: + label = 'Unknown twig element'; + break; + + } + if (hasClass(bar, 'colorized')) { + bar.setAttribute('title', label); + bar.textContent = ''; + } else { + bar.textContent = label; + } + bar.style.top = parseInt(rect.top + (document.documentElement.scrollTop || document.body.scrollTop) + rect.height) + 'px'; + bar.style.left = parseInt(rect.left + (document.documentElement.scrollLeft || document.body.scrollLeft)) + 'px'; + bar.style.display = 'block'; + }); + addEventListener(visual, 'mouseleave', function () { + document.getElementById('__twig-debug-bar').style.display = 'none'; + }); + document.body.appendChild(visual); + } + + var bar = document.createElement('DIV'); + bar.setAttribute('id', '__twig-debug-bar'); + bar.style.zIndex = String(MAX_ZINDEX); + document.body.appendChild(bar); + }; + + var twigDebugDisable = function () { + document.querySelectorAll('[data-twig-debug], #__twig-debug-bar').forEach(function (el) { + el.parentNode.removeChild(el); + }); + }; + return { hasClass: hasClass, @@ -507,6 +604,48 @@ dumpInfo.style.minHeight = ''; }); } + + addEventListener(window, 'resize', function() { + if (twigDebugEnabled()) { + twigDebugDisable(); + twigDebugEnable(); + document.getElementById('__twig-debug-discover').textContent = 'Discover'; + } + }); + + addEventListener(document.getElementById('__twig-debug-enable'), 'click', function () { + var discover = document.getElementById('__twig-debug-discover'); + discover.textContent = 'Discover'; + if (twigDebugEnabled()) { + twigDebugDisable(); + discover.style.display = 'none'; + this.textContent = 'Enable debug'; + } else { + window.scrollTo(0, 0); + twigDebugEnable(); + discover.style.display = 'initial'; + this.textContent = 'Disable debug'; + } + }); + + addEventListener(document.getElementById('__twig-debug-discover'), 'click', function() { + document.querySelectorAll('[data-twig-debug]').forEach(function (el) { + toggleClass(el, 'discovered'); + }); + this.textContent = document.querySelector('[data-twig-debug].discovered') ? 'Cancel discovery' : 'Discover'; + }); + + addEventListener(document.getElementById('__twig-debug-color'), 'change', function() { + var style = document.getElementById('twig-debug-style'); + if (!style) { + style = document.createElement('STYLE'); + document.body.appendChild(style); + } + style.textContent = '#__twig-debug-bar { background: ' + this.value + '; }'; + style.textContent += '[data-twig-debug]:hover { outline-color: ' + this.value + '; }'; + style.textContent += '[data-twig-debug].discovered:not(:hover):after { color: ' + this.value + '; }'; + addClass(document.getElementById('__twig-debug-bar'), 'colorized'); + }); }, function(xhr) { if (xhr.status !== 0) { @@ -675,7 +814,13 @@ toggles[i].setAttribute('data-processed', 'true'); } - } + }, + + twigDebugEnabled: twigDebugEnabled, + + twigDebugEnable: twigDebugEnable, + + twigDebugDisable: twigDebugDisable }; })(); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig index 09056e177093b..f2e0849340637 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig @@ -550,6 +550,45 @@ div.sf-toolbar .sf-toolbar-block a:hover { margin-right: 10px; } +#__twig-debug-bar { + position: absolute; + display: none; + padding: 3px; + margin-top: 3px; + margin-left: -5px; + background: red; + color: white; + font-family: monospace; + font-weight: bold; +} + +#__twig-debug-bar.colorized:after { + mix-blend-mode: difference; + content: attr(title); +} + +#__twig-debug-color { + display: block; + width: 80px; + margin: 5px 0 0 0; +} + +[data-twig-debug] { + position: absolute; +} +[data-twig-debug]:hover { + outline: 2px dashed red; + outline-offset: 3px; +} +[data-twig-debug].discovered:not(:hover):after { + content: ' \25E2'; + font-size: x-small; + color: red; + position: absolute; + right: 0; + bottom: 0; +} + /***** Media query print: Do not print the Toolbar. *****/ @media print { .sf-toolbar { diff --git a/src/Symfony/Bundle/WebProfilerBundle/Twig/Node/HtmlDebugEnterComment.php b/src/Symfony/Bundle/WebProfilerBundle/Twig/Node/HtmlDebugEnterComment.php new file mode 100644 index 0000000000000..554d6fb896a21 --- /dev/null +++ b/src/Symfony/Bundle/WebProfilerBundle/Twig/Node/HtmlDebugEnterComment.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\WebProfilerBundle\Twig\Node; + +use Twig\Node\Node; + +/** + * @author Yonel CerutoNote: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: