[WebProfilerBundle] Show AJAX requests in the symfony profiler toolbar

This commit is contained in:
Bart van den Burg 2013-08-30 16:21:48 +02:00 committed by Fabien Potencier
parent ea6ce1c8af
commit 37f7dd7483
12 changed files with 276 additions and 7 deletions

View File

@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* AjaxDataCollector.
*
* @author Bart van den Burg <bart@burgov.nl>
*/
class AjaxDataCollector extends DataCollector
{
public function collect(Request $request, Response $response, \Exception $exception = null)
{
// all collecting is done client side
}
public function getName()
{
return 'ajax';
}
}

View File

@ -15,6 +15,7 @@
<parameter key="data_collector.router.class">Symfony\Bundle\FrameworkBundle\DataCollector\RouterDataCollector</parameter>
<parameter key="data_collector.form.class">Symfony\Component\Form\Extension\DataCollector\FormDataCollector</parameter>
<parameter key="data_collector.form.extractor.class">Symfony\Component\Form\Extension\DataCollector\FormDataExtractor</parameter>
<parameter key="data_collector.ajax.class">Symfony\Bundle\FrameworkBundle\DataCollector\AjaxDataCollector</parameter>
</parameters>
<services>
@ -28,6 +29,10 @@
<tag name="data_collector" template="@WebProfiler/Collector/request.html.twig" id="request" priority="255" />
</service>
<service id="data_collector.ajax" class="%data_collector.ajax.class%" public="false">
<tag name="data_collector" template="@WebProfiler/Collector/ajax.html.twig" id="ajax" priority="255" />
</service>
<service id="data_collector.exception" class="%data_collector.exception.class%" public="false">
<tag name="data_collector" template="@WebProfiler/Collector/exception.html.twig" id="exception" priority="255" />
</service>
@ -64,6 +69,5 @@
<tag name="data_collector" template="@WebProfiler/Collector/form.html.twig" id="form" priority="255" />
<argument type="service" id="data_collector.form.extractor" />
</service>
</services>
</container>

View File

@ -108,6 +108,7 @@ class ProfilerController
'request' => $request,
'templates' => $this->getTemplateManager()->getTemplates($profile),
'is_ajax' => $request->isXmlHttpRequest(),
'excluded_ajax_paths' => null
)), 200, array('Content-Type' => 'text/html'));
}

View File

@ -45,6 +45,7 @@ class Configuration implements ConfigurationInterface
->end()
->end()
->booleanNode('intercept_redirects')->defaultFalse()->end()
->scalarNode('excluded_ajax_paths')->defaultValue('^/bundles|^/_wdt')->end()
->end()
;

View File

@ -45,6 +45,7 @@ class WebProfilerExtension extends Extension
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('profiler.xml');
$container->setParameter('web_profiler.debug_toolbar.position', $config['position']);
$container->setParameter('web_profiler.debug_toolbar.excluded_ajax_paths', $config['excluded_ajax_paths']);
if ($config['toolbar'] || $config['intercept_redirects']) {
$loader->load('toolbar.xml');

View File

@ -38,14 +38,16 @@ class WebDebugToolbarListener implements EventSubscriberInterface
protected $interceptRedirects;
protected $mode;
protected $position;
protected $excludedAjaxPaths;
public function __construct(\Twig_Environment $twig, $interceptRedirects = false, $mode = self::ENABLED, $position = 'bottom', UrlGeneratorInterface $urlGenerator = null)
public function __construct(\Twig_Environment $twig, $interceptRedirects = false, $mode = self::ENABLED, $position = 'bottom', UrlGeneratorInterface $urlGenerator = null, $excludedAjaxPaths = '^/bundles|^/_wdt')
{
$this->twig = $twig;
$this->urlGenerator = $urlGenerator;
$this->interceptRedirects = (bool) $interceptRedirects;
$this->mode = (int) $mode;
$this->position = $position;
$this->excludedAjaxPaths = $excludedAjaxPaths;
}
public function isEnabled()
@ -113,6 +115,7 @@ class WebDebugToolbarListener implements EventSubscriberInterface
'@WebProfiler/Profiler/toolbar_js.html.twig',
array(
'position' => $this->position,
'excluded_ajax_paths' => $this->excludedAjaxPaths,
'token' => $response->headers->get('X-Debug-Token'),
)
))."\n";

View File

@ -16,6 +16,7 @@
<argument>%web_profiler.debug_toolbar.mode%</argument>
<argument>%web_profiler.debug_toolbar.position%</argument>
<argument type="service" id="router" on-invalid="ignore" />
<argument>%web_profiler.debug_toolbar.excluded_ajax_paths%</argument>
</service>
</services>
</container>

View File

