diff --git a/includes/media.filter.inc b/includes/media.filter.inc
index 64b72a3..d5f0d87 100644
--- a/includes/media.filter.inc
+++ b/includes/media.filter.inc
@@ -17,7 +17,6 @@ function media_wysiwyg_include_directory($type) {
   switch ($type) {
     case 'plugins':
       return 'wysiwyg_plugins';
-
     break;
   }
 }
@@ -313,12 +312,11 @@ function media_token_to_markup($match, $wysiwyg = FALSE) {
     $fields = media_filter_field_parser($tag_info);
 
     $attributes = is_array($tag_info['attributes']) ? $tag_info['attributes'] : array();
-    $attribute_whitelist = variable_get('media__wysiwyg_allowed_attributes', array('height', 'width', 'hspace', 'vspace', 'border', 'align', 'style', 'class', 'id', 'usemap', 'data-picture-group', 'data-picture-align'));
+    $attribute_whitelist = variable_get('media__wysiwyg_allowed_attributes', _media_wysiwyg_allowed_attributes_default());
     $settings['attributes'] = array_intersect_key($attributes, array_flip($attribute_whitelist));
     $settings['fields'] = $fields;
 
     if (!empty($tag_info['attributes']) && is_array($tag_info['attributes'])) {
-      $attribute_whitelist = variable_get('media__wysiwyg_allowed_attributes', array('height', 'width', 'hspace', 'vspace', 'border', 'align', 'style', 'class', 'id', 'usemap', 'data-picture-group', 'data-picture-align'));
       $settings['attributes'] = array_intersect_key($tag_info['attributes'], array_flip($attribute_whitelist));
       $settings['fields'] = $fields;
 
@@ -379,6 +377,7 @@ function media_token_to_markup($match, $wysiwyg = FALSE) {
     // Display the field elements.
     $element = array();
     $element['content']['file'] = media_get_file_without_label($file, $tag_info['view_mode'], $settings);
+
     // Overwrite or set the file #alt attribute if it has been set in this
     // instance.
     if (!empty($element['content']['file']['#attributes']['alt'])) {
@@ -392,6 +391,7 @@ function media_token_to_markup($match, $wysiwyg = FALSE) {
     field_attach_prepare_view('file', array($file->fid => $file), $tag_info['view_mode']);
     entity_prepare_view('file', array($file->fid => $file));
     $element['content'] += field_attach_view('file', $file, $tag_info['view_mode']);
+
     if (count(element_children($element['content'])) > 1) {
       // Add surrounding divs to group them together.
       // We dont want divs when there are no additional fields to allow files
@@ -404,6 +404,7 @@ function media_token_to_markup($match, $wysiwyg = FALSE) {
       );
     }
   }
+
   drupal_alter('media_token_to_markup', $element, $tag_info, $settings);
   return drupal_render($element);
 }
@@ -668,18 +669,46 @@ function media_get_file_without_label($file, $view_mode, $settings = array()) {
   // support simple formatters that don't do this, set the element attributes to
   // what was requested, but not if the formatter applied its own logic for
   // element attributes.
-  if (!isset($element['#attributes']) && isset($settings['attributes'])) {
-    $element['#attributes'] = $settings['attributes'];
+  if (isset($settings['attributes'])) {
+    if (empty($element['#attributes'])) {
+      $element['#attributes'] = $settings['attributes'];
+    }
 
     // While this function may be called for any file type, images are a common
-    // use-case. theme_image() and theme_image_style() require the 'alt'
-    // attribute to be passed separately from the 'attributes' array (see
-    // http://drupal.org/node/999338). Until that's fixed, implement this
-    // special-case logic. Image formatters using other theme functions are
-    // responsible for their own 'alt' attribute handling. See
-    // theme_media_formatter_large_icon() for an example.
-    if (isset($settings['attributes']['alt']) && !isset($element['#alt']) && isset($element['#theme']) && in_array($element['#theme'], array('image', 'image_style'))) {
-      $element['#alt'] = $settings['attributes']['alt'];
+    // use-case, and image theme functions have their own structures for
+    // render arrays.
+    if (isset($element['#theme'])) {
+      switch ($element['#theme']) {
+        case 'image':
+        case 'image_style':
+          // theme_image() and theme_image_style() require the 'alt' attributes to
+          // be passed separately from the 'attributes' array. (see
+          // http://drupal.org/node/999338). Until that's fixed, implement this
+          // special-case logic. Image formatters using other theme functions are
+          // responsible for their own 'alt' attribute handling. See
+          // theme_media_formatter_large_icon() for an example.
+          if (empty($element['#alt']) && isset($settings['attributes']['alt'])) {
+            $element['#alt'] = $settings['attributes']['alt'];
+          }
+          break;
+
+        case 'image_formatter':
+          // theme_image_formatter() requires the attributes to be
+          // set on the item rather than the element itself.
+          if (empty($element['#item']['attributes'])) {
+            $element['#item']['attributes'] = $settings['attributes'];
+          }
+
+          // theme_image_formatter() also requires alt, title, height, and
+          // width attributes to be set on the item rather than within its
+          // attributes array.
+          foreach (array('alt', 'title', 'width', 'height') as $attr) {
+              if (isset($settings['attributes'][$attr])) {
+                  $element['#item'][$attr] = $settings['attributes'][$attr];
+              }
+          }
+          break;
+      }
     }
   }
 
diff --git a/js/media.filter.js b/js/media.filter.js
index c1b39c9..74c013c 100644
--- a/js/media.filter.js
+++ b/js/media.filter.js
@@ -14,36 +14,40 @@
      * @param content
      */
     replaceTokenWithPlaceholder: function(content) {
-      var tagmap = Drupal.media.filter.ensure_tagmap(),
-        matches = content.match(/\[\[.*?\]\]/g),
-        media_definition,
-        id = 0;
-
-      if (matches) {
-        var i = 1;
-        for (var macro in tagmap) {
-          var index = matches.indexOf(macro);
-          if (index !== -1) {
-            var media_json = macro.replace('[[', '').replace(']]', '');
-
-            // Make sure that the media JSON is valid.
-            try {
-              media_definition = JSON.parse(media_json);
-            }
-            catch (err) {
-              media_definition = null;
-            }
-            if (media_definition) {
-              // Apply attributes.
-              var element = Drupal.media.filter.create_element(tagmap[macro], media_definition);
-              var markup = Drupal.media.filter.outerHTML(element);
-
-              content = content.replace(macro, Drupal.media.filter.getWrapperStart(i) + markup + Drupal.media.filter.getWrapperEnd(i));
-            }
+      var matches = content.match(/\[\[.*?\]\]/g);
+
+      if (!!matches) {
+        for (i in matches) {
+          var match = matches[i];
+
+          // Check if the macro exists in the tagmap. This ensures backwards
+          // compatibility with existing media and is moderately more efficient
+          // than re-building the element.
+          var media = Drupal.settings.tagmap[match];
+          var media_json = match.replace('[[', '').replace(']]', '');
+
+          // Ensure that the media JSON is valid.
+          try {
+            var media_definition = JSON.parse(media_json);
+          }
+          catch (err) {
+            // TODO: Error logging.
+          }
+
+          if (!media) {
+            // Create the element if it has changed from the tagmap.
+            media = document.createElement(media_definition.tagName);
+            media.src = media_definition.src;
           }
-          i++;
+
+          // Apply attributes.
+          var element = Drupal.media.filter.createElement(media, media_definition);
+          var markup = Drupal.media.filter.safeMarkup(element);
+
+          content = content.replace(match, markup);
         }
       }
+
       return content;
     },
 
@@ -52,29 +56,25 @@
      * @param content
      */
     replacePlaceholderWithToken: function(content) {
-      var tagmap = Drupal.media.filter.ensure_tagmap();
-      var i = 1;
-      for (var macro in tagmap) {
-        var startTag = Drupal.media.filter.getWrapperStart(i), endTag = Drupal.media.filter.getWrapperEnd(i);
-        var startPos = content.indexOf(startTag), endPos = content.indexOf(endTag);
-        if (startPos !== -1 && endPos !== -1) {
-          // If the placeholder wrappers are empty, remove the macro too.
-          if (endPos - startPos - startTag.length === 0) {
-            macro = '';
-          }
-          content = content.substr(0, startPos) + macro + content.substr(endPos + (new String(endTag)).length);
-        }
-        i++;
-      }
-      return content;
-    },
+      Drupal.settings.tagmap = [];
 
-    getWrapperStart: function(i) {
-      return '<!--MEDIA-WRAPPER-START-' + i + '-->';
-    },
+      // Convert all xhtml markup to html for reliable matching/replacing.
+      content = content.replace(/[\s]\/\>/g, '>');
 
-    getWrapperEnd: function(i) {
-      return '<!--MEDIA-WRAPPER-END-' + i + '-->';
+      // Re-build the macros in case any element has changed in the editor.
+      $('.media-element', content).each(function(i, element) {
+        var markup = Drupal.media.filter.safeMarkup($(element));
+          macro = Drupal.media.filter.createMacro($(element));
+
+        // Store the macro => html for more efficient rendering in
+        // replaceTokenWithPlaceholder().
+        Drupal.settings.tagmap[macro] = markup;
+
+        // Replace the media element with its macro.
+        content = content.replace(markup, macro);
+      });
+
+      return content;
     },
 
     /**
@@ -87,32 +87,53 @@
      * @return The registered element.
      */
     registerNewElement: function (formattedMedia, fid) {
-      var element = Drupal.media.filter.create_element(formattedMedia.html, {
+      var element = Drupal.media.filter.createElement(formattedMedia.html, {
         fid: fid,
         view_mode: formattedMedia.type,
         attributes: formattedMedia.options
       });
 
-      var markup = Drupal.media.filter.outerHTML(element),
-        macro = Drupal.media.filter.create_macro(element);
+      var markup = Drupal.media.filter.safeMarkup(element),
+        macro = Drupal.media.filter.createMacro(element);
 
       // Store macro/markup pair in the tagmap.
       Drupal.media.filter.ensure_tagmap();
       Drupal.settings.tagmap[macro] = markup;
 
-      return element;
+      return markup;
+    },
+
+    /**
+     * Returns alt and title field values for use as html attributes.
+     *
+     * @param options (array) Options passed through a popup form submission.
+     */
+    parseAttributeFields: function(options) {
+      var attributes = [];
+
+      for (field in options) {
+        if (field.match('image_alt')) {
+          attributes['alt'] = options[field];
+        }
+
+        if (field.match('image_title')) {
+          attributes['title'] = options[field];
+        }
+      }
+
+      return attributes;
     },
 
     /**
-     * Serializes file information as a url-encoded JSON object and stores it as a
-     * data attribute on the html element.
+     * Serializes file information as a url-encoded JSON object and stores it
+     * as a data attribute on the html element.
      *
      * @param html (string)
      *    A html element to be used to represent the inserted media element.
      * @param info (object)
      *    A object containing the media file information (fid, view_mode, etc).
      */
-    create_element: function (html, info) {
+    createElement: function (html, info) {
       if ($('<div></div>').append(html).text().length === html.length) {
         // Element is not an html tag. Surround it in a span element
         // so we can pass the file attributes.
@@ -138,7 +159,7 @@
       // Adding media-element class so we can find markup element later.
       var classes = ['media-element'];
 
-      if(info.view_mode){
+      if (info.view_mode) {
         classes.push('file-' + info.view_mode.replace(/_/g, '-'));
       }
       element.addClass(classes.join(' '));
@@ -152,8 +173,8 @@
      * @param element (jQuery object)
      *    A media element with attached serialized file info.
      */
-    create_macro: function (element) {
-      var file_info = Drupal.media.filter.extract_file_info(element);
+    createMacro: function (element) {
+      var file_info = Drupal.media.filter.extractFileInfo(element);
       if (file_info) {
         return '[[' + JSON.stringify(file_info) + ']]';
       }
@@ -166,7 +187,7 @@
      * @param element (jQuery object)
      *    A media element with attached serialized file info.
      */
-    extract_file_info: function (element) {
+    extractFileInfo: function (element) {
       var file_json = $.data(element, 'file_info') || element.data('file_info'),
         file_info,
         value;
@@ -188,6 +209,10 @@
           }
         });
         delete(file_info.attributes['data-file_info']);
+
+        // Set tagName and src to rebuild later.
+        file_info.tagName = element[0].tagName;
+        file_info.src = element[0].src;
       }
 
       return file_info;
@@ -199,7 +224,28 @@
      * @param element (jQuery object)
      */
     outerHTML: function (element) {
-      return $('<div>').append(element.eq(0).clone()).html();
+      return element[0].outerHTML || $('<div>').append(element.eq(0).clone()).html();
+    },
+
+    /**
+     * Parses an element's markup and returns it with its attributes in
+     * alphabetical order. Use as a wrapper when outerHTML is needed.
+     *
+     * @param element (jQuery object) The element to modify.
+     *
+     * @see http://dev.ckeditor.com/ticket/1810
+     */
+    safeMarkup: function(element) {
+      var markup = Drupal.media.filter.outerHTML(element);
+
+      // Parse out and sort the element's attributes.
+      var attrs = markup.match(/([^\s]+?=".*?")/gi).sort();
+
+      // Create the media element markup with the sorted attributes.
+      var media = document.createElement($(markup)[0].tagName);
+      media = Drupal.media.filter.outerHTML($(media)).replace(/\>/, ' ' + attrs.join(' ') + '>');
+
+      return media;
     },
 
     /**
@@ -214,8 +260,8 @@
      */
     getWysiwygHTML: function (element) {
       // Create the markup and the macro.
-      var markup = Drupal.media.filter.outerHTML(element),
-        macro = Drupal.media.filter.create_macro(element);
+      var markup = Drupal.media.filter.safeMarkup(element),
+        macro = Drupal.media.filter.createMacro(element);
 
       // Store macro/markup in the tagmap.
       Drupal.media.filter.ensure_tagmap();
@@ -227,7 +273,7 @@
 
       // Return the wrapped html code to insert in an editor and use it with
       // replacePlaceholderWithToken()
-      return Drupal.media.filter.getWrapperStart(i) + markup + Drupal.media.filter.getWrapperEnd(i);
+      return markup;
     },
 
     /**
@@ -244,7 +290,7 @@
      */
     allowed_attributes: function () {
       Drupal.settings.wysiwyg_allowed_attributes = Drupal.settings.wysiwyg_allowed_attributes || ['height', 'width', 'hspace', 'vspace', 'border', 'align', 'style', 'alt', 'title', 'class', 'id', 'usemap'];
-      return Drupal.settings.wysiwyg_allowed_attributes;
+      return Drupal.settings.wysiwyg_allowed_attributes.sort();
     }
   }
 })(jQuery);
diff --git a/js/wysiwyg-media.js b/js/wysiwyg-media.js
index ac5bf4d..44c6508 100644
--- a/js/wysiwyg-media.js
+++ b/js/wysiwyg-media.js
@@ -42,7 +42,7 @@ Drupal.wysiwyg.plugins.media = {
       var insert = new InsertMedia(instanceId);
       if (this.isNode(data.node)) {
         // Change the view mode for already-inserted media.
-        var media_file = Drupal.media.filter.extract_file_info($(data.node));
+        var media_file = Drupal.media.filter.extractFileInfo($(data.node));
         insert.onSelect([media_file]);
       }
       else {
@@ -109,12 +109,15 @@ InsertMedia.prototype = {
    * tagmap.
    */
   insert: function (formatted_media) {
-    var element = Drupal.media.filter.create_element(formatted_media.html, {
+    var attributes = Drupal.media.filter.parseAttributeFields(formatted_media.options);
+
+    var element = Drupal.media.filter.createElement(formatted_media.html, {
           fid: this.mediaFile.fid,
           view_mode: formatted_media.type,
-          attributes: formatted_media.options,
+          attributes: $.extend(this.mediaFile.attributes, attributes),
           fields: formatted_media.options
         });
+
     // Get the markup and register it for the macro / placeholder handling.
     var markup = Drupal.media.filter.getWysiwygHTML(element);
 
@@ -143,8 +146,8 @@ function ensure_tagmap () {
  *
  * @deprecated
  */
-function create_element (html, info) {
-  return Drupal.media.filter.create_element(html, info);
+function createElement (html, info) {
+  return Drupal.media.filter.createElement(html, info);
 }
 
 /**
@@ -155,8 +158,8 @@ function create_element (html, info) {
  *
  * @deprecated
  */
-function create_macro (element) {
-  return Drupal.media.filter.create_macro(element);
+function createMacro (element) {
+  return Drupal.media.filter.createMacro(element);
 }
 
 /**
@@ -167,8 +170,8 @@ function create_macro (element) {
  *
  * @deprecated
  */
-function extract_file_info (element) {
-  return Drupal.media.filter.extract_file_info(element);
+function extractFileInfo (element) {
+  return Drupal.media.filter.extractFileInfo(element);
 }
 
 /**
diff --git a/media.module b/media.module
index 7435518..18058f5 100644
--- a/media.module
+++ b/media.module
@@ -1300,3 +1300,27 @@ function _media_get_migratable_file_types() {
 
   return array_diff($types, $enabled_types);
 }
+
+/**
+ * Returns the default set of allowed attributes for use with WYSIWYG.
+ *
+ * @return Array of whitelisted attributes.
+ */
+function _media_wysiwyg_allowed_attributes_default() {
+  return array(
+    'alt',
+    'title',
+    'height',
+    'width',
+    'hspace',
+    'vspace',
+    'border',
+    'align',
+    'style',
+    'class',
+    'id',
+    'usemap',
+    'data-picture-group',
+    'data-picture-align',
+  );
+}
diff --git a/wysiwyg_plugins/media.inc b/wysiwyg_plugins/media.inc
index d90b79f..611ee60 100644
--- a/wysiwyg_plugins/media.inc
+++ b/wysiwyg_plugins/media.inc
@@ -68,7 +68,7 @@ function media_include_browser_js() {
     }
   }
   // Add wysiwyg-specific settings.
-  $settings = array('wysiwyg_allowed_attributes' => variable_get('media__wysiwyg_allowed_attributes', array('height', 'width', 'hspace', 'vspace', 'border', 'align', 'style', 'class', 'id', 'usemap', 'data-picture-group', 'data-picture-align')));
+  $settings = array('wysiwyg_allowed_attributes' => variable_get('media__wysiwyg_allowed_attributes', _media_wysiwyg_allowed_attributes_default()));
   drupal_add_js(array('media' => $settings), 'setting');
 }
 
