diff --git a/core/lib/Drupal/Core/ContentNegotiation.php b/core/lib/Drupal/Core/ContentNegotiation.php
index 857f9d5..3322af8 100644
--- a/core/lib/Drupal/Core/ContentNegotiation.php
+++ b/core/lib/Drupal/Core/ContentNegotiation.php
@@ -38,7 +38,7 @@ public function getContentType(Request $request) {
 
     // Check all formats, if priority format is found return it.
     $first_found_format = FALSE;
-    $priority = array('html', 'drupal_ajax', 'drupal_modal', 'drupal_dialog');
+    $priority = array('html');
     foreach ($request->getAcceptableContentTypes() as $mime_type) {
       $format = $request->getFormat($mime_type);
       if (in_array($format, $priority, TRUE)) {
diff --git a/core/lib/Drupal/Core/EventSubscriber/MainContentViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/MainContentViewSubscriber.php
index fcc51a3..cbdce65 100644
--- a/core/lib/Drupal/Core/EventSubscriber/MainContentViewSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/MainContentViewSubscriber.php
@@ -51,6 +51,13 @@ class MainContentViewSubscriber implements EventSubscriberInterface {
   protected $mainContentRenderers;
 
   /**
+   * Url query attribute to indicate the wrapper used to render a request.
+   *
+   * This controls wrapping the HTML content for example in a modal dialog.
+   */
+  const WRAPPER_ENVELOPE = '_wrapper_format';
+
+  /**
    * Constructs a new MainContentViewSubscriber object.
    *
    * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
@@ -76,20 +83,22 @@ public function onViewRenderArray(GetResponseForControllerResultEvent $event) {
     $request = $event->getRequest();
     $result = $event->getControllerResult();
 
-    $format = $request->getRequestFormat();
-
     // Render the controller result into a response if it's a render array.
     if (is_array($result)) {
-      if (isset($this->mainContentRenderers[$format])) {
-        $renderer = $this->classResolver->getInstanceFromDefinition($this->mainContentRenderers[$format]);
+      // We default to getRequestFormat() because content negotiation uses this as
+      // its fallback method for displaying a 406 when we fail to match a route
+      // based on format.
+      // @todo this logic should be replaced with a more clear opt in to avoid
+      //    cache poisoning. https://www.drupal.org/node/2364011
+      $wrapper = $request->query->get(static::WRAPPER_ENVELOPE, $request->getRequestFormat());
+      if (isset($this->mainContentRenderers[$wrapper])) {
+        $renderer = $this->classResolver->getInstanceFromDefinition($this->mainContentRenderers[$wrapper]);
         $event->setResponse($renderer->renderResponse($result, $request, $this->routeMatch));
       }
       else {
-        $supported_formats = array_keys($this->mainContentRenderers);
-        $supported_mimetypes = array_map([$request, 'getMimeType'], $supported_formats);
         $event->setResponse(new JsonResponse([
           'message' => 'Not Acceptable.',
-          'supported_mime_types' => $supported_mimetypes,
+          'supported_wrapper_envelopes' => array_keys($this->mainContentRenderers),
         ], 406));
       }
     }
diff --git a/core/lib/Drupal/Core/Render/Element/RenderElement.php b/core/lib/Drupal/Core/Render/Element/RenderElement.php
index 0283333..b9de164 100644
--- a/core/lib/Drupal/Core/Render/Element/RenderElement.php
+++ b/core/lib/Drupal/Core/Render/Element/RenderElement.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Render\Element;
 
+use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\PluginBase;
 use Drupal\Core\Render\Element;
@@ -240,7 +241,7 @@ public static function preRenderAjaxForm($element) {
       $settings += array(
         'url' => isset($settings['callback']) ? Url::fromRoute('system.ajax') : NULL,
         'options' => array(),
-        'accepts' => 'application/vnd.drupal-ajax'
+        MainContentViewSubscriber::WRAPPER_ENVELOPE => 'ajax',
       );
 
       // @todo Legacy support. Remove in Drupal 8.
diff --git a/core/misc/ajax.js b/core/misc/ajax.js
index e2aeab1..1e26c9d 100644
--- a/core/misc/ajax.js
+++ b/core/misc/ajax.js
@@ -17,6 +17,13 @@
   Drupal.ajax = Drupal.ajax || {};
 
   /**
+   * Url query attribute to indicate the wrapper used to render a request.
+   *
+   * This controls wrapping the HTML content for example in a modal dialog.
+   */
+  Drupal.ajax.WRAPPER_ENVELOPE = '_wrapper_format';
+
+  /**
    * Attaches the Ajax behavior to each Ajax form element.
    */
   Drupal.behaviors.AJAX = {
@@ -52,7 +59,7 @@
           element_settings.url = $(this).attr('href');
           element_settings.event = 'click';
         }
-        element_settings.accepts = $(this).data('accepts');
+        element_settings.dialogType = $(this).data('dialog-type');
         element_settings.dialog = $(this).data('dialog-options');
         var baseUseAjax = $(this).attr('id');
         Drupal.ajax[baseUseAjax] = new Drupal.ajax(baseUseAjax, this, element_settings);
@@ -256,9 +263,6 @@
         }
       },
       dataType: 'json',
-      accepts: {
-        json: element_settings.accepts || 'application/vnd.drupal-ajax'
-      },
       type: 'POST'
     };
 
@@ -266,6 +270,18 @@
       ajax.options.data.dialogOptions = element_settings.dialog;
     }
 
