diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig
index 07feb3856a326..858d44382cc31 100644
--- a/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig
+++ b/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig
@@ -41,7 +41,7 @@
/* create the tab navigation for each group of tabs */
for (var i = 0; i < tabGroups.length; i++) {
- var tabs = tabGroups[i].querySelectorAll('.tab');
+ var tabs = tabGroups[i].querySelectorAll(':scope > .tab');
var tabNavigation = document.createElement('ul');
tabNavigation.className = 'tab-navigation';
@@ -67,7 +67,7 @@
/* display the active tab and add the 'click' event listeners */
for (i = 0; i < tabGroups.length; i++) {
- tabNavigation = tabGroups[i].querySelectorAll('.tab-navigation li');
+ tabNavigation = tabGroups[i].querySelectorAll(':scope >.tab-navigation li');
for (j = 0; j < tabNavigation.length; j++) {
tabId = tabNavigation[j].getAttribute('data-tab-id');
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig
index 8e496fe8a458e..02d4fe5097dfd 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig
+++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig
@@ -178,8 +178,27 @@
Request content not available (it was retrieved as a resource).
{% elseif collector.content %}
-
-
{{ collector.content }}
+
+ {% set prettyJson = collector.isJsonRequest ? collector.prettyJson : null %}
+ {% if prettyJson is not null %}
+
+ {% endif %}
+
+
+
Raw
+
+
+
{{ collector.content }}
+
+
+
{% else %}
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 ea6177e2e8d68..ddd6eeb0c6215 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
@@ -552,7 +552,7 @@
/* create the tab navigation for each group of tabs */
for (var i = 0; i < tabGroups.length; i++) {
- var tabs = tabGroups[i].querySelectorAll('.tab');
+ var tabs = tabGroups[i].querySelectorAll(':scope > .tab');
var tabNavigation = document.createElement('ul');
tabNavigation.className = 'tab-navigation';
@@ -578,7 +578,7 @@
/* display the active tab and add the 'click' event listeners */
for (i = 0; i < tabGroups.length; i++) {
- tabNavigation = tabGroups[i].querySelectorAll('.tab-navigation li');
+ tabNavigation = tabGroups[i].querySelectorAll(':scope > .tab-navigation li');
for (j = 0; j < tabNavigation.length; j++) {
tabId = tabNavigation[j].getAttribute('data-tab-id');
diff --git a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php
index 32cee4bff7b27..c2507b3bc62db 100644
--- a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php
+++ b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php
@@ -263,6 +263,18 @@ public function getContent()
return $this->data['content'];
}
+ public function isJsonRequest()
+ {
+ return 1 === preg_match('{^application/(?:\w+\++)*json$}i', $this->data['request_headers']['content-type']);
+ }
+
+ public function getPrettyJson()
+ {
+ $decoded = json_decode($this->getContent());
+
+ return JSON_ERROR_NONE === json_last_error() ? json_encode($decoded, JSON_PRETTY_PRINT) : null;
+ }
+
public function getContentType()
{
return $this->data['content_type'];
diff --git a/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php
index 24904f7ccadf1..051525e3cb301 100644
--- a/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php
@@ -333,4 +333,58 @@ private function getCookieByName(Response $response, $name)
throw new \InvalidArgumentException(sprintf('Cookie named "%s" is not in response', $name));
}
+
+ /**
+ * @dataProvider provideJsonContentTypes
+ */
+ public function testIsJson($contentType, $expected)
+ {
+ $response = $this->createResponse();
+ $request = $this->createRequest();
+ $request->headers->set('Content-Type', $contentType);
+
+ $c = new RequestDataCollector();
+ $c->collect($request, $response);
+
+ $this->assertSame($expected, $c->isJsonRequest());
+ }
+
+ public function provideJsonContentTypes()
+ {
+ return array(
+ array('text/csv', false),
+ array('application/json', true),
+ array('application/JSON', true),
+ array('application/hal+json', true),
+ array('application/xml+json', true),
+ array('application/xml', false),
+ array('', false),
+ );
+ }
+
+ /**
+ * @dataProvider providePrettyJson
+ */
+ public function testGetPrettyJsonValidity($content, $expected)
+ {
+ $response = $this->createResponse();
+ $request = Request::create('/', 'POST', array(), array(), array(), array(), $content);
+
+ $c = new RequestDataCollector();
+ $c->collect($request, $response);
+
+ $this->assertSame($expected, $c->getPrettyJson());
+ }
+
+ public function providePrettyJson()
+ {
+ return array(
+ array('null', 'null'),
+ array('{ "foo": "bar" }', '{
+ "foo": "bar"
+}'),
+ array('{ "abc" }', null),
+ array('', null),
+ );
+ }
}