@ -0,0 +1,29 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% block toolbar %}
{% set icon %}
<span>
<img width="13" height="28" alt="AJAX requests" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUIEiMpCg3ocwAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAABAElEQVRIx+3WMQ6CMBQG4L+EMHEIuAOTiZvxEl1g4AAubsa4ungAhnbhFmwmTtwBDsHE8pxIAGkh0BCjdKNJ8/H6Cn8ZEWHNYWHlsTpoD03W9ZPuuxMy19Uu9mIByX3WPBdpSDdcO3PGKhzCoqREmUQI04KMgiqsGTrUMo2NofYUpKoOeLzO2DtOpzcqrINCUPsFZ/cwDQMtpqrUngtymTOuONWqHfmPD38DN3ADvyiA0zCgpGwFbit8XTfD5ZgBAAFA5cXIJWeLKuQyZyL2JsXTXOzj5+1zyQTUkaPNwpHriLKHPpeDlU4N3lmHpo+awrR52Gxv/xa2BAMA9vM37zcnf5IxgZnMRAAAAABJRU5ErkJggg==">
<span class="sf-toolbar-ajax-requests">0</span>
</span>
{% endset %}
{% set text %}
<div class="sf-toolbar-info-piece">
<p><b>AJAX requests</b> <span class="sf-toolbar-ajax-info"></span></p>
<div>
<table class="sf-toolbar-ajax-requests">
<thead>
<tr>
<th>Method</th>
<th>URL</th>
<th>Time</th>
<th>Profile</th>
</tr>
</thead>
<tbody class="sf-toolbar-ajax-request-list"></tbody>
</table>
</div>
</div>
{% endset %}
{% include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': false } %}
{% endblock %}

View File

@ -65,8 +65,148 @@
}
localStorage.setItem(profilerStorageKey + name, value);
},
requestStack = [],
renderAjaxRequests = function() {
var requestCounter = document.getElementsByClassName('sf-toolbar-ajax-requests');
if (!requestCounter.length) {
return;
}
var tbodies = document.getElementsByClassName('sf-toolbar-ajax-request-list');
var state = 'ok';
if (tbodies.length) {
var tbody = tbodies[0];
var rows = document.createDocumentFragment();
if (requestStack.length) {
var firstItem = requestStack.length > 20 ? requestStack.length - 20 : 0;
for (var i = firstItem; i < requestStack.length; i++) {
var request = requestStack[i];
var row = document.createElement('tr');
rows.appendChild(row);
var methodCell = document.createElement('td');
methodCell.innerHTML = request.method;
row.appendChild(methodCell);
var pathCell = document.createElement('td');
pathCell.className = 'sf-ajax-request-url';
pathCell.innerHTML = request.url;
pathCell.setAttribute('title', request.url);
row.appendChild(pathCell);
var durationCell = document.createElement('td');
durationCell.className = 'sf-ajax-request-duration';
if (request.duration) {
durationCell.innerText = request.duration + "ms";
} else {
durationCell.innerText = '-';
}
row.appendChild(durationCell);
row.appendChild(document.createTextNode(' '));
var profilerCell = document.createElement('td');
if (request.profilerUrl) {
var profilerLink = document.createElement('a');
profilerLink.setAttribute('href', request.profilerUrl);
profilerLink.innerText = request.profile;
profilerCell.appendChild(profilerLink);
} else {
profilerCell.innerText = 'n/a';
}
row.appendChild(profilerCell);
var requestState = 'ok';
if (request.error) {
requestState = 'error';
if (state != "loading" && i > requestStack.length - 4) {
state = 'error';
}
} else if (request.loading) {
requestState = 'loading';
state = 'loading'
}
row.className = 'sf-ajax-request sf-ajax-request-' + requestState;
}
while (tbody.firstChild) {
tbody.removeChild(tbody.firstChild);
}
tbody.appendChild(rows);
var infoSpans = document.getElementsByClassName("sf-toolbar-ajax-info");
if (infoSpans.length) {
var text = firstItem == 0 ? 'Showing ' + requestStack.length + ' items' : 'Showing the last 20 '
+ 'out of ' + requestStack.length + ' items';
infoSpans[0].innerText = text;
}
} else {
var cell = document.createElement('td');
cell.setAttribute('colspan', '4');
cell.innerText = "No AJAX requests fired yet.";
var row = document.createElement('tr');
row.appendChild(cell);
tbody.appendChild(row);
}
}
requestCounter[0].innerText = requestStack.length;
var className = 'sf-toolbar-ajax-requests sf-toolbar-status';
if (state == 'ok') {
className += ' sf-toolbar-status-green';
} else if (state == 'error') {
className += ' sf-toolbar-status-red';
} else {
className += ' sf-ajax-request-loading';
}
requestCounter[0].className = className;
};
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 */
if (url.substr(0, 1) === '/' && !url.match(new RegExp("{{ excluded_ajax_paths }}"))) {
var stackElement = {
loading: true,
error: false,
url: url,
method: method,
start: new Date()
};
requestStack.push(stackElement);
this.addEventListener("readystatechange", function() {
if (self.readyState == 4) {
stackElement.duration = new Date() - stackElement.start;
stackElement.loading = false;
stackElement.error = self.status < 200 || self.status >= 400;
stackElement.profile = self.getResponseHeader("X-Debug-Token");
stackElement.profilerUrl = self.getResponseHeader("X-Debug-Token-Link");
Sfjs.renderAjaxRequests();
}
}, false);
Sfjs.renderAjaxRequests();
}
proxied.apply(this, Array.prototype.slice.call(arguments));
};
return {
hasClass: hasClass,
@ -80,6 +220,8 @@
request: request,
renderAjaxRequests: renderAjaxRequests,
load: function(selector, url, onSuccess, onError, options) {
var el = document.getElementById(selector);

View File

@ -283,6 +283,56 @@
line-height: 19px;
}
table.sf-toolbar-ajax-requests {
border-collapse: collapse;
margin: 0 -10px -10px;
}
.sf-toolbar-ajax-requests th, .sf-toolbar-ajax-requests td {
border: 1px solid #ddd;
padding: 0 3px;
}
.sf-toolbar-ajax-requests th {
background-color: #eee;
}
.sf-ajax-request-url {
max-width: 300px;
line-height: 9px;
overflow: hidden;
text-overflow: ellipsis;
}
.sf-ajax-request-duration {
text-align: right;
}
.sf-ajax-request-error {
color: red;
}
.sf-ajax-request-loading {
-webkit-animation: sf-blink .5s ease-in-out infinite;
-o-animation: sf-blink .5s ease-in-out infinite;
-moz-animation: sf-blink .5s ease-in-out infinite;
animation: sf-blink .5s ease-in-out infinite;
}
@-webkit-keyframes sf-blink {
0% { color: black; }
50% { color: #bbb; }
100% { color: black; }
}
@-moz-keyframes sf-blink {
0% { color: black; }
50% { color: #bbb; }
100% { color: black; }
}
@-o-keyframes sf-blink {
0% { color: black; }
50% { color: #bbb; }
100% { color: black; }
}
@keyframes sf-blink {
0% { color: black; }
50% { color: #bbb; }
100% { color: black; }
}
/***** Override the setting when the toolbar is on the top *****/
{% if position == 'top' %}
.sf-minitoolbar {

View File

@ -29,6 +29,8 @@
document.getElementById('sfToolbarClearer-{{ token }}').style.display = 'block';
document.getElementById('sfMiniToolbar-{{ token }}').style.display = 'none';
}
Sfjs.renderAjaxRequests();
},
function(xhr) {
if (xhr.status !== 0) {

View File

@ -31,11 +31,12 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
public function getDebugModes()
{
return array(
array(array(), array('intercept_redirects' => false, 'toolbar' => false, 'position' => 'bottom')),
array(array('intercept_redirects' => true), array('intercept_redirects' => true, 'toolbar' => false, 'position' => 'bottom')),
array(array('intercept_redirects' => false), array('intercept_redirects' => false, 'toolbar' => false, 'position' => 'bottom')),
array(array('toolbar' => true), array('intercept_redirects' => false, 'toolbar' => true, 'position' => 'bottom')),
array(array('position' => 'top'), array('intercept_redirects' => false, 'toolbar' => false, 'position' => 'top')),
array(array(), array('intercept_redirects' => false, 'toolbar' => false, 'position' => 'bottom', 'excluded_ajax_paths' => '^/bundles|^/_wdt')),
array(array('intercept_redirects' => true), array('intercept_redirects' => true, 'toolbar' => false, 'position' => 'bottom', 'excluded_ajax_paths' => '^/bundles|^/_wdt')),
array(array('intercept_redirects' => false), array('intercept_redirects' => false, 'toolbar' => false, 'position' => 'bottom', 'excluded_ajax_paths' => '^/bundles|^/_wdt')),
array(array('toolbar' => true), array('intercept_redirects' => false, 'toolbar' => true, 'position' => 'bottom', 'excluded_ajax_paths' => '^/bundles|^/_wdt')),
array(array('position' => 'top'), array('intercept_redirects' => false, 'toolbar' => false, 'position' => 'top', 'excluded_ajax_paths' => '^/bundles|^/_wdt')),
array(array('excluded_ajax_paths' => 'test'), array('intercept_redirects' => false, 'toolbar' => false, 'position' => 'bottom', 'excluded_ajax_paths' => 'test')),
);
}
}