+    if (element_settings.dialogType) {
+      // Ensure that we have a valid URL by adding ? when no query parameter is
+      // yet available, otherwise append using &.
+      if (ajax.options.url.indexOf('?') === -1) {
+        ajax.options.url += '?';
+      }
+      else {
+        ajax.options.url += '&';
+      }
+      ajax.options.url += Drupal.ajax.WRAPPER_ENVELOPE + '=drupal_' + element_settings.dialogType;
+    }
+
     // Bind the ajaxSubmit function to the element event.
     $(ajax.element).on(element_settings.event, function (event) {
       return ajax.eventResponse(this, event);
diff --git a/core/modules/block/src/BlockListBuilder.php b/core/modules/block/src/BlockListBuilder.php
index 6521287..913b718 100644
--- a/core/modules/block/src/BlockListBuilder.php
+++ b/core/modules/block/src/BlockListBuilder.php
@@ -362,7 +362,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
         ]),
         'attributes' => array(
           'class' => array('use-ajax', 'block-filter-text-source'),
-          'data-accepts' => 'application/vnd.drupal-modal',
+          'data-dialog-type' => 'modal',
           'data-dialog-options' => Json::encode(array(
             'width' => 700,
           )),
diff --git a/core/modules/ckeditor/js/ckeditor.js b/core/modules/ckeditor/js/ckeditor.js
index 1596808..525f8d2 100644
--- a/core/modules/ckeditor/js/ckeditor.js
+++ b/core/modules/ckeditor/js/ckeditor.js
@@ -160,8 +160,8 @@
       // a Drupal.ajax instance to load the dialog and trigger it.
       var $content = $('<div class="ckeditor-dialog-loading"><span style="top: -40px;" class="ckeditor-dialog-loading-link"><a>' + Drupal.t('Loading...') + '</a></span></div>');
       $content.appendTo($target);
-      new Drupal.ajax('ckeditor-dialog', $content.find('a').get(0), {
-        accepts: 'application/vnd.drupal-modal',
+
+      var options = {
         dialog: dialogSettings,
         selector: '.ckeditor-dialog-loading-link',
         url: url,
@@ -170,7 +170,9 @@
         submit: {
           editor_object: existingValues
         }
-      });
+      };
+      options[Drupal.ajax.WRAPPER_ENVELOPE] = 'modal';
+      new Drupal.ajax('ckeditor-dialog', $content.find('a').get(0), options);
       $content.find('a')
         .on('click', function () { return false; })
         .trigger('ckeditor-internal.ckeditor');
diff --git a/core/modules/config/src/Form/ConfigSync.php b/core/modules/config/src/Form/ConfigSync.php
index b97218b..0a91758 100644
--- a/core/modules/config/src/Form/ConfigSync.php
+++ b/core/modules/config/src/Form/ConfigSync.php
@@ -281,7 +281,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
             'url' => Url::fromRoute($route_name, $route_options),
             'attributes' => array(
               'class' => array('use-ajax'),
-              'data-accepts' => 'application/vnd.drupal-modal',
+              'data-dialog-type' => 'modal',
               'data-dialog-options' => json_encode(array(
                 'width' => 700
               )),
diff --git a/core/modules/rest/src/Routing/ResourceRoutes.php b/core/modules/rest/src/Routing/ResourceRoutes.php
index 9cc60b1..484c48e 100644
--- a/core/modules/rest/src/Routing/ResourceRoutes.php
+++ b/core/modules/rest/src/Routing/ResourceRoutes.php
@@ -19,7 +19,7 @@
 /**
  * Subscriber for REST-style routes.
  */
-class ResourceRoutes extends RouteSubscriberBase{
+class ResourceRoutes extends RouteSubscriberBase {
 
   /**
    * The plugin manager for REST plugins.
diff --git a/core/modules/rest/src/Tests/ReadTest.php b/core/modules/rest/src/Tests/ReadTest.php
index d5f2fc1..f54c7de 100644
--- a/core/modules/rest/src/Tests/ReadTest.php
+++ b/core/modules/rest/src/Tests/ReadTest.php
@@ -92,7 +92,7 @@ public function testRead() {
     // and hence when there is no matching REST route, the non-REST route is
     // used, but it can't render into application/hal+json, so it returns a 406.
     $this->assertResponse('406', 'HTTP response code is 406 when the resource does not define formats, because it falls back to the canonical, non-REST route.');
-    $this->assertTrue(strpos($response, '{"message":"Not Acceptable.","supported_mime_types":') !== FALSE);
+    $this->assertTrue(strpos($response, '{"message":"Not Acceptable.","supported_wrapper_envelopes":') !== FALSE);
   }
 
   /**
diff --git a/core/modules/rest/src/Tests/ResourceTest.php b/core/modules/rest/src/Tests/ResourceTest.php
index be02617..5144ae3 100644
--- a/core/modules/rest/src/Tests/ResourceTest.php
+++ b/core/modules/rest/src/Tests/ResourceTest.php
@@ -54,7 +54,7 @@ public function testFormats() {
     $this->config->save();
     $this->rebuildCache();
 
-    // Verify that accessing the resource returns 401.
+    // Verify that accessing the resource returns 406.
     $response = $this->httpRequest($this->entity->urlInfo(), 'GET', NULL, $this->defaultMimeType);
     // AcceptHeaderMatcher considers the canonical, non-REST route a match, but
     // a lower quality one: no format restrictions means there's always a match,
diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php
index fe96f9f..a8e99d6 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/WebTestBase.php
@@ -13,6 +13,7 @@
 use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\NestedArray;
+use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Cache\Cache;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\DependencyInjection\YamlFileLoader;
@@ -20,6 +21,7 @@
 use Drupal\Core\Database\Database;
 use Drupal\Core\Database\ConnectionNotDefinedException;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Session\AccountInterface;
@@ -1556,8 +1558,8 @@ protected function drupalGetJSON($path, array $options = array(), array $headers
    * Requests a Drupal path in drupal_ajax format and JSON-decodes the response.
    */
   protected function drupalGetAjax($path, array $options = array(), array $headers = array()) {
-    if (!preg_grep('/^Accept:/', $headers)) {
-      $headers[] = 'Accept: application/vnd.drupal-ajax';
+    if (!isset($options['query'][MainContentViewSubscriber::WRAPPER_ENVELOPE])) {
+      $options['query'][MainContentViewSubscriber::WRAPPER_ENVELOPE] = 'drupal_ajax';
     }
     return Json::decode($this->drupalGet($path, $options, $headers));
   }
@@ -1785,6 +1787,11 @@ protected function drupalPostForm($path, $edit, $submit, array $options = array(
    * @see ajax.js
    */
   protected function drupalPostAjaxForm($path, $edit, $triggering_element, $ajax_path = NULL, array $options = array(), array $headers = array(), $form_html_id = NULL, $ajax_settings = NULL) {
+    if (isset($options['query'][MainContentViewSubscriber::WRAPPER_ENVELOPE])) {
+      $original_wrapper = $options['query'][MainContentViewSubscriber::WRAPPER_ENVELOPE];
+      unset($options['query'][MainContentViewSubscriber::WRAPPER_ENVELOPE]);
+    }
+
     // Get the content of the initial page prior to calling drupalPostForm(),
     // since drupalPostForm() replaces $this->content.
     if (isset($path)) {
@@ -1793,8 +1800,11 @@ protected function drupalPostAjaxForm($path, $edit, $triggering_element, $ajax_p
     $content = $this->content;
     $drupal_settings = $this->drupalSettings;
 
-    if (!preg_grep('/^Accept:/', $headers)) {
-      $headers[] = 'Accept: application/vnd.drupal-ajax';
+    if (isset($original_wrapper)) {
+      $options['query'][MainContentViewSubscriber::WRAPPER_ENVELOPE] = $original_wrapper;
+    }
+    else {
+      $options['query'][MainContentViewSubscriber::WRAPPER_ENVELOPE] = 'drupal_ajax';
     }
 
     // Get the Ajax settings bound to the triggering element.
@@ -1834,8 +1844,21 @@ protected function drupalPostAjaxForm($path, $edit, $triggering_element, $ajax_p
     // Unless a particular path is specified, use the one specified by the
     // Ajax settings, or else 'system/ajax'.
     if (!isset($ajax_path)) {
-      $ajax_path = isset($ajax_settings['url']) ? $ajax_settings['url'] : 'system/ajax';
+      if (isset($ajax_settings['url'])) {
+        $parsed_url = UrlHelper::parse($ajax_settings['url']);
+        $options['query'] = $parsed_url['query'] + $options['query'];
+        $options += ['fragment' => $parsed_url['fragment']];
+        $ajax_path = preg_replace(
+          '/^' . preg_quote($GLOBALS['base_path'], '/') . '/',
+          '',
+          $parsed_url['path']
+        );
+      }
+      else {
+        $ajax_path = 'system/ajax';
+      }
     }
+    $ajax_path = $this->container->get('unrouted_url_assembler')->assemble('base://' . $ajax_path, $options);
 
     // Submit the POST request.
     $return = Json::decode($this->drupalPostForm(NULL, $edit, array('path' => $ajax_path, 'triggering_element' => $triggering_element), $options, $headers, $form_html_id, $extra_post));
diff --git a/core/modules/system/src/Tests/Ajax/DialogTest.php b/core/modules/system/src/Tests/Ajax/DialogTest.php
index 482e025..c78e94c 100644
--- a/core/modules/system/src/Tests/Ajax/DialogTest.php
+++ b/core/modules/system/src/Tests/Ajax/DialogTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\Ajax;
 
+use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
 use Drupal\Core\Url;
 
 /**
@@ -93,7 +94,7 @@ public function testDialog() {
     $this->assertRaw($dialog_contents, 'Non-JS modal dialog page present.');
 
     // Emulate going to the JS version of the page and check the JSON response.
-    $ajax_result = $this->drupalGetAjax('ajax-test/dialog-contents', array(), array('Accept: application/vnd.drupal-modal'));
+    $ajax_result = $this->drupalGetAjax('ajax-test/dialog-contents', array('query' => array(MainContentViewSubscriber::WRAPPER_ENVELOPE => 'drupal_modal')));
     $this->assertEqual($modal_expected_response, $ajax_result[3], 'Modal dialog JSON response matches.');
 
     // Check that requesting a "normal" dialog without JS goes to a page.
@@ -106,7 +107,7 @@ public function testDialog() {
     $ajax_result = $this->drupalPostAjaxForm('ajax-test/dialog', array(
         // We have to mock a form element to make drupalPost submit from a link.
         'textfield' => 'test',
-      ), array(), 'ajax-test/dialog-contents', array(), array('Accept: application/vnd.drupal-dialog'), NULL, array(
+      ), array(), 'ajax-test/dialog-contents', array('query' => array(MainContentViewSubscriber::WRAPPER_ENVELOPE => 'drupal_dialog')), array(), NULL, array(
       'submit' => array(
         'dialogOptions[target]' => 'ajax-test-dialog-wrapper-1',
       )
@@ -119,7 +120,7 @@ public function testDialog() {
     $ajax_result = $this->drupalPostAjaxForm('ajax-test/dialog', array(
         // We have to mock a form element to make drupalPost submit from a link.
         'textfield' => 'test',
-      ), array(), 'ajax-test/dialog-contents', array(), array('Accept: application/vnd.drupal-dialog'), NULL, array(
+      ), array(), 'ajax-test/dialog-contents', array('query' => array(MainContentViewSubscriber::WRAPPER_ENVELOPE => 'drupal_dialog')), array(), NULL, array(
       // Don't send a target.
       'submit' => array()
     ));
@@ -159,13 +160,13 @@ public function testDialog() {
     $this->assertTrue(!empty($form), 'Non-JS form page present.');
 
     // Emulate going to the JS version of the form and check the JSON response.
-    $ajax_result = $this->drupalGetAjax('ajax-test/dialog-form', array(), array('Accept: application/vnd.drupal-modal'));
+    $ajax_result = $this->drupalGetAjax('ajax-test/dialog-form', array('query' => array(MainContentViewSubscriber::WRAPPER_ENVELOPE => 'drupal_modal')));
     $expected_ajax_settings = [
       'edit-preview' => [
         'callback' => '::preview',
         'event' => 'click',
         'url' => Url::fromRoute('system.ajax')->toString(),
-        'accepts' => 'application/vnd.drupal-ajax',
+        MainContentViewSubscriber::WRAPPER_ENVELOPE => 'ajax',
         'submit' => [
           '_triggering_element_name' => 'op',
           '_triggering_element_value' => 'Preview',
@@ -188,7 +189,7 @@ public function testDialog() {
     $this->assertTrue(!empty($form), 'Non-JS entity form page present.');
 
     // Emulate going to the JS version of the form and check the JSON response.
-    $ajax_result = $this->drupalGetAjax('admin/structure/contact/add', array(), array('Accept: application/vnd.drupal-modal'));
+    $ajax_result = $this->drupalGetAjax('admin/structure/contact/add', array('query' => array(MainContentViewSubscriber::WRAPPER_ENVELOPE => 'drupal_modal')));
     $this->setRawContent($ajax_result[3]['data']);
     // Remove the data, the form build id and token will never match.
     unset($ajax_result[3]['data']);
diff --git a/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php
index b54b823..663a904 100644
--- a/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php
+++ b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php
@@ -120,7 +120,7 @@ public function dialog() {
       '#url' => Url::fromRoute('ajax_test.dialog_contents'),
       '#attributes' => array(
         'class' => array('use-ajax'),
-        'data-accepts' => 'application/vnd.drupal-modal',
+        'data-accepts' => 'application/vnd.drupal-modal', // TODO replace this.
       ),
     );
 
@@ -133,7 +133,7 @@ public function dialog() {
           'url' => Url::fromRoute('ajax_test.dialog_contents'),
           'attributes' => array(
             'class' => array('use-ajax'),
-            'data-accepts' => 'application/vnd.drupal-modal',
+            'data-dialog-type' => 'modal',
             'data-dialog-options' => json_encode(array(
               'width' => 400,
             ))
@@ -144,7 +144,7 @@ public function dialog() {
           'url' => Url::fromRoute('ajax_test.dialog_contents'),
           'attributes' => array(
             'class' => array('use-ajax'),
-            'data-accepts' => 'application/vnd.drupal-dialog',
+            'data-dialog-type' => 'dialog',
             'data-dialog-options' => json_encode(array(
               'target' => 'ajax-test-dialog-wrapper-1',
               'width' => 800,
@@ -163,7 +163,7 @@ public function dialog() {
           'url' => Url::fromRoute('ajax_test.dialog_form'),
           'attributes' => array(
             'class' => array('use-ajax'),
-            'data-accepts' => 'application/vnd.drupal-modal',
+            'data-dialog-type' => 'modal',
           ),
         ),
         'link6' => array(
@@ -171,7 +171,7 @@ public function dialog() {
           'url' => Url::fromRoute('contact.form_add'),
           'attributes' => array(
             'class' => array('use-ajax'),
-            'data-accepts' => 'application/vnd.drupal-modal',
+            'data-dialog-type' => 'modal',
             'data-dialog-options' => json_encode(array(
               'width' => 800,
               'height' => 500,
@@ -183,7 +183,7 @@ public function dialog() {
           'url' => Url::fromRoute('ajax_test.dialog_contents'),
           'attributes' => array(
             'class' => array('use-ajax'),
-            'data-accepts' => 'application/vnd.drupal-dialog',
+            'data-dialog-type' => 'dialog',
             'data-dialog-options' => json_encode(array(
               'width' => 800,
             ))
diff --git a/core/tests/Drupal/Tests/Core/ContentNegotiationTest.php b/core/tests/Drupal/Tests/Core/ContentNegotiationTest.php
index e411008..d5f3081 100644
--- a/core/tests/Drupal/Tests/Core/ContentNegotiationTest.php
+++ b/core/tests/Drupal/Tests/Core/ContentNegotiationTest.php
@@ -60,9 +60,6 @@ public function testAPriorityFormatIsFound($priority, $format) {
   public function priorityFormatProvider()
   {
     return [
-      ['drupal_dialog', ['format' => 'drupal_dialog', 'mime_type' => 'application/vnd.drupal-dialog']],
-      ['drupal_modal', ['format' => 'drupal_modal', 'mime_type' => 'application/vnd.drupal-modal']],
-      ['drupal_ajax', ['format' => 'drupal_ajax', 'mime_type' => 'application/vnd.drupal-ajax']],
       ['html', ['format' => 'html', 'mime_type' => 'text/html']],
     ];
   }
