diff --git a/core/modules/ckeditor/js/plugins/drupalimage/plugin.js b/core/modules/ckeditor/js/plugins/drupalimage/plugin.js
index c481264473cf43bd0d56c0d41de58c377800757a..51550188df2808917d12249e6a497c56bfb05bab 100644
--- a/core/modules/ckeditor/js/plugins/drupalimage/plugin.js
+++ b/core/modules/ckeditor/js/plugins/drupalimage/plugin.js
@@ -29,41 +29,51 @@
           return;
         }
 
-        // Override requiredContent & allowedContent.
+        // First, convert requiredContent & allowedContent from the string
+        // format that image2 uses for both to formats that are better suited
+        // for extending, so that both this basic drupalimage plugin and Drupal
+        // modules can easily extend it.
+        // @see http://docs.ckeditor.com/#!/api/CKEDITOR.filter.allowedContentRules
+        // Mapped from image2's allowedContent. Unlike image2, we don't allow
+        // <figure>, <figcaption>, <div> or <p>  in our downcast, so we omit
+        // those. For the <img> tag, we list all attributes it lists, but omit
+        // the classes, because the listed classes are for alignment, and for
+        // alignment we use the data-align attribute.
+        widgetDefinition.allowedContent = {
+          img: {
+            attributes: {
+              '!src': true,
+              '!alt': true,
+              'width': true,
+              'height': true
+            },
+            classes: {},
+            styles: {}
+          }
+        };
+        // Mapped from image2's requiredContent: "img[src,alt]". This does not
+        // use the object format unlike above, but a CKEDITOR.style instance,
+        // because requiredContent does not support the object format.
+        // @see https://www.drupal.org/node/2585173#comment-10456981
         widgetDefinition.requiredContent = new CKEDITOR.style({
           element: 'img',
           styles: {},
           attributes: {
-            'alt': '',
-            'src': '',
-            'width': '',
-            'height': '',
-            'data-entity-type': '',
-            'data-entity-uuid': ''
+            src: '',
+            alt: ''
           }
         });
-        var allowedContentDefinition = {
-          element: 'img',
-          styles: {},
-          attributes: {
-            '!data-entity-type': '',
-            '!data-entity-uuid': ''
-          }
-        };
-        var imgAttributes = widgetDefinition.allowedContent.img.attributes.split(/\s*,\s*/);
-        for (var i = 0; i < imgAttributes.length; i++) {
-          allowedContentDefinition.attributes[imgAttributes[i]] = '';
-        }
-        if (widgetDefinition.allowedContent.img.classes) {
-          allowedContentDefinition.attributes['class'] = widgetDefinition.allowedContent.img.classes.split(/\s*,\s*/).join(' ');
-        }
-        if (widgetDefinition.allowedContent.img.styles) {
-          var imgStyles = widgetDefinition.allowedContent.img.styles.split(/\s*,\s*/);
-          for (var j = 0; j < imgStyles.length; j++) {
-            allowedContentDefinition.styles[imgStyles[j]] = '';
-          }
-        }
-        widgetDefinition.allowedContent = new CKEDITOR.style(allowedContentDefinition);
+
+        // Extend requiredContent & allowedContent.
+        // CKEDITOR.style is an immutable object: we cannot modify its
+        // definition to extend requiredContent. Hence we get the definition,
+        // modify it, and pass it to a new CKEDITOR.style instance.
+        var requiredContent = widgetDefinition.requiredContent.getDefinition();
+        requiredContent.attributes['data-entity-type'] = '';
+        requiredContent.attributes['data-entity-uuid'] = '';
+        widgetDefinition.requiredContent = new CKEDITOR.style(requiredContent);
+        widgetDefinition.allowedContent.img.attributes['!data-entity-type'] = true;
+        widgetDefinition.allowedContent.img.attributes['!data-entity-uuid'] = true;
 
         // Override downcast(): since we only accept <img> in our upcast method,
         // the element is already correct. We only need to update the element's
diff --git a/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js b/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js
index 9fba103c308bc4b22f2c30f3443679541298746e..44e89dffd8906d93df182a62b1c06a36ba653baa 100644
--- a/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js
+++ b/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js
@@ -50,15 +50,16 @@
           }
         }, true);
 
-        // Override requiredContent & allowedContent.
+        // Extend requiredContent & allowedContent.
+        // CKEDITOR.style is an immutable object: we cannot modify its
+        // definition to extend requiredContent. Hence we get the definition,
+        // modify it, and pass it to a new CKEDITOR.style instance.
         var requiredContent = widgetDefinition.requiredContent.getDefinition();
         requiredContent.attributes['data-align'] = '';
         requiredContent.attributes['data-caption'] = '';
         widgetDefinition.requiredContent = new CKEDITOR.style(requiredContent);
-        var allowedContent = widgetDefinition.allowedContent.getDefinition();
-        allowedContent.attributes['!data-align'] = '';
-        allowedContent.attributes['!data-caption'] = '';
-        widgetDefinition.allowedContent = new CKEDITOR.style(allowedContent);
+        widgetDefinition.allowedContent.img.attributes['!data-align'] = true;
+        widgetDefinition.allowedContent.img.attributes['!data-caption'] = true;
 
         // Override allowedContent setting for the 'caption' nested editable.
         // This must match what caption_filter enforces.
diff --git a/core/modules/ckeditor/js/plugins/drupallink/plugin.js b/core/modules/ckeditor/js/plugins/drupallink/plugin.js
index 2cc9bc10069f5409be8ee9d7804434ea9c35d3fa..2707a947f34c1cd3f9f57f1e74ae350da547a78d 100644
--- a/core/modules/ckeditor/js/plugins/drupallink/plugin.js
+++ b/core/modules/ckeditor/js/plugins/drupallink/plugin.js
@@ -13,14 +13,16 @@
     init: function (editor) {
       // Add the commands for link and unlink.
       editor.addCommand('drupallink', {
-        allowedContent: new CKEDITOR.style({
-          element: 'a',
-          styles: {},
-          attributes: {
-            '!href': '',
-            'target': ''
+        allowedContent: {
+          a: {
+            attributes: {
+              '!href': true,
+              'target': true
+            },
+            classes: {},
+            styles: {}
           }
-        }),
+        },
         requiredContent: new CKEDITOR.style({
           element: 'a',
           styles: {},
@@ -147,13 +149,14 @@
       editor.addCommand('drupalunlink', {
         contextSensitive: 1,
         startDisabled: 1,
-        allowedContent: new CKEDITOR.style({
-          element: 'a',
-          attributes: {
-            '!href': '',
-            'target': ''
+        allowedContent: {
+          a: {
+            attributes: {
+              '!href': true,
+              'target': true
+            }
           }
-        }),
+        },
         requiredContent: new CKEDITOR.style({
           element: 'a',
           attributes: {