diff --git a/core/misc/active-link.js b/core/misc/active-link.js
index 6054faa..5bd12eb 100644
--- a/core/misc/active-link.js
+++ b/core/misc/active-link.js
@@ -5,59 +5,59 @@
 
 (function (Drupal, drupalSettings) {
 
-"use strict";
+  "use strict";
+
+  /**
+   * Append active class.
+   *
+   * The link is only active if its path corresponds to the current path, the
+   * language of the linked path is equal to the current language, and if the
+   * query parameters of the link equal those of the current request, since the
+   * same request with different query parameters may yield a different page
+   * (e.g. pagers, exposed View filters).
+   *
+   * Does not discriminate based on element type, so allows you to set the active
+   * class on any element: a, li…
+   */
+  Drupal.behaviors.activeLinks = {
+    attach: function (context) {
+      // Start by finding all potentially active links.
+      var path = drupalSettings.path;
+      var queryString = JSON.stringify(path.currentQuery);
+      var querySelector = path.currentQuery ? "[data-drupal-link-query='" + queryString + "']" : ':not([data-drupal-link-query])';
+      var originalSelectors = ['[data-drupal-link-system-path="' + path.currentPath + '"]'];
+      var selectors;
+
+      // If this is the front page, we have to check for the <front> path as well.
+      if (path.isFront) {
+        originalSelectors.push('[data-drupal-link-system-path="<front>"]');
+      }
 
-/**
- * Append active class.
- *
- * The link is only active if its path corresponds to the current path, the
- * language of the linked path is equal to the current language, and if the
- * query parameters of the link equal those of the current request, since the
- * same request with different query parameters may yield a different page
- * (e.g. pagers, exposed View filters).
- *
- * Does not discriminate based on element type, so allows you to set the active
- * class on any element: a, li…
- */
-Drupal.behaviors.activeLinks = {
-  attach: function (context) {
-    // Start by finding all potentially active links.
-    var path = drupalSettings.path;
-    var queryString = JSON.stringify(path.currentQuery);
-    var querySelector = path.currentQuery ? "[data-drupal-link-query='" + queryString + "']" : ':not([data-drupal-link-query])';
-    var originalSelectors = ['[data-drupal-link-system-path="' + path.currentPath + '"]'];
-    var selectors;
-
-    // If this is the front page, we have to check for the <front> path as well.
-    if (path.isFront) {
-      originalSelectors.push('[data-drupal-link-system-path="<front>"]');
-    }
+      // Add language filtering.
+      selectors = [].concat(
+        // Links without any hreflang attributes (most of them).
+        originalSelectors.map(function (selector) { return selector + ':not([hreflang])'; }),
+        // Links with hreflang equals to the current language.
+        originalSelectors.map(function (selector) { return selector + '[hreflang="' + path.currentLanguage + '"]'; })
+      );
 
-    // Add language filtering.
-    selectors = [].concat(
-      // Links without any hreflang attributes (most of them).
-      originalSelectors.map(function (selector) { return selector + ':not([hreflang])';}),
-      // Links with hreflang equals to the current language.
-      originalSelectors.map(function (selector) { return selector + '[hreflang="' + path.currentLanguage + '"]';})
-    );
-
-    // Add query string selector for pagers, exposed filters.
-    selectors = selectors.map(function (current) { return current + querySelector; });
-
-    // Query the DOM.
-    var activeLinks = context.querySelectorAll(selectors.join(','));
-    for (var i = 0, il = activeLinks.length; i < il; i += 1) {
-      activeLinks[i].classList.add('active');
-    }
-  },
-  detach: function (context, settings, trigger) {
-    if (trigger === 'unload') {
-      var activeLinks = context.querySelectorAll('[data-drupal-link-system-path].active');
+      // Add query string selector for pagers, exposed filters.
+      selectors = selectors.map(function (current) { return current + querySelector; });
+
+      // Query the DOM.
+      var activeLinks = context.querySelectorAll(selectors.join(','));
       for (var i = 0, il = activeLinks.length; i < il; i += 1) {
-        activeLinks[i].classList.remove('active');
+        activeLinks[i].classList.add('active');
+      }
+    },
+    detach: function (context, settings, trigger) {
+      if (trigger === 'unload') {
+        var activeLinks = context.querySelectorAll('[data-drupal-link-system-path].active');
+        for (var i = 0, il = activeLinks.length; i < il; i += 1) {
+          activeLinks[i].classList.remove('active');
+        }
       }
     }
-  }
-};
+  };
 
 })(Drupal, drupalSettings);
diff --git a/core/misc/ajax.js b/core/misc/ajax.js
index 4abb652..09621b2 100644
--- a/core/misc/ajax.js
+++ b/core/misc/ajax.js
@@ -1,738 +1,738 @@
 (function ($, window, Drupal, drupalSettings) {
 
-"use strict";
-
-/**
- * Provides Ajax page updating via jQuery $.ajax (Asynchronous JavaScript and XML).
- *
- * Ajax is a method of making a request via JavaScript while viewing an HTML
- * page. The request returns an array of commands encoded in JSON, which is
- * then executed to make any changes that are necessary to the page.
- *
- * Drupal uses this file to enhance form elements with #ajax['path'] and
- * #ajax['wrapper'] properties. If set, this file will automatically be included
- * to provide Ajax capabilities.
- */
-
-Drupal.ajax = Drupal.ajax || {};
-
-/**
- * Attaches the Ajax behavior to each Ajax form element.
- */
-Drupal.behaviors.AJAX = {
-  attach: function (context, settings) {
-
-    function loadAjaxBehavior(base) {
-      var element_settings = settings.ajax[base];
-      if (typeof element_settings.selector === 'undefined') {
-        element_settings.selector = '#' + base;
+  "use strict";
+
+  /**
+   * Provides Ajax page updating via jQuery $.ajax (Asynchronous JavaScript and XML).
+   *
+   * Ajax is a method of making a request via JavaScript while viewing an HTML
+   * page. The request returns an array of commands encoded in JSON, which is
+   * then executed to make any changes that are necessary to the page.
+   *
+   * Drupal uses this file to enhance form elements with #ajax['path'] and
+   * #ajax['wrapper'] properties. If set, this file will automatically be included
+   * to provide Ajax capabilities.
+   */
+
+  Drupal.ajax = Drupal.ajax || {};
+
+  /**
+   * Attaches the Ajax behavior to each Ajax form element.
+   */
+  Drupal.behaviors.AJAX = {
+    attach: function (context, settings) {
+
+      function loadAjaxBehavior(base) {
+        var element_settings = settings.ajax[base];
+        if (typeof element_settings.selector === 'undefined') {
+          element_settings.selector = '#' + base;
+        }
+        $(element_settings.selector).once('drupal-ajax', function () {
+          element_settings.element = this;
+          Drupal.ajax[element_settings.selector] = new Drupal.ajax(base, this, element_settings);
+        });
       }
-      $(element_settings.selector).once('drupal-ajax', function() {
-        element_settings.element = this;
-        Drupal.ajax[element_settings.selector] = new Drupal.ajax(base, this, element_settings);
-      });
-    }
 
-    // Load all Ajax behaviors specified in the settings.
-    for (var base in settings.ajax) {
-      if (settings.ajax.hasOwnProperty(base)) {
-        loadAjaxBehavior(base);
+      // Load all Ajax behaviors specified in the settings.
+      for (var base in settings.ajax) {
+        if (settings.ajax.hasOwnProperty(base)) {
+          loadAjaxBehavior(base);
+        }
       }
-    }
 
-    // Bind Ajax behaviors to all items showing the class.
-    $('.use-ajax').once('ajax', function () {
-      var element_settings = {};
-      // Clicked links look better with the throbber than the progress bar.
-      element_settings.progress = { 'type': 'throbber' };
+      // Bind Ajax behaviors to all items showing the class.
+      $('.use-ajax').once('ajax', function () {
+        var element_settings = {};
+        // Clicked links look better with the throbber than the progress bar.
+        element_settings.progress = { 'type': 'throbber' };
+
+        // For anchor tags, these will go to the target of the anchor rather
+        // than the usual location.
+        if ($(this).attr('href')) {
+          element_settings.url = $(this).attr('href');
+          element_settings.event = 'click';
+        }
+        element_settings.accepts = $(this).data('accepts');
+        element_settings.dialog = $(this).data('dialog-options');
+        var base = $(this).attr('id');
+        Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
+      });
 
-      // For anchor tags, these will go to the target of the anchor rather
-      // than the usual location.
-      if ($(this).attr('href')) {
-        element_settings.url = $(this).attr('href');
+      // This class means to submit the form to the action using Ajax.
+      $('.use-ajax-submit').once('ajax', function () {
+        var element_settings = {};
+
+        // Ajax submits specified in this manner automatically submit to the
+        // normal form action.
+        element_settings.url = $(this.form).attr('action');
+        // Form submit button clicks need to tell the form what was clicked so
+        // it gets passed in the POST request.
+        element_settings.setClick = true;
+        // Form buttons use the 'click' event rather than mousedown.
         element_settings.event = 'click';
-      }
-      element_settings.accepts = $(this).data('accepts');
-      element_settings.dialog = $(this).data('dialog-options');
-      var base = $(this).attr('id');
-      Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
-    });
+        // Clicked form buttons look better with the throbber than the progress bar.
+        element_settings.progress = { 'type': 'throbber' };
 
-    // This class means to submit the form to the action using Ajax.
-    $('.use-ajax-submit').once('ajax', function () {
-      var element_settings = {};
-
-      // Ajax submits specified in this manner automatically submit to the
-      // normal form action.
-      element_settings.url = $(this.form).attr('action');
-      // Form submit button clicks need to tell the form what was clicked so
-      // it gets passed in the POST request.
-      element_settings.setClick = true;
-      // Form buttons use the 'click' event rather than mousedown.
-      element_settings.event = 'click';
-      // Clicked form buttons look better with the throbber than the progress bar.
-      element_settings.progress = { 'type': 'throbber' };
-
-      var base = $(this).attr('id');
-      Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
-    });
-  }
-};
-
-/**
- * Extends Error to provide handling for Errors in AJAX
- */
-Drupal.AjaxError = function(xmlhttp, uri) {
-
-  var statusCode, statusText, pathText, responseText, readyStateText;
-  if (xmlhttp.status) {
-    statusCode = "\n" + Drupal.t("An AJAX HTTP error occurred.") +  "\n" + Drupal.t("HTTP Result Code: !status", {'!status': xmlhttp.status});
-  }
-  else {
-    statusCode = "\n" + Drupal.t("An AJAX HTTP request terminated abnormally.");
-  }
-  statusCode += "\n" + Drupal.t("Debugging information follows.");
-  pathText = "\n" + Drupal.t("Path: !uri", {'!uri': uri} );
-  statusText = '';
-  // In some cases, when statusCode === 0, xmlhttp.statusText may not be defined.
-  // Unfortunately, testing for it with typeof, etc, doesn't seem to catch that
-  // and the test causes an exception. So we need to catch the exception here.
-  try {
-    statusText = "\n" + Drupal.t("StatusText: !statusText", {'!statusText': $.trim(xmlhttp.statusText)});
-  }
-  catch (e) {}
-
-  responseText = '';
-  // Again, we don't have a way to know for sure whether accessing
-  // xmlhttp.responseText is going to throw an exception. So we'll catch it.
-  try {
-    responseText = "\n" + Drupal.t("ResponseText: !responseText", {'!responseText': $.trim(xmlhttp.responseText) } );
-  } catch (e) {}
-
-  // Make the responseText more readable by stripping HTML tags and newlines.
-  responseText = responseText.replace(/<("[^"]*"|'[^']*'|[^'">])*>/gi,"");
-  responseText = responseText.replace(/[\n]+\s+/g,"\n");
-
-  // We don't need readyState except for status == 0.
-  readyStateText = xmlhttp.status === 0 ? ("\n" + Drupal.t("ReadyState: !readyState", {'!readyState': xmlhttp.readyState})) : "";
-
-  this.message = statusCode + pathText + statusText + responseText + readyStateText;
-  this.name = 'AjaxError';
-};
-
-Drupal.AjaxError.prototype = new Error();
-Drupal.AjaxError.prototype.constructor = Drupal.AjaxError;
-
-/**
- * Ajax object.
- *
- * All Ajax objects on a page are accessible through the global Drupal.ajax
- * object and are keyed by the submit button's ID. You can access them from
- * your module's JavaScript file to override properties or functions.
- *
- * For example, if your Ajax enabled button has the ID 'edit-submit', you can
- * redefine the function that is called to insert the new content like this
- * (inside a Drupal.behaviors attach block):
- * @code
- *    Drupal.behaviors.myCustomAJAXStuff = {
- *      attach: function (context, settings) {
- *        Drupal.ajax['edit-submit'].commands.insert = function (ajax, response, status) {
- *          new_content = $(response.data);
- *          $('#my-wrapper').append(new_content);
- *          alert('New content was appended to #my-wrapper');
- *        }
- *      }
- *    };
- * @endcode
- */
-Drupal.ajax = function (base, element, element_settings) {
-  var defaults = {
-    event: 'mousedown',
-    keypress: true,
-    selector: '#' + base,
-    effect: 'none',
-    speed: 'none',
-    method: 'replaceWith',
-    progress: {
-      type: 'throbber',
-      message: Drupal.t('Please wait...')
-    },
-    submit: {
-      'js': true
+        var base = $(this).attr('id');
+        Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
+      });
     }
   };
 
-  $.extend(this, defaults, element_settings);
-
-  this.commands = new Drupal.AjaxCommands();
-
-  // @todo Remove this after refactoring the PHP code to:
-  //   - Call this 'selector'.
-  //   - Include the '#' for ID-based selectors.
-  //   - Support non-ID-based selectors.
-  if (this.wrapper) {
-    this.wrapper = '#' + this.wrapper;
-  }
-
-  this.element = element;
-  this.element_settings = element_settings;
-
-  // If there isn't a form, jQuery.ajax() will be used instead, allowing us to
-  // bind Ajax to links as well.
-  if (this.element.form) {
-    this.$form = $(this.element.form);
-  }
-
-  // If no Ajax callback URL was given, use the link href or form action.
-  if (!this.url) {
-    if ($(element).is('a')) {
-      this.url = $(element).attr('href');
-    }
-    else if (element.form) {
-      this.url = this.$form.attr('action');
-
-      // @todo If there's a file input on this form, then jQuery will submit the
-      //   AJAX response with a hidden Iframe rather than the XHR object. If the
-      //   response to the submission is an HTTP redirect, then the Iframe will
-      //   follow it, but the server won't content negotiate it correctly,
-      //   because there won't be an ajax_iframe_upload POST variable. Until we
-      //   figure out a work around to this problem, we prevent AJAX-enabling
-      //   elements that submit to the same URL as the form when there's a file
-      //   input. For example, this means the Delete button on the edit form of
-      //   an Article node doesn't open its confirmation form in a dialog.
-      if (this.$form.find(':file').length) {
-        return;
-      }
+  /**
+   * Extends Error to provide handling for Errors in AJAX
+   */
+  Drupal.AjaxError = function (xmlhttp, uri) {
+
+    var statusCode, statusText, pathText, responseText, readyStateText;
+    if (xmlhttp.status) {
+      statusCode = "\n" + Drupal.t("An AJAX HTTP error occurred.") + "\n" + Drupal.t("HTTP Result Code: !status", {'!status': xmlhttp.status});
     }
-  }
-
-  // Replacing 'nojs' with 'ajax' in the URL allows for an easy method to let
-  // the server detect when it needs to degrade gracefully.
-  // There are four scenarios to check for:
-  // 1. /nojs/
-  // 2. /nojs$ - The end of a URL string.
-  // 3. /nojs? - Followed by a query (e.g. path/nojs?destination=foobar).
-  // 4. /nojs# - Followed by a fragment (e.g.: path/nojs#myfragment).
-  this.url = this.url.replace(/\/nojs(\/|$|\?|#)/g, '/ajax$1');
-
-  // Set the options for the ajaxSubmit function.
-  // The 'this' variable will not persist inside of the options object.
-  var ajax = this;
-  ajax.options = {
-    url: ajax.url,
-    data: ajax.submit,
-    beforeSerialize: function (element_settings, options) {
-      return ajax.beforeSerialize(element_settings, options);
-    },
-    beforeSubmit: function (form_values, element_settings, options) {
-      ajax.ajaxing = true;
-      return ajax.beforeSubmit(form_values, element_settings, options);
-    },
-    beforeSend: function (xmlhttprequest, options) {
-      ajax.ajaxing = true;
-      return ajax.beforeSend(xmlhttprequest, options);
-    },
-    success: function (response, status) {
-      // Sanity check for browser support (object expected).
-      // When using iFrame uploads, responses must be returned as a string.
-      if (typeof response === 'string') {
-        response = $.parseJSON(response);
-      }
-      return ajax.success(response, status);
-    },
-    complete: function (response, status) {
-      ajax.ajaxing = false;
-      if (status === 'error' || status === 'parsererror') {
-        return ajax.error(response, ajax.url);
-      }
-    },
-    dataType: 'json',
-    accepts: {
-      json: element_settings.accepts || 'application/vnd.drupal-ajax'
-    },
-    type: 'POST'
+    else {
+      statusCode = "\n" + Drupal.t("An AJAX HTTP request terminated abnormally.");
+    }
+    statusCode += "\n" + Drupal.t("Debugging information follows.");
+    pathText = "\n" + Drupal.t("Path: !uri", {'!uri': uri});
+    statusText = '';
+    // In some cases, when statusCode === 0, xmlhttp.statusText may not be defined.
+    // Unfortunately, testing for it with typeof, etc, doesn't seem to catch that
+    // and the test causes an exception. So we need to catch the exception here.
+    try {
+      statusText = "\n" + Drupal.t("StatusText: !statusText", {'!statusText': $.trim(xmlhttp.statusText)});
+    }
+    catch (e) {}
+
+    responseText = '';
+    // Again, we don't have a way to know for sure whether accessing
+    // xmlhttp.responseText is going to throw an exception. So we'll catch it.
+    try {
+      responseText = "\n" + Drupal.t("ResponseText: !responseText", {'!responseText': $.trim(xmlhttp.responseText) });
+    } catch (e) {}
+
+    // Make the responseText more readable by stripping HTML tags and newlines.
+    responseText = responseText.replace(/<("[^"]*"|'[^']*'|[^'">])*>/gi, "");
+    responseText = responseText.replace(/[\n]+\s+/g, "\n");
+
+    // We don't need readyState except for status == 0.
+    readyStateText = xmlhttp.status === 0 ? ("\n" + Drupal.t("ReadyState: !readyState", {'!readyState': xmlhttp.readyState})) : "";
+
+    this.message = statusCode + pathText + statusText + responseText + readyStateText;
+    this.name = 'AjaxError';
   };
 
-  if (element_settings.dialog) {
-    ajax.options.data.dialogOptions = element_settings.dialog;
-  }
-
-  // Bind the ajaxSubmit function to the element event.
-  $(ajax.element).on(element_settings.event, function (event) {
-    return ajax.eventResponse(this, event);
-  });
-
-  // If necessary, enable keyboard submission so that Ajax behaviors
-  // can be triggered through keyboard input as well as e.g. a mousedown
-  // action.
-  if (element_settings.keypress) {
-    $(ajax.element).on('keypress', function (event) {
-      return ajax.keypressResponse(this, event);
-    });
-  }
-
-  // If necessary, prevent the browser default action of an additional event.
-  // For example, prevent the browser default action of a click, even if the
-  // AJAX behavior binds to mousedown.
-  if (element_settings.prevent) {
-    $(ajax.element).on(element_settings.prevent, false);
-  }
-};
-
-/**
- * Handle a key press.
- *
- * The Ajax object will, if instructed, bind to a key press response. This
- * will test to see if the key press is valid to trigger this event and
- * if it is, trigger it for us and prevent other keypresses from triggering.
- * In this case we're handling RETURN and SPACEBAR keypresses (event codes 13
- * and 32. RETURN is often used to submit a form when in a textfield, and
- * SPACE is often used to activate an element without submitting.
- */
-Drupal.ajax.prototype.keypressResponse = function (element, event) {
-  // Create a synonym for this to reduce code confusion.
-  var ajax = this;
-
-  // Detect enter key and space bar and allow the standard response for them,
-  // except for form elements of type 'text', 'tel', 'number' and 'textarea',
-  // where the spacebar activation causes inappropriate activation if
-  // #ajax['keypress'] is TRUE. On a text-type widget a space should always be a
-  // space.
-  if (event.which === 13 || (event.which === 32 && element.type !== 'text' &&
-      element.type !== 'textarea' && element.type !== 'tel' && element.type !== 'number')) {
-    event.preventDefault();
-    event.stopPropagation();
-    $(ajax.element_settings.element).trigger(ajax.element_settings.event);
-  }
-};
-
-/**
- * Handle an event that triggers an Ajax response.
- *
- * When an event that triggers an Ajax response happens, this method will
- * perform the actual Ajax call. It is bound to the event using
- * bind() in the constructor, and it uses the options specified on the
- * ajax object.
- */
-Drupal.ajax.prototype.eventResponse = function (element, event) {
-  event.preventDefault();
-  event.stopPropagation();
-
-  // Create a synonym for this to reduce code confusion.
-  var ajax = this;
-
-  // Do not perform another ajax command if one is already in progress.
-  if (ajax.ajaxing) {
-    return;
-  }
-
-  try {
-    if (ajax.$form) {
-      // If setClick is set, we must set this to ensure that the button's
-      // value is passed.
-      if (ajax.setClick) {
-        // Mark the clicked button. 'form.clk' is a special variable for
-        // ajaxSubmit that tells the system which element got clicked to
-        // trigger the submit. Without it there would be no 'op' or
-        // equivalent.
-        element.form.clk = element;
-      }
+  Drupal.AjaxError.prototype = new Error();
+  Drupal.AjaxError.prototype.constructor = Drupal.AjaxError;
 
-      ajax.$form.ajaxSubmit(ajax.options);
-    }
-    else {
-      ajax.beforeSerialize(ajax.element, ajax.options);
-      $.ajax(ajax.options);
-    }
-  }
-  catch (e) {
-    // Unset the ajax.ajaxing flag here because it won't be unset during
-    // the complete response.
-    ajax.ajaxing = false;
-    window.alert("An error occurred while attempting to process " + ajax.options.url + ": " + e.message);
-  }
-};
-
-/**
- * Handler for the form serialization.
- *
- * Runs before the beforeSend() handler (see below), and unlike that one, runs
- * before field data is collected.
- */
-Drupal.ajax.prototype.beforeSerialize = function (element, options) {
-  // Allow detaching behaviors to update field values before collecting them.
-  // This is only needed when field values are added to the POST data, so only
-  // when there is a form such that this.$form.ajaxSubmit() is used instead of
-  // $.ajax(). When there is no form and $.ajax() is used, beforeSerialize()
-  // isn't called, but don't rely on that: explicitly check this.$form.
-  if (this.$form) {
-    var settings = this.settings || drupalSettings;
-    Drupal.detachBehaviors(this.$form.get(0), settings, 'serialize');
-  }
-
-  // Prevent duplicate HTML ids in the returned markup.
-  // @see drupal_html_id()
-  var ids = document.querySelectorAll('[id]');
-  var ajaxHtmlIds = [];
-  for (var i = 0, il = ids.length; i < il; i++) {
-    ajaxHtmlIds.push(ids[i].id);
-  }
-  // Join IDs to minimize request size.
-  options.data.ajax_html_ids = ajaxHtmlIds.join(' ');
-
-  // Allow Drupal to return new JavaScript and CSS files to load without
-  // returning the ones already loaded.
-  // @see \Drupal\Core\Theme\AjaxBasePageNegotiator
-  // @see drupal_get_css()
-  // @see drupal_get_js()
-  var pageState = drupalSettings.ajaxPageState;
-  options.data['ajax_page_state[theme]'] = pageState.theme;
-  options.data['ajax_page_state[theme_token]'] = pageState.theme_token;
-  for (var cssFile in pageState.css) {
-    if (pageState.css.hasOwnProperty(cssFile)) {
-      options.data['ajax_page_state[css][' + cssFile + ']'] = 1;
-    }
-  }
-  for (var jsFile in pageState.js) {
-    if (pageState.js.hasOwnProperty(jsFile)) {
-      options.data['ajax_page_state[js][' + jsFile + ']'] = 1;
-    }
-  }
-};
-
-/**
- * Modify form values prior to form submission.
- */
-Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) {
-  // This function is left empty to make it simple to override for modules
-  // that wish to add functionality here.
-};
-
-/**
- * Prepare the Ajax request before it is sent.
- */
-Drupal.ajax.prototype.beforeSend = function (xmlhttprequest, options) {
-  // For forms without file inputs, the jQuery Form plugin serializes the form
-  // values, and then calls jQuery's $.ajax() function, which invokes this
-  // handler. In this circumstance, options.extraData is never used. For forms
-  // with file inputs, the jQuery Form plugin uses the browser's normal form
-  // submission mechanism, but captures the response in a hidden IFRAME. In this
-  // circumstance, it calls this handler first, and then appends hidden fields
-  // to the form to submit the values in options.extraData. There is no simple
-  // way to know which submission mechanism will be used, so we add to extraData
-  // regardless, and allow it to be ignored in the former case.
-  if (this.$form) {
-    options.extraData = options.extraData || {};
-
-    // Let the server know when the IFRAME submission mechanism is used. The
-    // server can use this information to wrap the JSON response in a TEXTAREA,
-    // as per http://jquery.malsup.com/form/#file-upload.
-    options.extraData.ajax_iframe_upload = '1';
-
-    // The triggering element is about to be disabled (see below), but if it
-    // contains a value (e.g., a checkbox, textfield, select, etc.), ensure that
-    // value is included in the submission. As per above, submissions that use
-    // $.ajax() are already serialized prior to the element being disabled, so
-    // this is only needed for IFRAME submissions.
-    var v = $.fieldValue(this.element);
-    if (v !== null) {
-      options.extraData[this.element.name] = v;
-    }
-  }
-
-  // Disable the element that received the change to prevent user interface
-  // interaction while the Ajax request is in progress. ajax.ajaxing prevents
-  // the element from triggering a new request, but does not prevent the user
-  // from changing its value.
-  $(this.element).addClass('progress-disabled').prop('disabled', true);
-
-  // Insert progressbar or throbber.
-  if (this.progress.type === 'bar') {
-    var progressBar = new Drupal.ProgressBar('ajax-progress-' + this.element.id, $.noop, this.progress.method, $.noop);
-    if (this.progress.message) {
-      progressBar.setProgress(-1, this.progress.message);
-    }
-    if (this.progress.url) {
-      progressBar.startMonitoring(this.progress.url, this.progress.interval || 1500);
-    }
-    this.progress.element = $(progressBar.element).addClass('ajax-progress ajax-progress-bar');
-    this.progress.object = progressBar;
-    $(this.element).after(this.progress.element);
-  }
-  else if (this.progress.type === 'throbber') {
-    this.progress.element = $('<div class="ajax-progress ajax-progress-throbber"><div class="throbber">&nbsp;</div></div>');
-    if (this.progress.message) {
-      this.progress.element.find('.throbber').after('<div class="message">' + this.progress.message + '</div>');
-    }
-    $(this.element).after(this.progress.element);
-  }
-};
-
-/**
- * Handler for the form redirection completion.
- */
-Drupal.ajax.prototype.success = function (response, status) {
-  // Remove the progress element.
-  if (this.progress.element) {
-    $(this.progress.element).remove();
-  }
-  if (this.progress.object) {
-    this.progress.object.stopMonitoring();
-  }
-  $(this.element).removeClass('progress-disabled').prop('disabled', false);
-
-  for (var i in response) {
-    if (response.hasOwnProperty(i) && response[i].command && this.commands[response[i].command]) {
-      this.commands[response[i].command](this, response[i], status);
-    }
-  }
-
-  // Reattach behaviors, if they were detached in beforeSerialize(). The
-  // attachBehaviors() called on the new content from processing the response
-  // commands is not sufficient, because behaviors from the entire form need
-  // to be reattached.
-  if (this.$form) {
-    var settings = this.settings || drupalSettings;
-    Drupal.attachBehaviors(this.$form.get(0), settings);
-  }
-
-  // Remove any response-specific settings so they don't get used on the next
-  // call by mistake.
-  this.settings = null;
-};
-
-/**
- * Build an effect object which tells us how to apply the effect when adding new HTML.
- */
-Drupal.ajax.prototype.getEffect = function (response) {
-  var type = response.effect || this.effect;
-  var speed = response.speed || this.speed;
-
-  var effect = {};
-  if (type === 'none') {
-    effect.showEffect = 'show';
-    effect.hideEffect = 'hide';
-    effect.showSpeed = '';
-  }
-  else if (type === 'fade') {
-    effect.showEffect = 'fadeIn';
-    effect.hideEffect = 'fadeOut';
-    effect.showSpeed = speed;
-  }
-  else {
-    effect.showEffect = type + 'Toggle';
-    effect.hideEffect = type + 'Toggle';
-    effect.showSpeed = speed;
-  }
-
-  return effect;
-};
-
-/**
- * Handler for the form redirection error.
- */
-Drupal.ajax.prototype.error = function (response, uri) {
-  // Remove the progress element.
-  if (this.progress.element) {
-    $(this.progress.element).remove();
-  }
-  if (this.progress.object) {
-    this.progress.object.stopMonitoring();
-  }
-  // Undo hide.
-  $(this.wrapper).show();
-  // Re-enable the element.
-  $(this.element).removeClass('progress-disabled').prop('disabled', false);
-  // Reattach behaviors, if they were detached in beforeSerialize().
-  if (this.$form) {
-    var settings = response.settings || this.settings || drupalSettings;
-    Drupal.attachBehaviors(this.$form.get(0), settings);
-  }
-  throw new Drupal.AjaxError(response, uri);
-};
-
-/**
- * Provide a series of commands that the server can request the client perform.
- */
-Drupal.AjaxCommands = function () {};
-Drupal.AjaxCommands.prototype = {
   /**
-   * Command to insert new content into the DOM.
+   * Ajax object.
+   *
+   * All Ajax objects on a page are accessible through the global Drupal.ajax
+   * object and are keyed by the submit button's ID. You can access them from
+   * your module's JavaScript file to override properties or functions.
+   *
+   * For example, if your Ajax enabled button has the ID 'edit-submit', you can
+   * redefine the function that is called to insert the new content like this
+   * (inside a Drupal.behaviors attach block):
+   * @code
+   *    Drupal.behaviors.myCustomAJAXStuff = {
+   *      attach: function (context, settings) {
+   *        Drupal.ajax['edit-submit'].commands.insert = function (ajax, response, status) {
+   *          new_content = $(response.data);
+   *          $('#my-wrapper').append(new_content);
+   *          alert('New content was appended to #my-wrapper');
+   *        }
+   *      }
+   *    };
+   * @endcode
    */
-  insert: function (ajax, response, status) {
-    // Get information from the response. If it is not there, default to
-    // our presets.
-    var wrapper = response.selector ? $(response.selector) : $(ajax.wrapper);
-    var method = response.method || ajax.method;
-    var effect = ajax.getEffect(response);
-    var settings;
-
-    // We don't know what response.data contains: it might be a string of text
-    // without HTML, so don't rely on jQuery correctly iterpreting
-    // $(response.data) as new HTML rather than a CSS selector. Also, if
-    // response.data contains top-level text nodes, they get lost with either
-    // $(response.data) or $('<div></div>').replaceWith(response.data).
-    var new_content_wrapped = $('<div></div>').html(response.data);
-    var new_content = new_content_wrapped.contents();
-
-    // For legacy reasons, the effects processing code assumes that new_content
-    // consists of a single top-level element. Also, it has not been
-    // sufficiently tested whether attachBehaviors() can be successfully called
-    // with a context object that includes top-level text nodes. However, to
-    // give developers full control of the HTML appearing in the page, and to
-    // enable Ajax content to be inserted in places where DIV elements are not
-    // allowed (e.g., within TABLE, TR, and SPAN parents), we check if the new
-    // content satisfies the requirement of a single top-level element, and
-    // only use the container DIV created above when it doesn't. For more
-    // information, please see http://drupal.org/node/736066.
-    if (new_content.length !== 1 || new_content.get(0).nodeType !== 1) {
-      new_content = new_content_wrapped;
-    }
-
-    // If removing content from the wrapper, detach behaviors first.
-    switch (method) {
-      case 'html':
-      case 'replaceWith':
-      case 'replaceAll':
-      case 'empty':
-      case 'remove':
-        settings = response.settings || ajax.settings || drupalSettings;
-        Drupal.detachBehaviors(wrapper.get(0), settings);
+  Drupal.ajax = function (base, element, element_settings) {
+    var defaults = {
+      event: 'mousedown',
+      keypress: true,
+      selector: '#' + base,
+      effect: 'none',
+      speed: 'none',
+      method: 'replaceWith',
+      progress: {
+        type: 'throbber',
+        message: Drupal.t('Please wait...')
+      },
+      submit: {
+        'js': true
+      }
+    };
+
+    $.extend(this, defaults, element_settings);
+
+    this.commands = new Drupal.AjaxCommands();
+
+    // @todo Remove this after refactoring the PHP code to:
+    //   - Call this 'selector'.
+    //   - Include the '#' for ID-based selectors.
+    //   - Support non-ID-based selectors.
+    if (this.wrapper) {
+      this.wrapper = '#' + this.wrapper;
     }
 
-    // Add the new content to the page.
-    wrapper[method](new_content);
+    this.element = element;
+    this.element_settings = element_settings;
 
-    // Immediately hide the new content if we're using any effects.
-    if (effect.showEffect !== 'show') {
-      new_content.hide();
+    // If there isn't a form, jQuery.ajax() will be used instead, allowing us to
+    // bind Ajax to links as well.
+    if (this.element.form) {
+      this.$form = $(this.element.form);
     }
 
-    // Determine which effect to use and what content will receive the
-    // effect, then show the new content.
-    if (new_content.find('.ajax-new-content').length > 0) {
-      new_content.find('.ajax-new-content').hide();
-      new_content.show();
-      new_content.find('.ajax-new-content')[effect.showEffect](effect.showSpeed);
+    // If no Ajax callback URL was given, use the link href or form action.
+    if (!this.url) {
+      if ($(element).is('a')) {
+        this.url = $(element).attr('href');
+      }
+      else if (element.form) {
+        this.url = this.$form.attr('action');
+
+        // @todo If there's a file input on this form, then jQuery will submit the
+        //   AJAX response with a hidden Iframe rather than the XHR object. If the
+        //   response to the submission is an HTTP redirect, then the Iframe will
+        //   follow it, but the server won't content negotiate it correctly,
+        //   because there won't be an ajax_iframe_upload POST variable. Until we
+        //   figure out a work around to this problem, we prevent AJAX-enabling
+        //   elements that submit to the same URL as the form when there's a file
+        //   input. For example, this means the Delete button on the edit form of
+        //   an Article node doesn't open its confirmation form in a dialog.
+        if (this.$form.find(':file').length) {
+          return;
+        }
+      }
     }
-    else if (effect.showEffect !== 'show') {
-      new_content[effect.showEffect](effect.showSpeed);
+
+    // Replacing 'nojs' with 'ajax' in the URL allows for an easy method to let
+    // the server detect when it needs to degrade gracefully.
+    // There are four scenarios to check for:
+    // 1. /nojs/
+    // 2. /nojs$ - The end of a URL string.
+    // 3. /nojs? - Followed by a query (e.g. path/nojs?destination=foobar).
+    // 4. /nojs# - Followed by a fragment (e.g.: path/nojs#myfragment).
+    this.url = this.url.replace(/\/nojs(\/|$|\?|#)/g, '/ajax$1');
+
+    // Set the options for the ajaxSubmit function.
+    // The 'this' variable will not persist inside of the options object.
+    var ajax = this;
+    ajax.options = {
+      url: ajax.url,
+      data: ajax.submit,
+      beforeSerialize: function (element_settings, options) {
+        return ajax.beforeSerialize(element_settings, options);
+      },
+      beforeSubmit: function (form_values, element_settings, options) {
+        ajax.ajaxing = true;
+        return ajax.beforeSubmit(form_values, element_settings, options);
+      },
+      beforeSend: function (xmlhttprequest, options) {
+        ajax.ajaxing = true;
+        return ajax.beforeSend(xmlhttprequest, options);
+      },
+      success: function (response, status) {
+        // Sanity check for browser support (object expected).
+        // When using iFrame uploads, responses must be returned as a string.
+        if (typeof response === 'string') {
+          response = $.parseJSON(response);
+        }
+        return ajax.success(response, status);
+      },
+      complete: function (response, status) {
+        ajax.ajaxing = false;
+        if (status === 'error' || status === 'parsererror') {
+          return ajax.error(response, ajax.url);
+        }
+      },
+      dataType: 'json',
+      accepts: {
+        json: element_settings.accepts || 'application/vnd.drupal-ajax'
+      },
+      type: 'POST'
+    };
+
+    if (element_settings.dialog) {
+      ajax.options.data.dialogOptions = element_settings.dialog;
+    }
+
+    // Bind the ajaxSubmit function to the element event.
+    $(ajax.element).on(element_settings.event, function (event) {
+      return ajax.eventResponse(this, event);
+    });
+
+    // If necessary, enable keyboard submission so that Ajax behaviors
+    // can be triggered through keyboard input as well as e.g. a mousedown
+    // action.
+    if (element_settings.keypress) {
+      $(ajax.element).on('keypress', function (event) {
+        return ajax.keypressResponse(this, event);
+      });
     }
 
-    // Attach all JavaScript behaviors to the new content, if it was successfully
-    // added to the page, this if statement allows #ajax['wrapper'] to be
-    // optional.
-    if (new_content.parents('html').length > 0) {
-      // Apply any settings from the returned JSON if available.
-      settings = response.settings || ajax.settings || drupalSettings;
-      Drupal.attachBehaviors(new_content.get(0), settings);
+    // If necessary, prevent the browser default action of an additional event.
+    // For example, prevent the browser default action of a click, even if the
+    // AJAX behavior binds to mousedown.
+    if (element_settings.prevent) {
+      $(ajax.element).on(element_settings.prevent, false);
     }
-  },
+  };
 
   /**
-   * Command to remove a chunk from the page.
+   * Handle a key press.
+   *
+   * The Ajax object will, if instructed, bind to a key press response. This
+   * will test to see if the key press is valid to trigger this event and
+   * if it is, trigger it for us and prevent other keypresses from triggering.
+   * In this case we're handling RETURN and SPACEBAR keypresses (event codes 13
+   * and 32. RETURN is often used to submit a form when in a textfield, and
+   * SPACE is often used to activate an element without submitting.
    */
-  remove: function (ajax, response, status) {
-    var settings = response.settings || ajax.settings || drupalSettings;
-    $(response.selector).each(function() {
-      Drupal.detachBehaviors(this, settings);
-    })
-    .remove();
-  },
+  Drupal.ajax.prototype.keypressResponse = function (element, event) {
+    // Create a synonym for this to reduce code confusion.
+    var ajax = this;
+
+    // Detect enter key and space bar and allow the standard response for them,
+    // except for form elements of type 'text', 'tel', 'number' and 'textarea',
+    // where the spacebar activation causes inappropriate activation if
+    // #ajax['keypress'] is TRUE. On a text-type widget a space should always be a
+    // space.
+    if (event.which === 13 || (event.which === 32 && element.type !== 'text' &&
+      element.type !== 'textarea' && element.type !== 'tel' && element.type !== 'number')) {
+      event.preventDefault();
+      event.stopPropagation();
+      $(ajax.element_settings.element).trigger(ajax.element_settings.event);
+    }
+  };
 
   /**
-   * Command to mark a chunk changed.
+   * Handle an event that triggers an Ajax response.
+   *
+   * When an event that triggers an Ajax response happens, this method will
+   * perform the actual Ajax call. It is bound to the event using
+   * bind() in the constructor, and it uses the options specified on the
+   * ajax object.
    */
-  changed: function (ajax, response, status) {
-    if (!$(response.selector).hasClass('ajax-changed')) {
-      $(response.selector).addClass('ajax-changed');
-      if (response.asterisk) {
-        $(response.selector).find(response.asterisk).append(' <abbr class="ajax-changed" title="' + Drupal.t('Changed') + '">*</abbr> ');
+  Drupal.ajax.prototype.eventResponse = function (element, event) {
+    event.preventDefault();
+    event.stopPropagation();
+
+    // Create a synonym for this to reduce code confusion.
+    var ajax = this;
+
+    // Do not perform another ajax command if one is already in progress.
+    if (ajax.ajaxing) {
+      return;
+    }
+
+    try {
+      if (ajax.$form) {
+        // If setClick is set, we must set this to ensure that the button's
+        // value is passed.
+        if (ajax.setClick) {
+          // Mark the clicked button. 'form.clk' is a special variable for
+          // ajaxSubmit that tells the system which element got clicked to
+          // trigger the submit. Without it there would be no 'op' or
+          // equivalent.
+          element.form.clk = element;
+        }
+
+        ajax.$form.ajaxSubmit(ajax.options);
+      }
+      else {
+        ajax.beforeSerialize(ajax.element, ajax.options);
+        $.ajax(ajax.options);
       }
     }
-  },
+    catch (e) {
+      // Unset the ajax.ajaxing flag here because it won't be unset during
+      // the complete response.
+      ajax.ajaxing = false;
+      window.alert("An error occurred while attempting to process " + ajax.options.url + ": " + e.message);
+    }
+  };
 
   /**
-   * Command to provide an alert.
+   * Handler for the form serialization.
+   *
+   * Runs before the beforeSend() handler (see below), and unlike that one, runs
+   * before field data is collected.
    */
-  alert: function (ajax, response, status) {
-    window.alert(response.text, response.title);
-  },
+  Drupal.ajax.prototype.beforeSerialize = function (element, options) {
+    // Allow detaching behaviors to update field values before collecting them.
+    // This is only needed when field values are added to the POST data, so only
+    // when there is a form such that this.$form.ajaxSubmit() is used instead of
+    // $.ajax(). When there is no form and $.ajax() is used, beforeSerialize()
+    // isn't called, but don't rely on that: explicitly check this.$form.
+    if (this.$form) {
+      var settings = this.settings || drupalSettings;
+      Drupal.detachBehaviors(this.$form.get(0), settings, 'serialize');
+    }
 
-  /**
-   * Command to set the window.location, redirecting the browser.
-   */
-  redirect: function (ajax, response, status) {
-    window.location = response.url;
-  },
+    // Prevent duplicate HTML ids in the returned markup.
+    // @see drupal_html_id()
+    var ids = document.querySelectorAll('[id]');
+    var ajaxHtmlIds = [];
+    for (var i = 0, il = ids.length; i < il; i++) {
+      ajaxHtmlIds.push(ids[i].id);
+    }
+    // Join IDs to minimize request size.
+    options.data.ajax_html_ids = ajaxHtmlIds.join(' ');
+
+    // Allow Drupal to return new JavaScript and CSS files to load without
+    // returning the ones already loaded.
+    // @see \Drupal\Core\Theme\AjaxBasePageNegotiator
+    // @see drupal_get_css()
+    // @see drupal_get_js()
+    var pageState = drupalSettings.ajaxPageState;
+    options.data['ajax_page_state[theme]'] = pageState.theme;
+    options.data['ajax_page_state[theme_token]'] = pageState.theme_token;
+    for (var cssFile in pageState.css) {
+      if (pageState.css.hasOwnProperty(cssFile)) {
+        options.data['ajax_page_state[css][' + cssFile + ']'] = 1;
+      }
+    }
+    for (var jsFile in pageState.js) {
+      if (pageState.js.hasOwnProperty(jsFile)) {
+        options.data['ajax_page_state[js][' + jsFile + ']'] = 1;
+      }
+    }
+  };
 
   /**
-   * Command to provide the jQuery css() function.
+   * Modify form values prior to form submission.
    */
-  css: function (ajax, response, status) {
-    $(response.selector).css(response.argument);
-  },
+  Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) {
+    // This function is left empty to make it simple to override for modules
+    // that wish to add functionality here.
+  };
 
   /**
-   * Command to set the settings that will be used for other commands in this response.
+   * Prepare the Ajax request before it is sent.
    */
-  settings: function (ajax, response, status) {
-    if (response.merge) {
-      $.extend(true, drupalSettings, response.settings);
+  Drupal.ajax.prototype.beforeSend = function (xmlhttprequest, options) {
+    // For forms without file inputs, the jQuery Form plugin serializes the form
+    // values, and then calls jQuery's $.ajax() function, which invokes this
+    // handler. In this circumstance, options.extraData is never used. For forms
+    // with file inputs, the jQuery Form plugin uses the browser's normal form
+    // submission mechanism, but captures the response in a hidden IFRAME. In this
+    // circumstance, it calls this handler first, and then appends hidden fields
+    // to the form to submit the values in options.extraData. There is no simple
+    // way to know which submission mechanism will be used, so we add to extraData
+    // regardless, and allow it to be ignored in the former case.
+    if (this.$form) {
+      options.extraData = options.extraData || {};
+
+      // Let the server know when the IFRAME submission mechanism is used. The
+      // server can use this information to wrap the JSON response in a TEXTAREA,
+      // as per http://jquery.malsup.com/form/#file-upload.
+      options.extraData.ajax_iframe_upload = '1';
+
+      // The triggering element is about to be disabled (see below), but if it
+      // contains a value (e.g., a checkbox, textfield, select, etc.), ensure that
+      // value is included in the submission. As per above, submissions that use
+      // $.ajax() are already serialized prior to the element being disabled, so
+      // this is only needed for IFRAME submissions.
+      var v = $.fieldValue(this.element);
+      if (v !== null) {
+        options.extraData[this.element.name] = v;
+      }
     }
-    else {
-      ajax.settings = response.settings;
+
+    // Disable the element that received the change to prevent user interface
+    // interaction while the Ajax request is in progress. ajax.ajaxing prevents
+    // the element from triggering a new request, but does not prevent the user
+    // from changing its value.
+    $(this.element).addClass('progress-disabled').prop('disabled', true);
+
+    // Insert progressbar or throbber.
+    if (this.progress.type === 'bar') {
+      var progressBar = new Drupal.ProgressBar('ajax-progress-' + this.element.id, $.noop, this.progress.method, $.noop);
+      if (this.progress.message) {
+        progressBar.setProgress(-1, this.progress.message);
+      }
+      if (this.progress.url) {
+        progressBar.startMonitoring(this.progress.url, this.progress.interval || 1500);
+      }
+      this.progress.element = $(progressBar.element).addClass('ajax-progress ajax-progress-bar');
+      this.progress.object = progressBar;
+      $(this.element).after(this.progress.element);
     }
-  },
+    else if (this.progress.type === 'throbber') {
+      this.progress.element = $('<div class="ajax-progress ajax-progress-throbber"><div class="throbber">&nbsp;</div></div>');
+      if (this.progress.message) {
+        this.progress.element.find('.throbber').after('<div class="message">' + this.progress.message + '</div>');
+      }
+      $(this.element).after(this.progress.element);
+    }
+  };
 
   /**
-   * Command to attach data using jQuery's data API.
+   * Handler for the form redirection completion.
    */
-  data: function (ajax, response, status) {
-    $(response.selector).data(response.name, response.value);
-  },
+  Drupal.ajax.prototype.success = function (response, status) {
+    // Remove the progress element.
+    if (this.progress.element) {
+      $(this.progress.element).remove();
+    }
+    if (this.progress.object) {
+      this.progress.object.stopMonitoring();
+    }
+    $(this.element).removeClass('progress-disabled').prop('disabled', false);
+
+    for (var i in response) {
+      if (response.hasOwnProperty(i) && response[i].command && this.commands[response[i].command]) {
+        this.commands[response[i].command](this, response[i], status);
+      }
+    }
+
+    // Reattach behaviors, if they were detached in beforeSerialize(). The
+    // attachBehaviors() called on the new content from processing the response
+    // commands is not sufficient, because behaviors from the entire form need
+    // to be reattached.
+    if (this.$form) {
+      var settings = this.settings || drupalSettings;
+      Drupal.attachBehaviors(this.$form.get(0), settings);
+    }
+
+    // Remove any response-specific settings so they don't get used on the next
+    // call by mistake.
+    this.settings = null;
+  };
 
   /**
-   * Command to apply a jQuery method.
+   * Build an effect object which tells us how to apply the effect when adding new HTML.
    */
-  invoke: function (ajax, response, status) {
-    var $element = $(response.selector);
-    $element[response.method].apply($element, response.args);
-  },
+  Drupal.ajax.prototype.getEffect = function (response) {
+    var type = response.effect || this.effect;
+    var speed = response.speed || this.speed;
+
+    var effect = {};
+    if (type === 'none') {
+      effect.showEffect = 'show';
+      effect.hideEffect = 'hide';
+      effect.showSpeed = '';
+    }
+    else if (type === 'fade') {
+      effect.showEffect = 'fadeIn';
+      effect.hideEffect = 'fadeOut';
+      effect.showSpeed = speed;
+    }
+    else {
+      effect.showEffect = type + 'Toggle';
+      effect.hideEffect = type + 'Toggle';
+      effect.showSpeed = speed;
+    }
+
+    return effect;
+  };
 
   /**
-   * Command to restripe a table.
+   * Handler for the form redirection error.
    */
-  restripe: function (ajax, response, status) {
-    // :even and :odd are reversed because jQuery counts from 0 and
-    // we count from 1, so we're out of sync.
-    // Match immediate children of the parent element to allow nesting.
-    $(response.selector).find('> tbody > tr:visible, > tr:visible')
-      .removeClass('odd even')
-      .filter(':even').addClass('odd').end()
-      .filter(':odd').addClass('even');
-  },
+  Drupal.ajax.prototype.error = function (response, uri) {
+    // Remove the progress element.
+    if (this.progress.element) {
+      $(this.progress.element).remove();
+    }
+    if (this.progress.object) {
+      this.progress.object.stopMonitoring();
+    }
+    // Undo hide.
+    $(this.wrapper).show();
+    // Re-enable the element.
+    $(this.element).removeClass('progress-disabled').prop('disabled', false);
+    // Reattach behaviors, if they were detached in beforeSerialize().
+    if (this.$form) {
+      var settings = response.settings || this.settings || drupalSettings;
+      Drupal.attachBehaviors(this.$form.get(0), settings);
+    }
+    throw new Drupal.AjaxError(response, uri);
+  };
 
   /**
-   * Command to add css.
-   *
-   * Uses the proprietary addImport method if available as browsers which
-   * support that method ignore @import statements in dynamically added
-   * stylesheets.
+   * Provide a series of commands that the server can request the client perform.
    */
-  add_css: function (ajax, response, status) {
-    // Add the styles in the normal way.
-    $('head').prepend(response.data);
-    // Add imports in the styles using the addImport method if available.
-    var match, importMatch = /^@import url\("(.*)"\);$/igm;
-    if (document.styleSheets[0].addImport && importMatch.test(response.data)) {
-      importMatch.lastIndex = 0;
-      do {
-        match = importMatch.exec(response.data);
-        document.styleSheets[0].addImport(match[1]);
-      } while (match);
-    }
-  }
-};
+  Drupal.AjaxCommands = function () {};
+  Drupal.AjaxCommands.prototype = {
+    /**
+     * Command to insert new content into the DOM.
+     */
+    insert: function (ajax, response, status) {
+      // Get information from the response. If it is not there, default to
+      // our presets.
+      var wrapper = response.selector ? $(response.selector) : $(ajax.wrapper);
+      var method = response.method || ajax.method;
+      var effect = ajax.getEffect(response);
+      var settings;
+
+      // We don't know what response.data contains: it might be a string of text
+      // without HTML, so don't rely on jQuery correctly iterpreting
+      // $(response.data) as new HTML rather than a CSS selector. Also, if
+      // response.data contains top-level text nodes, they get lost with either
+      // $(response.data) or $('<div></div>').replaceWith(response.data).
+      var new_content_wrapped = $('<div></div>').html(response.data);
+      var new_content = new_content_wrapped.contents();
+
+      // For legacy reasons, the effects processing code assumes that new_content
+      // consists of a single top-level element. Also, it has not been
+      // sufficiently tested whether attachBehaviors() can be successfully called
+      // with a context object that includes top-level text nodes. However, to
+      // give developers full control of the HTML appearing in the page, and to
+      // enable Ajax content to be inserted in places where DIV elements are not
+      // allowed (e.g., within TABLE, TR, and SPAN parents), we check if the new
+      // content satisfies the requirement of a single top-level element, and
+      // only use the container DIV created above when it doesn't. For more
+      // information, please see http://drupal.org/node/736066.
+      if (new_content.length !== 1 || new_content.get(0).nodeType !== 1) {
+        new_content = new_content_wrapped;
+      }
+
+      // If removing content from the wrapper, detach behaviors first.
+      switch (method) {
+        case 'html':
+        case 'replaceWith':
+        case 'replaceAll':
+        case 'empty':
+        case 'remove':
+          settings = response.settings || ajax.settings || drupalSettings;
+          Drupal.detachBehaviors(wrapper.get(0), settings);
+      }
+
+      // Add the new content to the page.
+      wrapper[method](new_content);
+
+      // Immediately hide the new content if we're using any effects.
+      if (effect.showEffect !== 'show') {
+        new_content.hide();
+      }
+
+      // Determine which effect to use and what content will receive the
+      // effect, then show the new content.
+      if (new_content.find('.ajax-new-content').length > 0) {
+        new_content.find('.ajax-new-content').hide();
+        new_content.show();
+        new_content.find('.ajax-new-content')[effect.showEffect](effect.showSpeed);
+      }
+      else if (effect.showEffect !== 'show') {
+        new_content[effect.showEffect](effect.showSpeed);
+      }
+
+      // Attach all JavaScript behaviors to the new content, if it was successfully
+      // added to the page, this if statement allows #ajax['wrapper'] to be
+      // optional.
+      if (new_content.parents('html').length > 0) {
+        // Apply any settings from the returned JSON if available.
+        settings = response.settings || ajax.settings || drupalSettings;
+        Drupal.attachBehaviors(new_content.get(0), settings);
+      }
+    },
+
+    /**
+     * Command to remove a chunk from the page.
+     */
+    remove: function (ajax, response, status) {
+      var settings = response.settings || ajax.settings || drupalSettings;
+      $(response.selector).each(function () {
+        Drupal.detachBehaviors(this, settings);
+      })
+        .remove();
+    },
+
+    /**
+     * Command to mark a chunk changed.
+     */
+    changed: function (ajax, response, status) {
+      if (!$(response.selector).hasClass('ajax-changed')) {
+        $(response.selector).addClass('ajax-changed');
+        if (response.asterisk) {
+          $(response.selector).find(response.asterisk).append(' <abbr class="ajax-changed" title="' + Drupal.t('Changed') + '">*</abbr> ');
+        }
+      }
+    },
+
+    /**
+     * Command to provide an alert.
+     */
+    alert: function (ajax, response, status) {
+      window.alert(response.text, response.title);
+    },
+
+    /**
+     * Command to set the window.location, redirecting the browser.
+     */
+    redirect: function (ajax, response, status) {
+      window.location = response.url;
+    },
+
+    /**
+     * Command to provide the jQuery css() function.
+     */
+    css: function (ajax, response, status) {
+      $(response.selector).css(response.argument);
+    },
+
+    /**
+     * Command to set the settings that will be used for other commands in this response.
+     */
+    settings: function (ajax, response, status) {
+      if (response.merge) {
+        $.extend(true, drupalSettings, response.settings);
+      }
+      else {
+        ajax.settings = response.settings;
+      }
+    },
+
+    /**
+     * Command to attach data using jQuery's data API.
+     */
+    data: function (ajax, response, status) {
+      $(response.selector).data(response.name, response.value);
+    },
+
+    /**
+     * Command to apply a jQuery method.
+     */
+    invoke: function (ajax, response, status) {
+      var $element = $(response.selector);
+      $element[response.method].apply($element, response.args);
+    },
+
+    /**
+     * Command to restripe a table.
+     */
+    restripe: function (ajax, response, status) {
+      // :even and :odd are reversed because jQuery counts from 0 and
+      // we count from 1, so we're out of sync.
+      // Match immediate children of the parent element to allow nesting.
+      $(response.selector).find('> tbody > tr:visible, > tr:visible')
+        .removeClass('odd even')
+        .filter(':even').addClass('odd').end()
+        .filter(':odd').addClass('even');
+    },
+
+    /**
+     * Command to add css.
+     *
+     * Uses the proprietary addImport method if available as browsers which
+     * support that method ignore @import statements in dynamically added
+     * stylesheets.
+     */
+    add_css: function (ajax, response, status) {
+      // Add the styles in the normal way.
+      $('head').prepend(response.data);
+      // Add imports in the styles using the addImport method if available.
+      var match, importMatch = /^@import url\("(.*)"\);$/igm;
+      if (document.styleSheets[0].addImport && importMatch.test(response.data)) {
+        importMatch.lastIndex = 0;
+        do {
+          match = importMatch.exec(response.data);
+          document.styleSheets[0].addImport(match[1]);
+        } while (match);
+      }
+    }
+  };
 
 })(jQuery, this, Drupal, drupalSettings);
diff --git a/core/misc/announce.js b/core/misc/announce.js
index dbefeb6..00ce14b 100644
--- a/core/misc/announce.js
+++ b/core/misc/announce.js
@@ -42,7 +42,7 @@
   /**
    * Concatenates announcements to a single string; appends to the live region.
    */
-  function announce () {
+  function announce() {
     var text = [];
     var priority = 'polite';
     var announcement;
diff --git a/core/misc/autocomplete.js b/core/misc/autocomplete.js
index 743e8d0..2181fda 100644
--- a/core/misc/autocomplete.js
+++ b/core/misc/autocomplete.js
@@ -1,201 +1,201 @@
 (function ($, Drupal) {
 
-"use strict";
-
-var autocomplete;
-
-/**
- * Helper splitting terms from the autocomplete value.
- *
- * @param {String} value
- *
- * @return {Array}
- */
-function autocompleteSplitValues (value) {
-  // We will match the value against comma-seperated terms.
-  var result = [];
-  var quote = false;
-  var current = '';
-  var valueLength = value.length;
-  var i, character;
-
-  for (i = 0; i < valueLength; i++) {
-    character = value.charAt(i);
-    if (character === '"') {
-      current += character;
-      quote = !quote;
-    }
-    else if (character === ',' && !quote) {
-      result.push(current.trim());
-      current = '';
+  "use strict";
+
+  var autocomplete;
+
+  /**
+   * Helper splitting terms from the autocomplete value.
+   *
+   * @param {String} value
+   *
+   * @return {Array}
+   */
+  function autocompleteSplitValues(value) {
+    // We will match the value against comma-seperated terms.
+    var result = [];
+    var quote = false;
+    var current = '';
+    var valueLength = value.length;
+    var i, character;
+
+    for (i = 0; i < valueLength; i++) {
+      character = value.charAt(i);
+      if (character === '"') {
+        current += character;
+        quote = !quote;
+      }
+      else if (character === ',' && !quote) {
+        result.push(current.trim());
+        current = '';
+      }
+      else {
+        current += character;
+      }
     }
-    else {
-      current += character;
+    if (value.length > 0) {
+      result.push($.trim(current));
     }
-  }
-  if (value.length > 0) {
-    result.push($.trim(current));
+
+    return result;
   }
 
-  return result;
-}
-
-/**
- * Returns the last value of an multi-value textfield.
- *
- * @param {String} terms
- *
- * @return {String}
- */
-function extractLastTerm (terms) {
-  return autocomplete.splitValues(terms).pop();
-}
-
-/**
- * The search handler is called before a search is performed.
- *
- * @param {Object} event
- *
- * @return {Boolean}
- */
-function searchHandler (event) {
-  // Only search when the term is two characters or larger.
-  var term = autocomplete.extractLastTerm(event.target.value);
-  return term.length >= autocomplete.minLength;
-}
-
-/**
- * jQuery UI autocomplete source callback.
- *
- * @param {Object} request
- * @param {Function} response
- */
-function sourceData (request, response) {
-  /*jshint validthis:true */
-  var elementId = this.element.attr('id');
-
-  if (!(elementId in autocomplete.cache)) {
-    autocomplete.cache[elementId] = {};
+  /**
+   * Returns the last value of an multi-value textfield.
+   *
+   * @param {String} terms
+   *
+   * @return {String}
+   */
+  function extractLastTerm(terms) {
+    return autocomplete.splitValues(terms).pop();
   }
 
   /**
-   * Filter through the suggestions removing all terms already tagged and
-   * display the available terms to the user.
+   * The search handler is called before a search is performed.
+   *
+   * @param {Object} event
    *
-   * @param {Object} suggestions
+   * @return {Boolean}
    */
-  function showSuggestions (suggestions) {
-    var tagged = autocomplete.splitValues(request.term);
-    for (var i = 0, il = tagged.length; i < il; i++) {
-      var index = suggestions.indexOf(tagged[i]);
-      if (index >= 0) {
-        suggestions.splice(index, 1);
-      }
-    }
-    response(suggestions);
+  function searchHandler(event) {
+    // Only search when the term is two characters or larger.
+    var term = autocomplete.extractLastTerm(event.target.value);
+    return term.length >= autocomplete.minLength;
   }
 
   /**
-   * Transforms the data object into an array and update autocomplete results.
+   * jQuery UI autocomplete source callback.
    *
-   * @param {Object} data
+   * @param {Object} request
+   * @param {Function} response
    */
-  function sourceCallbackHandler (data) {
-    autocomplete.cache[elementId][term] = data;
+  function sourceData(request, response) {
+    /*jshint validthis:true */
+    var elementId = this.element.attr('id');
 
-    // Send the new string array of terms to the jQuery UI list.
-    showSuggestions(data);
-  }
+    if (!(elementId in autocomplete.cache)) {
+      autocomplete.cache[elementId] = {};
+    }
 
-  // Get the desired term and construct the autocomplete URL for it.
-  var term = autocomplete.extractLastTerm(request.term);
+    /**
+     * Filter through the suggestions removing all terms already tagged and
+     * display the available terms to the user.
+     *
+     * @param {Object} suggestions
+     */
+    function showSuggestions(suggestions) {
+      var tagged = autocomplete.splitValues(request.term);
+      for (var i = 0, il = tagged.length; i < il; i++) {
+        var index = suggestions.indexOf(tagged[i]);
+        if (index >= 0) {
+          suggestions.splice(index, 1);
+        }
+      }
+      response(suggestions);
+    }
 
-  // Check if the term is already cached.
-  if (autocomplete.cache[elementId].hasOwnProperty(term)) {
-    showSuggestions(autocomplete.cache[elementId][term]);
-  }
-  else {
-    var options = $.extend({ success: sourceCallbackHandler, data: { q: term } }, autocomplete.ajax);
-    /*jshint validthis:true */
-    $.ajax(this.element.attr('data-autocomplete-path'), options);
-  }
-}
-
-/**
- * Handles an autocompletefocus event.
- *
- * @return {Boolean}
- */
-function focusHandler () {
-  return false;
-}
-
-/**
- * Handles an autocompleteselect event.
- *
- * @param {Object} event
- * @param {Object} ui
- *
- * @return {Boolean}
- */
-function selectHandler (event, ui) {
-  var terms = autocomplete.splitValues(event.target.value);
-  // Remove the current input.
-  terms.pop();
-  // Add the selected item.
-  if (ui.item.value.search(",") > 0) {
-    terms.push('"' + ui.item.value + '"');
+    /**
+     * Transforms the data object into an array and update autocomplete results.
+     *
+     * @param {Object} data
+     */
+    function sourceCallbackHandler(data) {
+      autocomplete.cache[elementId][term] = data;
+
+      // Send the new string array of terms to the jQuery UI list.
+      showSuggestions(data);
+    }
+
+    // Get the desired term and construct the autocomplete URL for it.
+    var term = autocomplete.extractLastTerm(request.term);
+
+    // Check if the term is already cached.
+    if (autocomplete.cache[elementId].hasOwnProperty(term)) {
+      showSuggestions(autocomplete.cache[elementId][term]);
+    }
+    else {
+      var options = $.extend({ success: sourceCallbackHandler, data: { q: term } }, autocomplete.ajax);
+      /*jshint validthis:true */
+      $.ajax(this.element.attr('data-autocomplete-path'), options);
+    }
   }
-  else {
-    terms.push(ui.item.value);
+
+  /**
+   * Handles an autocompletefocus event.
+   *
+   * @return {Boolean}
+   */
+  function focusHandler() {
+    return false;
   }
-  event.target.value = terms.join(', ');
-  // Return false to tell jQuery UI that we've filled in the value already.
-  return false;
-}
-
-/**
- * Attaches the autocomplete behavior to all required fields.
- */
-Drupal.behaviors.autocomplete = {
-  attach: function (context) {
-    // Act on textfields with the "form-autocomplete" class.
-    var $autocomplete = $(context).find('input.form-autocomplete').once('autocomplete');
-    if ($autocomplete.length) {
-      // Use jQuery UI Autocomplete on the textfield.
-      $autocomplete.autocomplete(autocomplete.options);
+
+  /**
+   * Handles an autocompleteselect event.
+   *
+   * @param {Object} event
+   * @param {Object} ui
+   *
+   * @return {Boolean}
+   */
+  function selectHandler(event, ui) {
+    var terms = autocomplete.splitValues(event.target.value);
+    // Remove the current input.
+    terms.pop();
+    // Add the selected item.
+    if (ui.item.value.search(",") > 0) {
+      terms.push('"' + ui.item.value + '"');
     }
-  },
-  detach: function (context, settings, trigger) {
-    if (trigger === 'unload') {
-      $(context).find('input.form-autocomplete')
-        .removeOnce('autocomplete')
-        .autocomplete('destroy');
+    else {
+      terms.push(ui.item.value);
     }
+    event.target.value = terms.join(', ');
+    // Return false to tell jQuery UI that we've filled in the value already.
+    return false;
   }
-};
-
-/**
- * Autocomplete object implementation.
- */
-autocomplete = {
-  cache: {},
-  // Exposes methods to allow overriding by contrib.
-  minLength: 1,
-  splitValues: autocompleteSplitValues,
-  extractLastTerm: extractLastTerm,
-  // jQuery UI autocomplete options.
-  options: {
-    source: sourceData,
-    focus: focusHandler,
-    search: searchHandler,
-    select: selectHandler
-  },
-  ajax: {
-    dataType: 'json'
-  }
-};
 
-Drupal.autocomplete = autocomplete;
+  /**
+   * Attaches the autocomplete behavior to all required fields.
+   */
+  Drupal.behaviors.autocomplete = {
+    attach: function (context) {
+      // Act on textfields with the "form-autocomplete" class.
+      var $autocomplete = $(context).find('input.form-autocomplete').once('autocomplete');
+      if ($autocomplete.length) {
+        // Use jQuery UI Autocomplete on the textfield.
+        $autocomplete.autocomplete(autocomplete.options);
+      }
+    },
+    detach: function (context, settings, trigger) {
+      if (trigger === 'unload') {
+        $(context).find('input.form-autocomplete')
+          .removeOnce('autocomplete')
+          .autocomplete('destroy');
+      }
+    }
+  };
+
+  /**
+   * Autocomplete object implementation.
+   */
+  autocomplete = {
+    cache: {},
+    // Exposes methods to allow overriding by contrib.
+    minLength: 1,
+    splitValues: autocompleteSplitValues,
+    extractLastTerm: extractLastTerm,
+    // jQuery UI autocomplete options.
+    options: {
+      source: sourceData,
+      focus: focusHandler,
+      search: searchHandler,
+      select: selectHandler
+    },
+    ajax: {
+      dataType: 'json'
+    }
+  };
+
+  Drupal.autocomplete = autocomplete;
 
 })(jQuery, Drupal);
diff --git a/core/misc/batch.js b/core/misc/batch.js
index b3013e5..82d6bce 100644
--- a/core/misc/batch.js
+++ b/core/misc/batch.js
@@ -1,39 +1,39 @@
 (function ($, Drupal) {
 
-"use strict";
+  "use strict";
 
-/**
- * Attaches the batch behavior to progress bars.
- */
-Drupal.behaviors.batch = {
-  attach: function (context, settings) {
-    var batch = settings.batch;
-    var $progress = $('#progress').once('batch');
-    var progressBar;
+  /**
+   * Attaches the batch behavior to progress bars.
+   */
+  Drupal.behaviors.batch = {
+    attach: function (context, settings) {
+      var batch = settings.batch;
+      var $progress = $('#progress').once('batch');
+      var progressBar;
 
-    // Success: redirect to the summary.
-    function updateCallback(progress, status, pb) {
-      if (progress === '100') {
-        pb.stopMonitoring();
-        window.location = batch.uri + '&op=finished';
+      // Success: redirect to the summary.
+      function updateCallback(progress, status, pb) {
+        if (progress === '100') {
+          pb.stopMonitoring();
+          window.location = batch.uri + '&op=finished';
+        }
       }
-    }
 
-    function errorCallback(pb) {
-      $progress.prepend($('<p class="error"></p>').html(batch.errorMessage));
-      $('#wait').hide();
-    }
+      function errorCallback(pb) {
+        $progress.prepend($('<p class="error"></p>').html(batch.errorMessage));
+        $('#wait').hide();
+      }
 
-    if ($progress.length) {
-      progressBar = new Drupal.ProgressBar('updateprogress', updateCallback, 'POST', errorCallback);
-      progressBar.setProgress(-1, batch.initMessage);
-      progressBar.startMonitoring(batch.uri + '&op=do', 10);
-      // Remove HTML from no-js progress bar.
-      $progress.empty();
-      // Append the JS progressbar element.
-      $progress.append(progressBar.element);
+      if ($progress.length) {
+        progressBar = new Drupal.ProgressBar('updateprogress', updateCallback, 'POST', errorCallback);
+        progressBar.setProgress(-1, batch.initMessage);
+        progressBar.startMonitoring(batch.uri + '&op=do', 10);
+        // Remove HTML from no-js progress bar.
+        $progress.empty();
+        // Append the JS progressbar element.
+        $progress.append(progressBar.element);
+      }
     }
-  }
-};
+  };
 
 })(jQuery, Drupal);
diff --git a/core/misc/collapse.js b/core/misc/collapse.js
index 84a803d..9dc4202 100644
--- a/core/misc/collapse.js
+++ b/core/misc/collapse.js
@@ -1,113 +1,113 @@
 (function ($, Modernizr, Drupal) {
 
-"use strict";
+  "use strict";
 
-/**
- * The collapsible details object represents a single collapsible details element.
- */
-function CollapsibleDetails (node) {
-  this.$node = $(node);
-  this.$node.data('details', this);
-  // Expand details if there are errors inside, or if it contains an
-  // element that is targeted by the URI fragment identifier.
-  var anchor = location.hash && location.hash !== '#' ? ', ' + location.hash : '';
-  if (this.$node.find('.error' + anchor).length) {
-    this.$node.attr('open', true);
-  }
-  // Initialize and setup the summary,
-  this.setupSummary();
-  // Initialize and setup the legend.
-  this.setupLegend();
-}
-
-/**
- * Extend CollapsibleDetails function.
- */
-$.extend(CollapsibleDetails, {
   /**
-   * Holds references to instantiated CollapsibleDetails objects.
+   * The collapsible details object represents a single collapsible details element.
    */
-  instances: []
-});
+  function CollapsibleDetails(node) {
+    this.$node = $(node);
+    this.$node.data('details', this);
+    // Expand details if there are errors inside, or if it contains an
+    // element that is targeted by the URI fragment identifier.
+    var anchor = location.hash && location.hash !== '#' ? ', ' + location.hash : '';
+    if (this.$node.find('.error' + anchor).length) {
+      this.$node.attr('open', true);
+    }
+    // Initialize and setup the summary,
+    this.setupSummary();
+    // Initialize and setup the legend.
+    this.setupLegend();
+  }
 
-/**
- * Extend CollapsibleDetails prototype.
- */
-$.extend(CollapsibleDetails.prototype, {
   /**
-   * Initialize and setup summary events and markup.
+   * Extend CollapsibleDetails function.
    */
-  setupSummary: function () {
-    this.$summary = $('<span class="summary"></span>');
-    this.$node
-      .on('summaryUpdated', $.proxy(this.onSummaryUpdated, this))
-      .trigger('summaryUpdated');
-  },
+  $.extend(CollapsibleDetails, {
+    /**
+     * Holds references to instantiated CollapsibleDetails objects.
+     */
+    instances: []
+  });
+
   /**
-   * Initialize and setup legend markup.
+   * Extend CollapsibleDetails prototype.
    */
-  setupLegend: function () {
-    // Turn the summary into a clickable link.
-    var $legend = this.$node.find('> summary');
+  $.extend(CollapsibleDetails.prototype, {
+    /**
+     * Initialize and setup summary events and markup.
+     */
+    setupSummary: function () {
+      this.$summary = $('<span class="summary"></span>');
+      this.$node
+        .on('summaryUpdated', $.proxy(this.onSummaryUpdated, this))
+        .trigger('summaryUpdated');
+    },
+    /**
+     * Initialize and setup legend markup.
+     */
+    setupLegend: function () {
+      // Turn the summary into a clickable link.
+      var $legend = this.$node.find('> summary');
 
-    $('<span class="details-summary-prefix visually-hidden"></span>')
-      .append(this.$node.attr('open') ? Drupal.t('Hide') : Drupal.t('Show'))
-      .prependTo($legend)
-      .after(document.createTextNode(' '));
+      $('<span class="details-summary-prefix visually-hidden"></span>')
+        .append(this.$node.attr('open') ? Drupal.t('Hide') : Drupal.t('Show'))
+        .prependTo($legend)
+        .after(document.createTextNode(' '));
 
-    // .wrapInner() does not retain bound events.
-    $('<a class="details-title"></a>')
-      .attr('href', '#' + this.$node.attr('id'))
-      .prepend($legend.contents())
-      .appendTo($legend)
-      .on('click', $.proxy(this.onLegendClick, this));
-    $legend.append(this.$summary);
-  },
-  /**
-   * Handle legend clicks
-   */
-  onLegendClick: function (e) {
-    this.toggle();
-    e.preventDefault();
-  },
-  /**
-   * Update summary
-   */
-  onSummaryUpdated: function () {
-    var text = $.trim(this.$node.drupalGetSummary());
-    this.$summary.html(text ? ' (' + text + ')' : '');
-  },
-  /**
-   * Toggle the visibility of a details element using smooth animations.
-   */
-  toggle: function () {
-    var isOpen = !!this.$node.attr('open');
-    var $summaryPrefix = this.$node.find('> summary span.details-summary-prefix');
-    if (isOpen) {
-      $summaryPrefix.html(Drupal.t('Show'));
-    }
-    else {
-      $summaryPrefix.html(Drupal.t('Hide'));
+      // .wrapInner() does not retain bound events.
+      $('<a class="details-title"></a>')
+        .attr('href', '#' + this.$node.attr('id'))
+        .prepend($legend.contents())
+        .appendTo($legend)
+        .on('click', $.proxy(this.onLegendClick, this));
+      $legend.append(this.$summary);
+    },
+    /**
+     * Handle legend clicks
+     */
+    onLegendClick: function (e) {
+      this.toggle();
+      e.preventDefault();
+    },
+    /**
+     * Update summary
+     */
+    onSummaryUpdated: function () {
+      var text = $.trim(this.$node.drupalGetSummary());
+      this.$summary.html(text ? ' (' + text + ')' : '');
+    },
+    /**
+     * Toggle the visibility of a details element using smooth animations.
+     */
+    toggle: function () {
+      var isOpen = !!this.$node.attr('open');
+      var $summaryPrefix = this.$node.find('> summary span.details-summary-prefix');
+      if (isOpen) {
+        $summaryPrefix.html(Drupal.t('Show'));
+      }
+      else {
+        $summaryPrefix.html(Drupal.t('Hide'));
+      }
+      this.$node.attr('open', !isOpen);
     }
-    this.$node.attr('open', !isOpen);
-  }
-});
+  });
 
-Drupal.behaviors.collapse = {
-  attach: function (context) {
-    if (Modernizr.details) {
-      return;
-    }
-    var $collapsibleDetails = $(context).find('details').once('collapse');
-    if ($collapsibleDetails.length) {
-      for (var i = 0; i < $collapsibleDetails.length; i++) {
-        CollapsibleDetails.instances.push(new CollapsibleDetails($collapsibleDetails[i]));
+  Drupal.behaviors.collapse = {
+    attach: function (context) {
+      if (Modernizr.details) {
+        return;
+      }
+      var $collapsibleDetails = $(context).find('details').once('collapse');
+      if ($collapsibleDetails.length) {
+        for (var i = 0; i < $collapsibleDetails.length; i++) {
+          CollapsibleDetails.instances.push(new CollapsibleDetails($collapsibleDetails[i]));
+        }
       }
     }
-  }
-};
+  };
 
 // Expose constructor in the public space.
-Drupal.CollapsibleDetails = CollapsibleDetails;
+  Drupal.CollapsibleDetails = CollapsibleDetails;
 
 })(jQuery, Modernizr, Drupal);
diff --git a/core/misc/dialog.ajax.js b/core/misc/dialog.ajax.js
index d2d8d3a..4845cbd 100644
--- a/core/misc/dialog.ajax.js
+++ b/core/misc/dialog.ajax.js
@@ -98,7 +98,7 @@
     }
 
     // Bind dialogButtonsChange
-    $dialog.on('dialogButtonsChange', function() {
+    $dialog.on('dialogButtonsChange', function () {
       var buttons = Drupal.behaviors.dialog.prepareDialogButtons($dialog);
       $dialog.dialog('option', 'buttons', buttons);
     });
diff --git a/core/misc/dialog.js b/core/misc/dialog.js
index 2bb3a32..789c37e 100644
--- a/core/misc/dialog.js
+++ b/core/misc/dialog.js
@@ -6,56 +6,56 @@
  */
 (function ($, Drupal, drupalSettings) {
 
-"use strict";
-
-drupalSettings.dialog = {
-  autoOpen: true,
-  dialogClass: '',
-  // When using this API directly (when generating dialogs on the client side),
-  // you may want to override this method and do
-  // @code
-  // jQuery(event.target).remove()
-  // @endcode
-  // as well, to remove the dialog on closing.
-  close: function (event) {
-    Drupal.detachBehaviors(event.target, null, 'unload');
-  }
-};
-
-Drupal.dialog = function (element, options) {
-
-  function openDialog (settings) {
-    settings = $.extend({}, drupalSettings.dialog, options, settings);
-    // Trigger a global event to allow scripts to bind events to the dialog.
-    $(window).trigger('dialog:beforecreate', [dialog, $element, settings]);
-    $element.dialog(settings);
-    dialog.open = true;
-    $(window).trigger('dialog:aftercreate', [dialog, $element, settings]);
-  }
-
-  function closeDialog (value) {
-    $(window).trigger('dialog:beforeclose', [dialog, $element]);
-    $element.dialog('close');
-    dialog.returnValue = value;
-    dialog.open = false;
-    $(window).trigger('dialog:afterclose', [dialog, $element]);
-  }
-
-  var undef;
-  var $element = $(element);
-  var dialog = {
-    open: false,
-    returnValue: undef,
-    show: function () {
-      openDialog({modal: false});
-    },
-    showModal: function () {
-      openDialog({modal: true});
-    },
-    close: closeDialog
+  "use strict";
+
+  drupalSettings.dialog = {
+    autoOpen: true,
+    dialogClass: '',
+    // When using this API directly (when generating dialogs on the client side),
+    // you may want to override this method and do
+    // @code
+    // jQuery(event.target).remove()
+    // @endcode
+    // as well, to remove the dialog on closing.
+    close: function (event) {
+      Drupal.detachBehaviors(event.target, null, 'unload');
+    }
   };
 
-  return dialog;
-};
+  Drupal.dialog = function (element, options) {
+
+    function openDialog(settings) {
+      settings = $.extend({}, drupalSettings.dialog, options, settings);
+      // Trigger a global event to allow scripts to bind events to the dialog.
+      $(window).trigger('dialog:beforecreate', [dialog, $element, settings]);
+      $element.dialog(settings);
+      dialog.open = true;
+      $(window).trigger('dialog:aftercreate', [dialog, $element, settings]);
+    }
+
+    function closeDialog(value) {
+      $(window).trigger('dialog:beforeclose', [dialog, $element]);
+      $element.dialog('close');
+      dialog.returnValue = value;
+      dialog.open = false;
+      $(window).trigger('dialog:afterclose', [dialog, $element]);
+    }
+
+    var undef;
+    var $element = $(element);
+    var dialog = {
+      open: false,
+      returnValue: undef,
+      show: function () {
+        openDialog({modal: false});
+      },
+      showModal: function () {
+        openDialog({modal: true});
+      },
+      close: closeDialog
+    };
+
+    return dialog;
+  };
 
 })(jQuery, Drupal, drupalSettings);
diff --git a/core/misc/dialog.position.js b/core/misc/dialog.position.js
index 7b12055..9d03f69 100644
--- a/core/misc/dialog.position.js
+++ b/core/misc/dialog.position.js
@@ -13,7 +13,7 @@
    * be disabled by setting autoResize: false in the options array when creating
    * a new Drupal.dialog().
    */
-  function resetSize (event) {
+  function resetSize(event) {
     var positionOptions = ['width', 'height', 'minWidth', 'minHeight', 'maxHeight', 'maxWidth', 'position'];
     var adjustedOptions = {};
     var windowHeight = $(window).height();
@@ -45,13 +45,13 @@
   /**
    * Position the dialog's center at the center of displace.offsets boundaries.
    */
-  function resetPosition (options) {
+  function resetPosition(options) {
     var offsets = displace.offsets;
     var left = offsets.left - offsets.right;
     var top = offsets.top - offsets.bottom;
 
-    var leftString = (left > 0 ? '+' : '-') + Math.abs(Math.round(left/2)) + 'px';
-    var topString = (top > 0 ? '+' : '-') + Math.abs(Math.round(top/2)) + 'px';
+    var leftString = (left > 0 ? '+' : '-') + Math.abs(Math.round(left / 2)) + 'px';
+    var topString = (top > 0 ? '+' : '-') + Math.abs(Math.round(top / 2)) + 'px';
     options.position = {
       my: 'center' + (left !== 0 ? leftString : '') + ' center' + (top !== 0 ? topString : '')
     };
diff --git a/core/misc/displace.js b/core/misc/displace.js
index c658694..9cc285a 100644
--- a/core/misc/displace.js
+++ b/core/misc/displace.js
@@ -39,7 +39,7 @@
    *   and left. The value of each key is the viewport displacement distance for
    *   that edge.
    */
-  function displace (broadcast) {
+  function displace(broadcast) {
     offsets = Drupal.displace.offsets = calculateOffsets();
     if (typeof broadcast === 'undefined' || broadcast) {
       $(document).trigger('drupalViewportOffsetChange', offsets);
@@ -55,7 +55,7 @@
    *   and left. The value of each key is the viewport displacement distance for
    *   that edge.
    */
-  function calculateOffsets () {
+  function calculateOffsets() {
     return {
       top: calculateOffset('top'),
       right: calculateOffset('right'),
@@ -79,7 +79,7 @@
    * @return {number}
    *   The viewport displacement distance for the requested edge.
    */
-  function calculateOffset (edge) {
+  function calculateOffset(edge) {
     var edgeOffset = 0;
     var displacingElements = document.querySelectorAll('[data-offset-' + edge + ']');
     for (var i = 0, n = displacingElements.length; i < n; i++) {
@@ -94,7 +94,7 @@
       // but is not a valid number then get the displacement
       // dimensions directly from the element.
       if (isNaN(displacement)) {
-          displacement = getRawOffset(el, edge);
+        displacement = getRawOffset(el, edge);
       }
       // If the displacement value is larger than the current value for this
       // edge, use the displacement value.
@@ -117,7 +117,7 @@
    * @return {number}
    *   The viewport displacement distance for the requested edge.
    */
-  function getRawOffset (el, edge) {
+  function getRawOffset(el, edge) {
     var $el = $(el);
     var documentElement = document.documentElement;
     var displacement = 0;
diff --git a/core/misc/dropbutton/dropbutton.js b/core/misc/dropbutton/dropbutton.js
index 516ecfc..06452fc 100644
--- a/core/misc/dropbutton/dropbutton.js
+++ b/core/misc/dropbutton/dropbutton.js
@@ -1,164 +1,164 @@
 (function ($, Drupal) {
 
-"use strict";
-
-/**
- * Process elements with the .dropbutton class on page load.
- */
-Drupal.behaviors.dropButton = {
-  attach: function (context, settings) {
-    var $dropbuttons = $(context).find('.dropbutton-wrapper').once('dropbutton');
-    if ($dropbuttons.length) {
-      // Adds the delegated handler that will toggle dropdowns on click.
-      var $body = $('body').once('dropbutton-click');
-      if ($body.length) {
-        $body.on('click', '.dropbutton-toggle', dropbuttonClickHandler);
-      }
-      // Initialize all buttons.
-      for (var i = 0, il = $dropbuttons.length; i < il; i++) {
-        DropButton.dropbuttons.push(new DropButton($dropbuttons[i], settings.dropbutton));
+  "use strict";
+
+  /**
+   * Process elements with the .dropbutton class on page load.
+   */
+  Drupal.behaviors.dropButton = {
+    attach: function (context, settings) {
+      var $dropbuttons = $(context).find('.dropbutton-wrapper').once('dropbutton');
+      if ($dropbuttons.length) {
+        // Adds the delegated handler that will toggle dropdowns on click.
+        var $body = $('body').once('dropbutton-click');
+        if ($body.length) {
+          $body.on('click', '.dropbutton-toggle', dropbuttonClickHandler);
+        }
+        // Initialize all buttons.
+        for (var i = 0, il = $dropbuttons.length; i < il; i++) {
+          DropButton.dropbuttons.push(new DropButton($dropbuttons[i], settings.dropbutton));
+        }
       }
     }
-  }
-};
-
-/**
- * Delegated callback for opening and closing dropbutton secondary actions.
- */
-function dropbuttonClickHandler (e) {
-  e.preventDefault();
-  $(e.target).closest('.dropbutton-wrapper').toggleClass('open');
-}
-
-/**
- * A DropButton presents an HTML list as a button with a primary action.
- *
- * All secondary actions beyond the first in the list are presented in a
- * dropdown list accessible through a toggle arrow associated with the button.
- *
- * @param {jQuery} $dropbutton
- *   A jQuery element.
- *
- * @param {Object} settings
- *   A list of options including:
- *    - {String} title: The text inside the toggle link element. This text is
- *      hidden from visual UAs.
- */
-function DropButton (dropbutton, settings) {
-  // Merge defaults with settings.
-  var options = $.extend({'title': Drupal.t('List additional actions')}, settings);
-  var $dropbutton = $(dropbutton);
-  this.$dropbutton = $dropbutton;
-  this.$list = $dropbutton.find('.dropbutton');
-  // Find actions and mark them.
-  this.$actions = this.$list.find('li').addClass('dropbutton-action');
-
-  // Add the special dropdown only if there are hidden actions.
-  if (this.$actions.length > 1) {
-    // Identify the first element of the collection.
-    var $primary = this.$actions.slice(0,1);
-    // Identify the secondary actions.
-    var $secondary = this.$actions.slice(1);
-    $secondary.addClass('secondary-action');
-    // Add toggle link.
-    $primary.after(Drupal.theme('dropbuttonToggle', options));
-    // Bind mouse events.
-    this.$dropbutton
-      .addClass('dropbutton-multiple')
-      .on({
-        /**
-         * Adds a timeout to close the dropdown on mouseleave.
-         */
-        'mouseleave.dropbutton': $.proxy(this.hoverOut, this),
-        /**
-         * Clears timeout when mouseout of the dropdown.
-         */
-        'mouseenter.dropbutton': $.proxy(this.hoverIn, this),
-        /**
-         * Similar to mouseleave/mouseenter, but for keyboard navigation.
-         */
-        'focusout.dropbutton': $.proxy(this.focusOut, this),
-        'focusin.dropbutton': $.proxy(this.focusIn, this)
-      });
-  }
-}
+  };
 
-/**
- * Extend the DropButton constructor.
- */
-$.extend(DropButton, {
   /**
-   * Store all processed DropButtons.
-   *
-   * @type {Array}
+   * Delegated callback for opening and closing dropbutton secondary actions.
    */
-  dropbuttons: []
-});
+  function dropbuttonClickHandler(e) {
+    e.preventDefault();
+    $(e.target).closest('.dropbutton-wrapper').toggleClass('open');
+  }
 
-/**
- * Extend the DropButton prototype.
- */
-$.extend(DropButton.prototype, {
   /**
-   * Toggle the dropbutton open and closed.
+   * A DropButton presents an HTML list as a button with a primary action.
+   *
+   * All secondary actions beyond the first in the list are presented in a
+   * dropdown list accessible through a toggle arrow associated with the button.
    *
-   * @param {Boolean} show
-   *   (optional) Force the dropbutton to open by passing true or to close by
-   *   passing false.
+   * @param {jQuery} $dropbutton
+   *   A jQuery element.
+   *
+   * @param {Object} settings
+   *   A list of options including:
+   *    - {String} title: The text inside the toggle link element. This text is
+   *      hidden from visual UAs.
    */
-  toggle: function (show) {
-    var isBool = typeof show === 'boolean';
-    show = isBool ? show : !this.$dropbutton.hasClass('open');
-    this.$dropbutton.toggleClass('open', show);
-  },
-
-  hoverIn: function () {
-    // Clear any previous timer we were using.
-    if (this.timerID) {
-      window.clearTimeout(this.timerID);
+  function DropButton(dropbutton, settings) {
+    // Merge defaults with settings.
+    var options = $.extend({'title': Drupal.t('List additional actions')}, settings);
+    var $dropbutton = $(dropbutton);
+    this.$dropbutton = $dropbutton;
+    this.$list = $dropbutton.find('.dropbutton');
+    // Find actions and mark them.
+    this.$actions = this.$list.find('li').addClass('dropbutton-action');
+
+    // Add the special dropdown only if there are hidden actions.
+    if (this.$actions.length > 1) {
+      // Identify the first element of the collection.
+      var $primary = this.$actions.slice(0, 1);
+      // Identify the secondary actions.
+      var $secondary = this.$actions.slice(1);
+      $secondary.addClass('secondary-action');
+      // Add toggle link.
+      $primary.after(Drupal.theme('dropbuttonToggle', options));
+      // Bind mouse events.
+      this.$dropbutton
+        .addClass('dropbutton-multiple')
+        .on({
+          /**
+           * Adds a timeout to close the dropdown on mouseleave.
+           */
+          'mouseleave.dropbutton': $.proxy(this.hoverOut, this),
+          /**
+           * Clears timeout when mouseout of the dropdown.
+           */
+          'mouseenter.dropbutton': $.proxy(this.hoverIn, this),
+          /**
+           * Similar to mouseleave/mouseenter, but for keyboard navigation.
+           */
+          'focusout.dropbutton': $.proxy(this.focusOut, this),
+          'focusin.dropbutton': $.proxy(this.focusIn, this)
+        });
     }
-  },
+  }
 
-  hoverOut: function () {
-    // Wait half a second before closing.
-    this.timerID = window.setTimeout($.proxy(this, 'close'), 500);
-  },
+  /**
+   * Extend the DropButton constructor.
+   */
+  $.extend(DropButton, {
+    /**
+     * Store all processed DropButtons.
+     *
+     * @type {Array}
+     */
+    dropbuttons: []
+  });
 
-  open: function () {
-    this.toggle(true);
-  },
+  /**
+   * Extend the DropButton prototype.
+   */
+  $.extend(DropButton.prototype, {
+    /**
+     * Toggle the dropbutton open and closed.
+     *
+     * @param {Boolean} show
+     *   (optional) Force the dropbutton to open by passing true or to close by
+     *   passing false.
+     */
+    toggle: function (show) {
+      var isBool = typeof show === 'boolean';
+      show = isBool ? show : !this.$dropbutton.hasClass('open');
+      this.$dropbutton.toggleClass('open', show);
+    },
+
+    hoverIn: function () {
+      // Clear any previous timer we were using.
+      if (this.timerID) {
+        window.clearTimeout(this.timerID);
+      }
+    },
 
-  close: function () {
-    this.toggle(false);
-  },
+    hoverOut: function () {
+      // Wait half a second before closing.
+      this.timerID = window.setTimeout($.proxy(this, 'close'), 500);
+    },
 
-  focusOut: function(e) {
-    this.hoverOut.call(this, e);
-  },
+    open: function () {
+      this.toggle(true);
+    },
 
-  focusIn: function(e) {
-    this.hoverIn.call(this, e);
-  }
-});
+    close: function () {
+      this.toggle(false);
+    },
 
+    focusOut: function (e) {
+      this.hoverOut.call(this, e);
+    },
 
-$.extend(Drupal.theme, {
-  /**
-   * A toggle is an interactive element often bound to a click handler.
-   *
-   * @param {Object} options
-   *   - {String} title: (optional) The HTML anchor title attribute and
-   *     text for the inner span element.
-   *
-   * @return {String}
-   *   A string representing a DOM fragment.
-   */
-  dropbuttonToggle: function (options) {
-    return '<li class="dropbutton-toggle"><button type="button"><span class="dropbutton-arrow"><span class="visually-hidden">' + options.title + '</span></span></button></li>';
-  }
-});
+    focusIn: function (e) {
+      this.hoverIn.call(this, e);
+    }
+  });
+
+
+  $.extend(Drupal.theme, {
+    /**
+     * A toggle is an interactive element often bound to a click handler.
+     *
+     * @param {Object} options
+     *   - {String} title: (optional) The HTML anchor title attribute and
+     *     text for the inner span element.
+     *
+     * @return {String}
+     *   A string representing a DOM fragment.
+     */
+    dropbuttonToggle: function (options) {
+      return '<li class="dropbutton-toggle"><button type="button"><span class="dropbutton-arrow"><span class="visually-hidden">' + options.title + '</span></span></button></li>';
+    }
+  });
 
-// Expose constructor in the public space.
-Drupal.DropButton = DropButton;
+  // Expose constructor in the public space.
+  Drupal.DropButton = DropButton;
 
 })(jQuery, Drupal);
diff --git a/core/misc/drupal.js b/core/misc/drupal.js
index e994ec6..15df5c0 100644
--- a/core/misc/drupal.js
+++ b/core/misc/drupal.js
@@ -12,358 +12,358 @@ if (window.jQuery) {
 // wrapping it in an anonymous closure.
 (function (domready, Drupal, drupalSettings) {
 
-"use strict";
+  "use strict";
 
-/**
- * Custom error type thrown after attach/detach if one or more behaviors failed.
- *
- * @param list
- *   An array of errors thrown during attach/detach.
- * @param event
- *   A string containing either 'attach' or 'detach'.
- */
-function DrupalBehaviorError(list, event) {
-  this.name = 'DrupalBehaviorError';
-  this.event = event || 'attach';
-  this.list = list;
-  // Makes the list of errors readable.
-  var messageList = [];
-  messageList.push(this.event);
-  for (var i = 0, il = this.list.length; i < il; i++) {
-    messageList.push(this.list[i].behavior + ': ' + this.list[i].error.message);
+  /**
+   * Custom error type thrown after attach/detach if one or more behaviors failed.
+   *
+   * @param list
+   *   An array of errors thrown during attach/detach.
+   * @param event
+   *   A string containing either 'attach' or 'detach'.
+   */
+  function DrupalBehaviorError(list, event) {
+    this.name = 'DrupalBehaviorError';
+    this.event = event || 'attach';
+    this.list = list;
+    // Makes the list of errors readable.
+    var messageList = [];
+    messageList.push(this.event);
+    for (var i = 0, il = this.list.length; i < il; i++) {
+      messageList.push(this.list[i].behavior + ': ' + this.list[i].error.message);
+    }
+    this.message = messageList.join(' ; ');
   }
-  this.message = messageList.join(' ; ');
-}
-DrupalBehaviorError.prototype = new Error();
+  DrupalBehaviorError.prototype = new Error();
 
-/**
- * Attach all registered behaviors to a page element.
- *
- * Behaviors are event-triggered actions that attach to page elements, enhancing
- * default non-JavaScript UIs. Behaviors are registered in the Drupal.behaviors
- * object using the method 'attach' and optionally also 'detach' as follows:
- * @code
- *    Drupal.behaviors.behaviorName = {
- *      attach: function (context, settings) {
- *        ...
- *      },
- *      detach: function (context, settings, trigger) {
- *        ...
- *      }
- *    };
- * @endcode
- *
- * Drupal.attachBehaviors is added below to the jQuery.ready event and therefore
- * runs on initial page load. Developers implementing Ajax in their solutions
- * should also call this function after new page content has been loaded,
- * feeding in an element to be processed, in order to attach all behaviors to
- * the new content.
- *
- * Behaviors should use
- * @code
- *   var elements = $(context).find(selector).once('behavior-name');
- * @endcode
- * to ensure the behavior is attached only once to a given element. (Doing so
- * enables the reprocessing of given elements, which may be needed on occasion
- * despite the ability to limit behavior attachment to a particular element.)
- *
- * @param context
- *   An element to attach behaviors to. If none is given, the document element
- *   is used.
- * @param settings
- *   An object containing settings for the current context. If none is given,
- *   the global drupalSettings object is used.
- */
-Drupal.attachBehaviors = function (context, settings) {
-  context = context || document;
-  settings = settings || drupalSettings;
-  var i, errors = [], behaviors = Drupal.behaviors;
-  // Execute all of them.
-  for (i in behaviors) {
-    if (behaviors.hasOwnProperty(i) && typeof behaviors[i].attach === 'function') {
-      // Don't stop the execution of behaviors in case of an error.
-      try {
-        behaviors[i].attach(context, settings);
-      }
-      catch (e) {
-        errors.push({ behavior: i, error: e });
+  /**
+   * Attach all registered behaviors to a page element.
+   *
+   * Behaviors are event-triggered actions that attach to page elements, enhancing
+   * default non-JavaScript UIs. Behaviors are registered in the Drupal.behaviors
+   * object using the method 'attach' and optionally also 'detach' as follows:
+   * @code
+   *    Drupal.behaviors.behaviorName = {
+   *      attach: function (context, settings) {
+   *        ...
+   *      },
+   *      detach: function (context, settings, trigger) {
+   *        ...
+   *      }
+   *    };
+   * @endcode
+   *
+   * Drupal.attachBehaviors is added below to the jQuery.ready event and therefore
+   * runs on initial page load. Developers implementing Ajax in their solutions
+   * should also call this function after new page content has been loaded,
+   * feeding in an element to be processed, in order to attach all behaviors to
+   * the new content.
+   *
+   * Behaviors should use
+   * @code
+   *   var elements = $(context).find(selector).once('behavior-name');
+   * @endcode
+   * to ensure the behavior is attached only once to a given element. (Doing so
+   * enables the reprocessing of given elements, which may be needed on occasion
+   * despite the ability to limit behavior attachment to a particular element.)
+   *
+   * @param context
+   *   An element to attach behaviors to. If none is given, the document element
+   *   is used.
+   * @param settings
+   *   An object containing settings for the current context. If none is given,
+   *   the global drupalSettings object is used.
+   */
+  Drupal.attachBehaviors = function (context, settings) {
+    context = context || document;
+    settings = settings || drupalSettings;
+    var i, errors = [], behaviors = Drupal.behaviors;
+    // Execute all of them.
+    for (i in behaviors) {
+      if (behaviors.hasOwnProperty(i) && typeof behaviors[i].attach === 'function') {
+        // Don't stop the execution of behaviors in case of an error.
+        try {
+          behaviors[i].attach(context, settings);
+        }
+        catch (e) {
+          errors.push({ behavior: i, error: e });
+        }
       }
     }
-  }
-  // Once all behaviors have been processed, inform the user about errors.
-  if (errors.length) {
-    throw new DrupalBehaviorError(errors, 'attach');
-  }
-};
+    // Once all behaviors have been processed, inform the user about errors.
+    if (errors.length) {
+      throw new DrupalBehaviorError(errors, 'attach');
+    }
+  };
 
 // Attach all behaviors.
-domready(function () { Drupal.attachBehaviors(document, drupalSettings); });
+  domready(function () { Drupal.attachBehaviors(document, drupalSettings); });
 
-/**
- * Detach registered behaviors from a page element.
- *
- * Developers implementing AHAH/Ajax in their solutions should call this
- * function before page content is about to be removed, feeding in an element
- * to be processed, in order to allow special behaviors to detach from the
- * content.
- *
- * Such implementations should look for the class name that was added in their
- * corresponding Drupal.behaviors.behaviorName.attach implementation, i.e.
- * behaviorName-processed, to ensure the behavior is detached only from
- * previously processed elements.
- *
- * @param context
- *   An element to detach behaviors from. If none is given, the document element
- *   is used.
- * @param settings
- *   An object containing settings for the current context. If none given, the
- *   global drupalSettings object is used.
- * @param trigger
- *   A string containing what's causing the behaviors to be detached. The
- *   possible triggers are:
- *   - unload: (default) The context element is being removed from the DOM.
- *   - move: The element is about to be moved within the DOM (for example,
- *     during a tabledrag row swap). After the move is completed,
- *     Drupal.attachBehaviors() is called, so that the behavior can undo
- *     whatever it did in response to the move. Many behaviors won't need to
- *     do anything simply in response to the element being moved, but because
- *     IFRAME elements reload their "src" when being moved within the DOM,
- *     behaviors bound to IFRAME elements (like WYSIWYG editors) may need to
- *     take some action.
- *   - serialize: When an Ajax form is submitted, this is called with the
- *     form as the context. This provides every behavior within the form an
- *     opportunity to ensure that the field elements have correct content
- *     in them before the form is serialized. The canonical use-case is so
- *     that WYSIWYG editors can update the hidden textarea to which they are
- *     bound.
- *
- * @see Drupal.attachBehaviors
- */
-Drupal.detachBehaviors = function (context, settings, trigger) {
-  context = context || document;
-  settings = settings || drupalSettings;
-  trigger = trigger || 'unload';
-  var i, errors = [], behaviors = Drupal.behaviors;
-  // Execute all of them.
-  for (i in behaviors) {
-    if (behaviors.hasOwnProperty(i) && typeof behaviors[i].detach === 'function' ) {
-      // Don't stop the execution of behaviors in case of an error.
-      try {
-        behaviors[i].detach(context, settings, trigger);
-      }
-      catch (e) {
-        errors.push({ behavior: i, error: e });
+  /**
+   * Detach registered behaviors from a page element.
+   *
+   * Developers implementing AHAH/Ajax in their solutions should call this
+   * function before page content is about to be removed, feeding in an element
+   * to be processed, in order to allow special behaviors to detach from the
+   * content.
+   *
+   * Such implementations should look for the class name that was added in their
+   * corresponding Drupal.behaviors.behaviorName.attach implementation, i.e.
+   * behaviorName-processed, to ensure the behavior is detached only from
+   * previously processed elements.
+   *
+   * @param context
+   *   An element to detach behaviors from. If none is given, the document element
+   *   is used.
+   * @param settings
+   *   An object containing settings for the current context. If none given, the
+   *   global drupalSettings object is used.
+   * @param trigger
+   *   A string containing what's causing the behaviors to be detached. The
+   *   possible triggers are:
+   *   - unload: (default) The context element is being removed from the DOM.
+   *   - move: The element is about to be moved within the DOM (for example,
+   *     during a tabledrag row swap). After the move is completed,
+   *     Drupal.attachBehaviors() is called, so that the behavior can undo
+   *     whatever it did in response to the move. Many behaviors won't need to
+   *     do anything simply in response to the element being moved, but because
+   *     IFRAME elements reload their "src" when being moved within the DOM,
+   *     behaviors bound to IFRAME elements (like WYSIWYG editors) may need to
+   *     take some action.
+   *   - serialize: When an Ajax form is submitted, this is called with the
+   *     form as the context. This provides every behavior within the form an
+   *     opportunity to ensure that the field elements have correct content
+   *     in them before the form is serialized. The canonical use-case is so
+   *     that WYSIWYG editors can update the hidden textarea to which they are
+   *     bound.
+   *
+   * @see Drupal.attachBehaviors
+   */
+  Drupal.detachBehaviors = function (context, settings, trigger) {
+    context = context || document;
+    settings = settings || drupalSettings;
+    trigger = trigger || 'unload';
+    var i, errors = [], behaviors = Drupal.behaviors;
+    // Execute all of them.
+    for (i in behaviors) {
+      if (behaviors.hasOwnProperty(i) && typeof behaviors[i].detach === 'function') {
+        // Don't stop the execution of behaviors in case of an error.
+        try {
+          behaviors[i].detach(context, settings, trigger);
+        }
+        catch (e) {
+          errors.push({ behavior: i, error: e });
+        }
       }
     }
-  }
-  // Once all behaviors have been processed, inform the user about errors.
-  if (errors.length) {
-    throw new DrupalBehaviorError(errors, 'detach:' + trigger);
-  }
-};
+    // Once all behaviors have been processed, inform the user about errors.
+    if (errors.length) {
+      throw new DrupalBehaviorError(errors, 'detach:' + trigger);
+    }
+  };
 
-/**
- * Helper to test document width for mobile configurations.
- * @todo Temporary solution for the mobile initiative.
- */
-Drupal.checkWidthBreakpoint = function (width) {
-  width = width || drupalSettings.widthBreakpoint || 640;
-  return (document.documentElement.clientWidth > width);
-};
+  /**
+   * Helper to test document width for mobile configurations.
+   * @todo Temporary solution for the mobile initiative.
+   */
+  Drupal.checkWidthBreakpoint = function (width) {
+    width = width || drupalSettings.widthBreakpoint || 640;
+    return (document.documentElement.clientWidth > width);
+  };
 
-/**
- * Encode special characters in a plain-text string for display as HTML.
- *
- * @param str
- *   The string to be encoded.
- * @return
- *   The encoded string.
- * @ingroup sanitization
- */
-Drupal.checkPlain = function (str) {
-  str = str.toString()
-    .replace(/&/g, '&amp;')
-    .replace(/"/g, '&quot;')
-    .replace(/</g, '&lt;')
-    .replace(/>/g, '&gt;');
-  return str;
-};
+  /**
+   * Encode special characters in a plain-text string for display as HTML.
+   *
+   * @param str
+   *   The string to be encoded.
+   * @return
+   *   The encoded string.
+   * @ingroup sanitization
+   */
+  Drupal.checkPlain = function (str) {
+    str = str.toString()
+      .replace(/&/g, '&amp;')
+      .replace(/"/g, '&quot;')
+      .replace(/</g, '&lt;')
+      .replace(/>/g, '&gt;');
+    return str;
+  };
 
-/**
- * Replace placeholders with sanitized values in a string.
- *
- * @param str
- *   A string with placeholders.
- * @param args
- *   An object of replacements pairs to make. Incidences of any key in this
- *   array are replaced with the corresponding value. Based on the first
- *   character of the key, the value is escaped and/or themed:
- *    - !variable: inserted as is
- *    - @variable: escape plain text to HTML (Drupal.checkPlain)
- *    - %variable: escape text and theme as a placeholder for user-submitted
- *      content (checkPlain + Drupal.theme('placeholder'))
- *
- * @see Drupal.t()
- * @ingroup sanitization
- */
-Drupal.formatString = function(str, args) {
-  // Transform arguments before inserting them.
-  for (var key in args) {
-    if (args.hasOwnProperty(key)) {
-      switch (key.charAt(0)) {
-        // Escaped only.
-        case '@':
-          args[key] = Drupal.checkPlain(args[key]);
-        break;
-        // Pass-through.
-        case '!':
-          break;
-        // Escaped and placeholder.
-        default:
-          args[key] = Drupal.theme('placeholder', args[key]);
-          break;
+  /**
+   * Replace placeholders with sanitized values in a string.
+   *
+   * @param str
+   *   A string with placeholders.
+   * @param args
+   *   An object of replacements pairs to make. Incidences of any key in this
+   *   array are replaced with the corresponding value. Based on the first
+   *   character of the key, the value is escaped and/or themed:
+   *    - !variable: inserted as is
+   *    - @variable: escape plain text to HTML (Drupal.checkPlain)
+   *    - %variable: escape text and theme as a placeholder for user-submitted
+   *      content (checkPlain + Drupal.theme('placeholder'))
+   *
+   * @see Drupal.t()
+   * @ingroup sanitization
+   */
+  Drupal.formatString = function (str, args) {
+    // Transform arguments before inserting them.
+    for (var key in args) {
+      if (args.hasOwnProperty(key)) {
+        switch (key.charAt(0)) {
+          // Escaped only.
+          case '@':
+            args[key] = Drupal.checkPlain(args[key]);
+            break;
+          // Pass-through.
+          case '!':
+            break;
+          // Escaped and placeholder.
+          default:
+            args[key] = Drupal.theme('placeholder', args[key]);
+            break;
+        }
+        str = str.replace(key, args[key]);
       }
-      str = str.replace(key, args[key]);
     }
-  }
-  return str;
-};
+    return str;
+  };
 
-/**
- * Translate strings to the page language or a given language.
- *
- * See the documentation of the server-side t() function for further details.
- *
- * @param str
- *   A string containing the English string to translate.
- * @param args
- *   An object of replacements pairs to make after translation. Incidences
- *   of any key in this array are replaced with the corresponding value.
- *   See Drupal.formatString().
- *
- * @param options
- *   - 'context' (defaults to the empty context): The context the source string
- *     belongs to.
- *
- * @return
- *   The translated string.
- */
-Drupal.t = function (str, args, options) {
-  options = options || {};
-  options.context = options.context || '';
+  /**
+   * Translate strings to the page language or a given language.
+   *
+   * See the documentation of the server-side t() function for further details.
+   *
+   * @param str
+   *   A string containing the English string to translate.
+   * @param args
+   *   An object of replacements pairs to make after translation. Incidences
+   *   of any key in this array are replaced with the corresponding value.
+   *   See Drupal.formatString().
+   *
+   * @param options
+   *   - 'context' (defaults to the empty context): The context the source string
+   *     belongs to.
+   *
+   * @return
+   *   The translated string.
+   */
+  Drupal.t = function (str, args, options) {
+    options = options || {};
+    options.context = options.context || '';
 
-  // Fetch the localized version of the string.
-  if (Drupal.locale.strings && Drupal.locale.strings[options.context] && Drupal.locale.strings[options.context][str]) {
-    str = Drupal.locale.strings[options.context][str];
-  }
+    // Fetch the localized version of the string.
+    if (Drupal.locale.strings && Drupal.locale.strings[options.context] && Drupal.locale.strings[options.context][str]) {
+      str = Drupal.locale.strings[options.context][str];
+    }
 
-  if (args) {
-    str = Drupal.formatString(str, args);
-  }
-  return str;
-};
+    if (args) {
+      str = Drupal.formatString(str, args);
+    }
+    return str;
+  };
 
-/**
- * Returns the URL to a Drupal page.
- */
-Drupal.url = function (path) {
-  return drupalSettings.path.basePath + drupalSettings.path.scriptPath + path;
-};
+  /**
+   * Returns the URL to a Drupal page.
+   */
+  Drupal.url = function (path) {
+    return drupalSettings.path.basePath + drupalSettings.path.scriptPath + path;
+  };
 
-/**
- * Format a string containing a count of items.
- *
- * This function ensures that the string is pluralized correctly. Since Drupal.t() is
- * called by this function, make sure not to pass already-localized strings to it.
- *
- * See the documentation of the server-side format_plural() function for further details.
- *
- * @param count
- *   The item count to display.
- * @param singular
- *   The string for the singular case. Please make sure it is clear this is
- *   singular, to ease translation (e.g. use "1 new comment" instead of "1 new").
- *   Do not use @count in the singular string.
- * @param plural
- *   The string for the plural case. Please make sure it is clear this is plural,
- *   to ease translation. Use @count in place of the item count, as in "@count
- *   new comments".
- * @param args
- *   An object of replacements pairs to make after translation. Incidences
- *   of any key in this array are replaced with the corresponding value.
- *   See Drupal.formatString().
- *   Note that you do not need to include @count in this array.
- *   This replacement is done automatically for the plural case.
- * @param options
- *   The options to pass to the Drupal.t() function.
- * @return
- *   A translated string.
- */
-Drupal.formatPlural = function (count, singular, plural, args, options) {
-  args = args || {};
-  args['@count'] = count;
-  // Determine the index of the plural form.
-  var index = Drupal.locale.pluralFormula ? Drupal.locale.pluralFormula(args['@count']) : ((args['@count'] === 1) ? 0 : 1);
+  /**
+   * Format a string containing a count of items.
+   *
+   * This function ensures that the string is pluralized correctly. Since Drupal.t() is
+   * called by this function, make sure not to pass already-localized strings to it.
+   *
+   * See the documentation of the server-side format_plural() function for further details.
+   *
+   * @param count
+   *   The item count to display.
+   * @param singular
+   *   The string for the singular case. Please make sure it is clear this is
+   *   singular, to ease translation (e.g. use "1 new comment" instead of "1 new").
+   *   Do not use @count in the singular string.
+   * @param plural
+   *   The string for the plural case. Please make sure it is clear this is plural,
+   *   to ease translation. Use @count in place of the item count, as in "@count
+   *   new comments".
+   * @param args
+   *   An object of replacements pairs to make after translation. Incidences
+   *   of any key in this array are replaced with the corresponding value.
+   *   See Drupal.formatString().
+   *   Note that you do not need to include @count in this array.
+   *   This replacement is done automatically for the plural case.
+   * @param options
+   *   The options to pass to the Drupal.t() function.
+   * @return
+   *   A translated string.
+   */
+  Drupal.formatPlural = function (count, singular, plural, args, options) {
+    args = args || {};
+    args['@count'] = count;
+    // Determine the index of the plural form.
+    var index = Drupal.locale.pluralFormula ? Drupal.locale.pluralFormula(args['@count']) : ((args['@count'] === 1) ? 0 : 1);
 
-  if (index === 0) {
-    return Drupal.t(singular, args, options);
-  }
-  else if (index === 1) {
-    return Drupal.t(plural, args, options);
-  }
-  else {
-    args['@count[' + index + ']'] = args['@count'];
-    delete args['@count'];
-    return Drupal.t(plural.replace('@count', '@count[' + index + ']'), args, options);
-  }
-};
+    if (index === 0) {
+      return Drupal.t(singular, args, options);
+    }
+    else if (index === 1) {
+      return Drupal.t(plural, args, options);
+    }
+    else {
+      args['@count[' + index + ']'] = args['@count'];
+      delete args['@count'];
+      return Drupal.t(plural.replace('@count', '@count[' + index + ']'), args, options);
+    }
+  };
 
-/**
- * Encodes a Drupal path for use in a URL.
- *
- * For aesthetic reasons slashes are not escaped.
- */
-Drupal.encodePath = function (item) {
-  return window.encodeURIComponent(item).replace(/%2F/g, '/');
-};
+  /**
+   * Encodes a Drupal path for use in a URL.
+   *
+   * For aesthetic reasons slashes are not escaped.
+   */
+  Drupal.encodePath = function (item) {
+    return window.encodeURIComponent(item).replace(/%2F/g, '/');
+  };
 
-/**
- * Generate the themed representation of a Drupal object.
- *
- * All requests for themed output must go through this function. It examines
- * the request and routes it to the appropriate theme function. If the current
- * theme does not provide an override function, the generic theme function is
- * called.
- *
- * For example, to retrieve the HTML for text that should be emphasized and
- * displayed as a placeholder inside a sentence, call
- * Drupal.theme('placeholder', text).
- *
- * @param func
- *   The name of the theme function to call.
- * @param ...
- *   Additional arguments to pass along to the theme function.
- * @return
- *   Any data the theme function returns. This could be a plain HTML string,
- *   but also a complex object.
- */
-Drupal.theme = function (func) {
-  var args = Array.prototype.slice.apply(arguments, [1]);
-  if (func in Drupal.theme) {
-    return Drupal.theme[func].apply(this, args);
-  }
-};
+  /**
+   * Generate the themed representation of a Drupal object.
+   *
+   * All requests for themed output must go through this function. It examines
+   * the request and routes it to the appropriate theme function. If the current
+   * theme does not provide an override function, the generic theme function is
+   * called.
+   *
+   * For example, to retrieve the HTML for text that should be emphasized and
+   * displayed as a placeholder inside a sentence, call
+   * Drupal.theme('placeholder', text).
+   *
+   * @param func
+   *   The name of the theme function to call.
+   * @param ...
+   *   Additional arguments to pass along to the theme function.
+   * @return
+   *   Any data the theme function returns. This could be a plain HTML string,
+   *   but also a complex object.
+   */
+  Drupal.theme = function (func) {
+    var args = Array.prototype.slice.apply(arguments, [1]);
+    if (func in Drupal.theme) {
+      return Drupal.theme[func].apply(this, args);
+    }
+  };
 
-/**
- * Formats text for emphasized display in a placeholder inside a sentence.
- *
- * @param str
- *   The text to format (plain-text).
- * @return
- *   The formatted text (html).
- */
-Drupal.theme.placeholder = function (str) {
-  return '<em class="placeholder">' + Drupal.checkPlain(str) + '</em>';
-};
+  /**
+   * Formats text for emphasized display in a placeholder inside a sentence.
+   *
+   * @param str
+   *   The text to format (plain-text).
+   * @return
+   *   The formatted text (html).
+   */
+  Drupal.theme.placeholder = function (str) {
+    return '<em class="placeholder">' + Drupal.checkPlain(str) + '</em>';
+  };
 
 })(domready, Drupal, window.drupalSettings);
diff --git a/core/misc/form.js b/core/misc/form.js
index 9bb9379..f752f31 100644
--- a/core/misc/form.js
+++ b/core/misc/form.js
@@ -1,213 +1,213 @@
 (function ($, Drupal, debounce) {
 
-"use strict";
-
-/**
- * Retrieves the summary for the first element.
- */
-$.fn.drupalGetSummary = function () {
-  var callback = this.data('summaryCallback');
-  return (this[0] && callback) ? $.trim(callback(this[0])) : '';
-};
-
-/**
- * Sets the summary for all matched elements.
- *
- * @param callback
- *   Either a function that will be called each time the summary is
- *   retrieved or a string (which is returned each time).
- */
-$.fn.drupalSetSummary = function (callback) {
-  var self = this;
-
-  // To facilitate things, the callback should always be a function. If it's
-  // not, we wrap it into an anonymous function which just returns the value.
-  if (typeof callback !== 'function') {
-    var val = callback;
-    callback = function () { return val; };
-  }
+  "use strict";
+
+  /**
+   * Retrieves the summary for the first element.
+   */
+  $.fn.drupalGetSummary = function () {
+    var callback = this.data('summaryCallback');
+    return (this[0] && callback) ? $.trim(callback(this[0])) : '';
+  };
+
+  /**
+   * Sets the summary for all matched elements.
+   *
+   * @param callback
+   *   Either a function that will be called each time the summary is
+   *   retrieved or a string (which is returned each time).
+   */
+  $.fn.drupalSetSummary = function (callback) {
+    var self = this;
+
+    // To facilitate things, the callback should always be a function. If it's
+    // not, we wrap it into an anonymous function which just returns the value.
+    if (typeof callback !== 'function') {
+      var val = callback;
+      callback = function () { return val; };
+    }
 
-  return this
-    .data('summaryCallback', callback)
-    // To prevent duplicate events, the handlers are first removed and then
-    // (re-)added.
-    .off('formUpdated.summary')
-    .on('formUpdated.summary', function () {
-      self.trigger('summaryUpdated');
-    })
-    // The actual summaryUpdated handler doesn't fire when the callback is
-    // changed, so we have to do this manually.
-    .trigger('summaryUpdated');
-};
-
-/**
- * Sends a 'formUpdated' event each time a form element is modified.
- */
-Drupal.behaviors.formUpdated = {
-  attach: function (context) {
-    // These events are namespaced so that we can remove them later.
-    var events = 'change.formUpdated click.formUpdated blur.formUpdated keyup.formUpdated';
-    $(context)
-      // Since context could be an input element itself, it's added back to
-      // the jQuery object and filtered again.
-      .find(':input').addBack().filter(':input')
+    return this
+      .data('summaryCallback', callback)
       // To prevent duplicate events, the handlers are first removed and then
       // (re-)added.
-      .off(events).on(events, function () {
-        $(this).trigger('formUpdated');
-      });
-  }
-};
-
-/**
- * Prevents consecutive form submissions of identical form values.
- *
- * Repetitive form submissions that would submit the identical form values are
- * prevented, unless the form values are different to the previously submitted
- * values.
- *
- * This is a simplified re-implementation of a user-agent behavior that should
- * be natively supported by major web browsers, but at this time, only Firefox
- * has a built-in protection.
- *
- * A form value-based approach ensures that the constraint is triggered for
- * consecutive, identical form submissions only. Compared to that, a form
- * button-based approach would (1) rely on [visible] buttons to exist where
- * technically not required and (2) require more complex state management if
- * there are multiple buttons in a form.
- *
- * This implementation is based on form-level submit events only and relies on
- * jQuery's serialize() method to determine submitted form values. As such, the
- * following limitations exist:
- *
- * - Event handlers on form buttons that preventDefault() do not receive a
- *   double-submit protection. That is deemed to be fine, since such button
- *   events typically trigger reversible client-side or server-side operations
- *   that are local to the context of a form only.
- * - Changed values in advanced form controls, such as file inputs, are not part
- *   of the form values being compared between consecutive form submits (due to
- *   limitations of jQuery.serialize()). That is deemed to be acceptable,
- *   because if the user forgot to attach a file, then the size of HTTP payload
- *   will most likely be small enough to be fully passed to the server endpoint
- *   within (milli)seconds. If a user mistakenly attached a wrong file and is
- *   technically versed enough to cancel the form submission (and HTTP payload)
- *   in order to attach a different file, then that edge-case is not supported
- *   here.
- *
- * Lastly, all forms submitted via HTTP GET are idempotent by definition of HTTP
- * standards, so excluded in this implementation.
- */
-Drupal.behaviors.formSingleSubmit = {
-  attach: function () {
-    function onFormSubmit (e) {
-      var $form = $(e.currentTarget);
-      var formValues = $form.serialize();
-      var previousValues = $form.attr('data-drupal-form-submit-last');
-      if (previousValues === formValues) {
-        e.preventDefault();
-      }
-      else {
-        $form.attr('data-drupal-form-submit-last', formValues);
+      .off('formUpdated.summary')
+      .on('formUpdated.summary', function () {
+        self.trigger('summaryUpdated');
+      })
+      // The actual summaryUpdated handler doesn't fire when the callback is
+      // changed, so we have to do this manually.
+      .trigger('summaryUpdated');
+  };
+
+  /**
+   * Sends a 'formUpdated' event each time a form element is modified.
+   */
+  Drupal.behaviors.formUpdated = {
+    attach: function (context) {
+      // These events are namespaced so that we can remove them later.
+      var events = 'change.formUpdated click.formUpdated blur.formUpdated keyup.formUpdated';
+      $(context)
+        // Since context could be an input element itself, it's added back to
+        // the jQuery object and filtered again.
+        .find(':input').addBack().filter(':input')
+        // To prevent duplicate events, the handlers are first removed and then
+        // (re-)added.
+        .off(events).on(events, function () {
+          $(this).trigger('formUpdated');
+        });
+    }
+  };
+
+  /**
+   * Prevents consecutive form submissions of identical form values.
+   *
+   * Repetitive form submissions that would submit the identical form values are
+   * prevented, unless the form values are different to the previously submitted
+   * values.
+   *
+   * This is a simplified re-implementation of a user-agent behavior that should
+   * be natively supported by major web browsers, but at this time, only Firefox
+   * has a built-in protection.
+   *
+   * A form value-based approach ensures that the constraint is triggered for
+   * consecutive, identical form submissions only. Compared to that, a form
+   * button-based approach would (1) rely on [visible] buttons to exist where
+   * technically not required and (2) require more complex state management if
+   * there are multiple buttons in a form.
+   *
+   * This implementation is based on form-level submit events only and relies on
+   * jQuery's serialize() method to determine submitted form values. As such, the
+   * following limitations exist:
+   *
+   * - Event handlers on form buttons that preventDefault() do not receive a
+   *   double-submit protection. That is deemed to be fine, since such button
+   *   events typically trigger reversible client-side or server-side operations
+   *   that are local to the context of a form only.
+   * - Changed values in advanced form controls, such as file inputs, are not part
+   *   of the form values being compared between consecutive form submits (due to
+   *   limitations of jQuery.serialize()). That is deemed to be acceptable,
+   *   because if the user forgot to attach a file, then the size of HTTP payload
+   *   will most likely be small enough to be fully passed to the server endpoint
+   *   within (milli)seconds. If a user mistakenly attached a wrong file and is
+   *   technically versed enough to cancel the form submission (and HTTP payload)
+   *   in order to attach a different file, then that edge-case is not supported
+   *   here.
+   *
+   * Lastly, all forms submitted via HTTP GET are idempotent by definition of HTTP
+   * standards, so excluded in this implementation.
+   */
+  Drupal.behaviors.formSingleSubmit = {
+    attach: function () {
+      function onFormSubmit(e) {
+        var $form = $(e.currentTarget);
+        var formValues = $form.serialize();
+        var previousValues = $form.attr('data-drupal-form-submit-last');
+        if (previousValues === formValues) {
+          e.preventDefault();
+        }
+        else {
+          $form.attr('data-drupal-form-submit-last', formValues);
+        }
       }
+
+      $('body').once('form-single-submit')
+        .on('submit.singleSubmit', 'form:not([method~="GET"])', onFormSubmit);
     }
+  };
 
-    $('body').once('form-single-submit')
-      .on('submit.singleSubmit', 'form:not([method~="GET"])', onFormSubmit);
+
+  /**
+   * Sends a 'formUpdated' event each time a form element is modified.
+   */
+  function triggerFormUpdated(element) {
+    $(element).trigger('formUpdated');
   }
-};
-
-
-/**
- * Sends a 'formUpdated' event each time a form element is modified.
- */
-function triggerFormUpdated (element) {
- $(element).trigger('formUpdated');
-}
-
-/**
- * Collects the IDs of all form fields in the given form.
- *
- * @param {HTMLFormElement} form
- * @return {Array}
- */
-function fieldsList (form) {
-  var $fieldList = $(form).find('[name]').map(function (index, element) {
-    // We use id to avoid name duplicates on radio fields and filter out
-    // elements with a name but no id.
-    return element.getAttribute('id');
-  });
-  // Return a true array.
-  return $.makeArray($fieldList);
-}
-
-/**
- * Triggers the 'formUpdated' event on form elements when they are modified.
- */
-Drupal.behaviors.formUpdated = {
- attach: function (context) {
-   var $context = $(context);
-   var contextIsForm = $context.is('form');
-   var $forms = (contextIsForm ? $context : $context.find('form')).once('form-updated');
-
-
-   if ($forms.length) {
-     // Initialize form behaviors, use $.makeArray to be able to use native
-     // forEach array method and have the callback parameters in the right order.
-     $.makeArray($forms).forEach(function (form) {
-       var events = 'change.formUpdated keypress.formUpdated';
-       var eventHandler = debounce(function (event) { triggerFormUpdated(event.target); }, 300);
-       var formFields = fieldsList(form).join(',');
-
-       form.setAttribute('data-drupal-form-fields', formFields);
-       $(form).on(events, eventHandler);
-     });
-   }
-   // On ajax requests context is the form element.
-   if (contextIsForm) {
-    var formFields = fieldsList(context).join(',');
-    // @todo replace with form.getAttribute() when #1979468 is in.
-    var currentFields = $(context).attr('data-drupal-form-fields');
-    // if there has been a change in the fields or their order, trigger
-    // formUpdated.
-    if (formFields !== currentFields) {
-      triggerFormUpdated(context);
-    }
+
+  /**
+   * Collects the IDs of all form fields in the given form.
+   *
+   * @param {HTMLFormElement} form
+   * @return {Array}
+   */
+  function fieldsList(form) {
+    var $fieldList = $(form).find('[name]').map(function (index, element) {
+      // We use id to avoid name duplicates on radio fields and filter out
+      // elements with a name but no id.
+      return element.getAttribute('id');
+    });
+    // Return a true array.
+    return $.makeArray($fieldList);
   }
 
- },
- detach: function (context, settings, trigger) {
-   var $context = $(context);
-   var contextIsForm = $context.is('form');
-   if (trigger === 'unload') {
-     var $forms = (contextIsForm ? $context : $context.find('form')).removeOnce('form-updated');
-     if ($forms.length) {
-       $.makeArray($forms).forEach(function (form) {
-         form.removeAttribute('data-drupal-form-fields');
-         $(form).off('.formUpdated');
-       });
-     }
-   }
- }
-};
-
-/**
- * Prepopulate form fields with information from the visitor cookie.
- */
-Drupal.behaviors.fillUserInfoFromCookie = {
-  attach: function (context, settings) {
-    var userInfo = ['name', 'mail', 'homepage'];
-    $('form.user-info-from-cookie').once('user-info-from-cookie', function () {
-      var $formContext = $(this);
-      var i, il, $element, cookie;
-      for (i = 0, il = userInfo.length; i < il; i += 1) {
-        $element = $formContext.find('[name=' + userInfo[i] + ']');
-        cookie = $.cookie('Drupal.visitor.' + userInfo[i]);
-        if ($element.length && cookie) {
-          $element.val(cookie);
+  /**
+   * Triggers the 'formUpdated' event on form elements when they are modified.
+   */
+  Drupal.behaviors.formUpdated = {
+    attach: function (context) {
+      var $context = $(context);
+      var contextIsForm = $context.is('form');
+      var $forms = (contextIsForm ? $context : $context.find('form')).once('form-updated');
+
+
+      if ($forms.length) {
+        // Initialize form behaviors, use $.makeArray to be able to use native
+        // forEach array method and have the callback parameters in the right order.
+        $.makeArray($forms).forEach(function (form) {
+          var events = 'change.formUpdated keypress.formUpdated';
+          var eventHandler = debounce(function (event) { triggerFormUpdated(event.target); }, 300);
+          var formFields = fieldsList(form).join(',');
+
+          form.setAttribute('data-drupal-form-fields', formFields);
+          $(form).on(events, eventHandler);
+        });
+      }
+      // On ajax requests context is the form element.
+      if (contextIsForm) {
+        var formFields = fieldsList(context).join(',');
+        // @todo replace with form.getAttribute() when #1979468 is in.
+        var currentFields = $(context).attr('data-drupal-form-fields');
+        // if there has been a change in the fields or their order, trigger
+        // formUpdated.
+        if (formFields !== currentFields) {
+          triggerFormUpdated(context);
         }
       }
-    });
-  }
-};
+
+    },
+    detach: function (context, settings, trigger) {
+      var $context = $(context);
+      var contextIsForm = $context.is('form');
+      if (trigger === 'unload') {
+        var $forms = (contextIsForm ? $context : $context.find('form')).removeOnce('form-updated');
+        if ($forms.length) {
+          $.makeArray($forms).forEach(function (form) {
+            form.removeAttribute('data-drupal-form-fields');
+            $(form).off('.formUpdated');
+          });
+        }
+      }
+    }
+  };
+
+  /**
+   * Prepopulate form fields with information from the visitor cookie.
+   */
+  Drupal.behaviors.fillUserInfoFromCookie = {
+    attach: function (context, settings) {
+      var userInfo = ['name', 'mail', 'homepage'];
+      $('form.user-info-from-cookie').once('user-info-from-cookie', function () {
+        var $formContext = $(this);
+        var i, il, $element, cookie;
+        for (i = 0, il = userInfo.length; i < il; i += 1) {
+          $element = $formContext.find('[name=' + userInfo[i] + ']');
+          cookie = $.cookie('Drupal.visitor.' + userInfo[i]);
+          if ($element.length && cookie) {
+            $element.val(cookie);
+          }
+        }
+      });
+    }
+  };
 
 })(jQuery, Drupal, Drupal.debounce);
diff --git a/core/misc/machine-name.js b/core/misc/machine-name.js
index f399e20..3a9fa03 100644
--- a/core/misc/machine-name.js
+++ b/core/misc/machine-name.js
@@ -1,171 +1,171 @@
 (function ($, Drupal, drupalSettings) {
 
-"use strict";
+  "use strict";
 
-/**
- * Attach the machine-readable name form element behavior.
- */
-Drupal.behaviors.machineName = {
   /**
-   * Attaches the behavior.
-   *
-   * @param settings.machineName
-   *   A list of elements to process, keyed by the HTML ID of the form element
-   *   containing the human-readable value. Each element is an object defining
-   *   the following properties:
-   *   - target: The HTML ID of the machine name form element.
-   *   - suffix: The HTML ID of a container to show the machine name preview in
-   *     (usually a field suffix after the human-readable name form element).
-   *   - label: The label to show for the machine name preview.
-   *   - replace_pattern: A regular expression (without modifiers) matching
-   *     disallowed characters in the machine name; e.g., '[^a-z0-9]+'.
-   *   - replace: A character to replace disallowed characters with; e.g., '_'
-   *     or '-'.
-   *   - standalone: Whether the preview should stay in its own element rather
-   *     than the suffix of the source element.
-   *   - field_prefix: The #field_prefix of the form element.
-   *   - field_suffix: The #field_suffix of the form element.
+   * Attach the machine-readable name form element behavior.
    */
-  attach: function (context, settings) {
-    var self = this;
-    var $context = $(context);
-    var source_id, options, machine, eventData;
+  Drupal.behaviors.machineName = {
+    /**
+     * Attaches the behavior.
+     *
+     * @param settings.machineName
+     *   A list of elements to process, keyed by the HTML ID of the form element
+     *   containing the human-readable value. Each element is an object defining
+     *   the following properties:
+     *   - target: The HTML ID of the machine name form element.
+     *   - suffix: The HTML ID of a container to show the machine name preview in
+     *     (usually a field suffix after the human-readable name form element).
+     *   - label: The label to show for the machine name preview.
+     *   - replace_pattern: A regular expression (without modifiers) matching
+     *     disallowed characters in the machine name; e.g., '[^a-z0-9]+'.
+     *   - replace: A character to replace disallowed characters with; e.g., '_'
+     *     or '-'.
+     *   - standalone: Whether the preview should stay in its own element rather
+     *     than the suffix of the source element.
+     *   - field_prefix: The #field_prefix of the form element.
+     *   - field_suffix: The #field_suffix of the form element.
+     */
+    attach: function (context, settings) {
+      var self = this;
+      var $context = $(context);
+      var source_id, options, machine, eventData;
 
-    function clickEditHandler(e) {
-      var data = e.data;
-      e.preventDefault();
-      data.$wrapper.show();
-      data.$target.trigger('focus');
-      data.$suffix.hide();
-      data.$source.off('.machineName');
-    }
-
-    function machineNameHandler(e) {
-      var data = e.data;
-      var settings = data.options;
-      var baseValue = $(e.target).val();
-
-      var rx = new RegExp(settings.replace_pattern, 'g');
-      var expected = baseValue.toLowerCase().replace(rx, settings.replace).substr(0, settings.maxlength);
-
-      if(baseValue.toLowerCase() !== expected) {
-        self.transliterate(baseValue, settings).done(function (machine) {
-          self.showMachineName(machine.substr(0, settings.maxlength), data);
-        });
-      }
-      else {
-        self.showMachineName(expected, data);
+      function clickEditHandler(e) {
+        var data = e.data;
+        e.preventDefault();
+        data.$wrapper.show();
+        data.$target.trigger('focus');
+        data.$suffix.hide();
+        data.$source.off('.machineName');
       }
-    }
 
-    for (source_id in settings.machineName) {
-      if (settings.machineName.hasOwnProperty(source_id)) {
-        options =  settings.machineName[source_id];
+      function machineNameHandler(e) {
+        var data = e.data;
+        var settings = data.options;
+        var baseValue = $(e.target).val();
 
-        var $source = $context.find(source_id).addClass('machine-name-source').once('machine-name');
-        var $target = $context.find(options.target).addClass('machine-name-target');
-        var $suffix = $context.find(options.suffix);
-        var $wrapper = $target.closest('.form-item');
-        // All elements have to exist.
-        if (!$source.length || !$target.length || !$suffix.length || !$wrapper.length) {
-          return;
-        }
-        // Skip processing upon a form validation error on the machine name.
-        if ($target.hasClass('error')) {
-          return;
-        }
-        // Figure out the maximum length for the machine name.
-        options.maxlength = $target.attr('maxlength');
-        // Hide the form item container of the machine name form element.
-        $wrapper.hide();
-        // Determine the initial machine name value. Unless the machine name form
-        // element is disabled or not empty, the initial default value is based on
-        // the human-readable form element value.
-        if ($target.is(':disabled') || $target.val() !== '') {
-          machine = $target.val();
+        var rx = new RegExp(settings.replace_pattern, 'g');
+        var expected = baseValue.toLowerCase().replace(rx, settings.replace).substr(0, settings.maxlength);
+
+        if (baseValue.toLowerCase() !== expected) {
+          self.transliterate(baseValue, settings).done(function (machine) {
+            self.showMachineName(machine.substr(0, settings.maxlength), data);
+          });
         }
         else {
-          machine = self.transliterate($source.val(), options);
-        }
-        // Append the machine name preview to the source field.
-        var $preview = $('<span class="machine-name-value">' + options.field_prefix + Drupal.checkPlain(machine) + options.field_suffix + '</span>');
-        $suffix.empty();
-        if (options.label) {
-          $suffix.append(' ').append('<span class="machine-name-label">' + options.label + ':</span>');
+          self.showMachineName(expected, data);
         }
-        $suffix.append(' ').append($preview);
+      }
 
-        // If the machine name cannot be edited, stop further processing.
-        if ($target.is(':disabled')) {
-          return;
-        }
+      for (source_id in settings.machineName) {
+        if (settings.machineName.hasOwnProperty(source_id)) {
+          options = settings.machineName[source_id];
+
+          var $source = $context.find(source_id).addClass('machine-name-source').once('machine-name');
+          var $target = $context.find(options.target).addClass('machine-name-target');
+          var $suffix = $context.find(options.suffix);
+          var $wrapper = $target.closest('.form-item');
+          // All elements have to exist.
+          if (!$source.length || !$target.length || !$suffix.length || !$wrapper.length) {
+            return;
+          }
+          // Skip processing upon a form validation error on the machine name.
+          if ($target.hasClass('error')) {
+            return;
+          }
+          // Figure out the maximum length for the machine name.
+          options.maxlength = $target.attr('maxlength');
+          // Hide the form item container of the machine name form element.
+          $wrapper.hide();
+          // Determine the initial machine name value. Unless the machine name form
+          // element is disabled or not empty, the initial default value is based on
+          // the human-readable form element value.
+          if ($target.is(':disabled') || $target.val() !== '') {
+            machine = $target.val();
+          }
+          else {
+            machine = self.transliterate($source.val(), options);
+          }
+          // Append the machine name preview to the source field.
+          var $preview = $('<span class="machine-name-value">' + options.field_prefix + Drupal.checkPlain(machine) + options.field_suffix + '</span>');
+          $suffix.empty();
+          if (options.label) {
+            $suffix.append(' ').append('<span class="machine-name-label">' + options.label + ':</span>');
+          }
+          $suffix.append(' ').append($preview);
 
-        eventData = {
-          $source: $source,
-          $target: $target,
-          $suffix: $suffix,
-          $wrapper: $wrapper,
-          $preview: $preview,
-          options: options
-        };
-        // If it is editable, append an edit link.
-        var $link = $('<span class="admin-link"><button type="button" class="link">' + Drupal.t('Edit') + '</button></span>').on('click', eventData, clickEditHandler);
-        $suffix.append(' ').append($link);
+          // If the machine name cannot be edited, stop further processing.
+          if ($target.is(':disabled')) {
+            return;
+          }
 
-        // Preview the machine name in realtime when the human-readable name
-        // changes, but only if there is no machine name yet; i.e., only upon
-        // initial creation, not when editing.
-        if ($target.val() === '') {
-          $source.on('keyup.machineName change.machineName input.machineName', eventData, machineNameHandler)
-          // Initialize machine name preview.
-          .trigger('keyup');
+          eventData = {
+            $source: $source,
+            $target: $target,
+            $suffix: $suffix,
+            $wrapper: $wrapper,
+            $preview: $preview,
+            options: options
+          };
+          // If it is editable, append an edit link.
+          var $link = $('<span class="admin-link"><button type="button" class="link">' + Drupal.t('Edit') + '</button></span>').on('click', eventData, clickEditHandler);
+          $suffix.append(' ').append($link);
+
+          // Preview the machine name in realtime when the human-readable name
+          // changes, but only if there is no machine name yet; i.e., only upon
+          // initial creation, not when editing.
+          if ($target.val() === '') {
+            $source.on('keyup.machineName change.machineName input.machineName', eventData, machineNameHandler)
+              // Initialize machine name preview.
+              .trigger('keyup');
+          }
         }
       }
-    }
-  },
+    },
 
-  showMachineName: function (machine, data) {
-    var settings = data.options;
-    // Set the machine name to the transliterated value.
-    if (machine !== '') {
-      if (machine !== settings.replace) {
+    showMachineName: function (machine, data) {
+      var settings = data.options;
+      // Set the machine name to the transliterated value.
+      if (machine !== '') {
+        if (machine !== settings.replace) {
+          data.$target.val(machine);
+          data.$preview.html(settings.field_prefix + Drupal.checkPlain(machine) + settings.field_suffix);
+        }
+        data.$suffix.show();
+      }
+      else {
+        data.$suffix.hide();
         data.$target.val(machine);
-        data.$preview.html(settings.field_prefix + Drupal.checkPlain(machine) + settings.field_suffix);
+        data.$preview.empty();
       }
-      data.$suffix.show();
-    }
-    else {
-      data.$suffix.hide();
-      data.$target.val(machine);
-      data.$preview.empty();
-    }
-  },
+    },
 
-  /**
-   * Transliterate a human-readable name to a machine name.
-   *
-   * @param source
-   *   A string to transliterate.
-   * @param settings
-   *   The machine name settings for the corresponding field, containing:
-   *   - replace_pattern: A regular expression (without modifiers) matching
-   *     disallowed characters in the machine name; e.g., '[^a-z0-9]+'.
-   *   - replace: A character to replace disallowed characters with; e.g., '_'
-   *     or '-'.
-   *   - maxlength: The maximum length of the machine name.
-   *
-   * @return
-   *   The transliterated source string.
-   */
-  transliterate: function (source, settings) {
-    return $.get(drupalSettings.path.basePath + 'machine_name/transliterate', {
-      text: source,
-      langcode: drupalSettings.langcode,
-      replace_pattern: settings.replace_pattern,
-      replace: settings.replace,
-      lowercase: true
-    });
-  }
-};
+    /**
+     * Transliterate a human-readable name to a machine name.
+     *
+     * @param source
+     *   A string to transliterate.
+     * @param settings
+     *   The machine name settings for the corresponding field, containing:
+     *   - replace_pattern: A regular expression (without modifiers) matching
+     *     disallowed characters in the machine name; e.g., '[^a-z0-9]+'.
+     *   - replace: A character to replace disallowed characters with; e.g., '_'
+     *     or '-'.
+     *   - maxlength: The maximum length of the machine name.
+     *
+     * @return
+     *   The transliterated source string.
+     */
+    transliterate: function (source, settings) {
+      return $.get(drupalSettings.path.basePath + 'machine_name/transliterate', {
+        text: source,
+        langcode: drupalSettings.langcode,
+        replace_pattern: settings.replace_pattern,
+        replace: settings.replace,
+        lowercase: true
+      });
+    }
+  };
 
 })(jQuery, Drupal, drupalSettings);
diff --git a/core/misc/matchmedia.js b/core/misc/matchmedia.js
index a26723a..4d23bae 100644
--- a/core/misc/matchmedia.js
+++ b/core/misc/matchmedia.js
@@ -33,7 +33,7 @@ window.matchMedia = window.matchMedia || (function (doc, window, Drupal) {
    * @param {String} q
    *   A media query e.g. "screen" or "screen and (min-width: 28em)".
    */
-  function MediaQueryList (q) {
+  function MediaQueryList(q) {
     this.media = q;
     this.matches = false;
     this.check.call(this);
diff --git a/core/misc/progress.js b/core/misc/progress.js
index e3e000f..e54c26d 100644
--- a/core/misc/progress.js
+++ b/core/misc/progress.js
@@ -1,113 +1,113 @@
 (function ($) {
 
-"use strict";
+  "use strict";
 
-/**
- * A progressbar object. Initialized with the given id. Must be inserted into
- * the DOM afterwards through progressBar.element.
- *
- * method is the function which will perform the HTTP request to get the
- * progress bar state. Either "GET" or "POST".
- *
- * e.g. pb = new Drupal.ProgressBar('myProgressBar');
- *      some_element.appendChild(pb.element);
- */
-Drupal.ProgressBar = function (id, updateCallback, method, errorCallback) {
-  this.id = id;
-  this.method = method || 'GET';
-  this.updateCallback = updateCallback;
-  this.errorCallback = errorCallback;
-
-  // The WAI-ARIA setting aria-live="polite" will announce changes after users
-  // have completed their current activity and not interrupt the screen reader.
-  this.element = $('<div class="progress" aria-live="polite"></div>').attr('id', id);
-  this.element.html('<div class="progress__label">&nbsp;</div>' +
-                    '<div class="progress__track"><div class="progress__bar"></div></div>' +
-                    '<div class="progress__percentage"></div>' +
-                    '<div class="progress__description">&nbsp;</div>');
-};
-
-$.extend(Drupal.ProgressBar.prototype, {
   /**
-   * Set the percentage and status message for the progressbar.
+   * A progressbar object. Initialized with the given id. Must be inserted into
+   * the DOM afterwards through progressBar.element.
+   *
+   * method is the function which will perform the HTTP request to get the
+   * progress bar state. Either "GET" or "POST".
+   *
+   * e.g. pb = new Drupal.ProgressBar('myProgressBar');
+   *      some_element.appendChild(pb.element);
    */
-  setProgress: function (percentage, message, label) {
-    if (percentage >= 0 && percentage <= 100) {
-      $(this.element).find('div.progress__bar').css('width', percentage + '%');
-      $(this.element).find('div.progress__percentage').html(percentage + '%');
-    }
-    $('div.progress__description', this.element).html(message);
-    $('div.progress__label', this.element).html(label);
-    if (this.updateCallback) {
-      this.updateCallback(percentage, message, this);
-    }
-  },
+  Drupal.ProgressBar = function (id, updateCallback, method, errorCallback) {
+    this.id = id;
+    this.method = method || 'GET';
+    this.updateCallback = updateCallback;
+    this.errorCallback = errorCallback;
 
-  /**
-   * Start monitoring progress via Ajax.
-   */
-  startMonitoring: function (uri, delay) {
-    this.delay = delay;
-    this.uri = uri;
-    this.sendPing();
-  },
+    // The WAI-ARIA setting aria-live="polite" will announce changes after users
+    // have completed their current activity and not interrupt the screen reader.
+    this.element = $('<div class="progress" aria-live="polite"></div>').attr('id', id);
+    this.element.html('<div class="progress__label">&nbsp;</div>' +
+      '<div class="progress__track"><div class="progress__bar"></div></div>' +
+      '<div class="progress__percentage"></div>' +
+      '<div class="progress__description">&nbsp;</div>');
+  };
 
-  /**
-   * Stop monitoring progress via Ajax.
-   */
-  stopMonitoring: function () {
-    clearTimeout(this.timer);
-    // This allows monitoring to be stopped from within the callback.
-    this.uri = null;
-  },
+  $.extend(Drupal.ProgressBar.prototype, {
+    /**
+     * Set the percentage and status message for the progressbar.
+     */
+    setProgress: function (percentage, message, label) {
+      if (percentage >= 0 && percentage <= 100) {
+        $(this.element).find('div.progress__bar').css('width', percentage + '%');
+        $(this.element).find('div.progress__percentage').html(percentage + '%');
+      }
+      $('div.progress__description', this.element).html(message);
+      $('div.progress__label', this.element).html(label);
+      if (this.updateCallback) {
+        this.updateCallback(percentage, message, this);
+      }
+    },
 
-  /**
-   * Request progress data from server.
-   */
-  sendPing: function () {
-    if (this.timer) {
+    /**
+     * Start monitoring progress via Ajax.
+     */
+    startMonitoring: function (uri, delay) {
+      this.delay = delay;
+      this.uri = uri;
+      this.sendPing();
+    },
+
+    /**
+     * Stop monitoring progress via Ajax.
+     */
+    stopMonitoring: function () {
       clearTimeout(this.timer);
-    }
-    if (this.uri) {
-      var pb = this;
-      // When doing a post request, you need non-null data. Otherwise a
-      // HTTP 411 or HTTP 406 (with Apache mod_security) error may result.
-      $.ajax({
-        type: this.method,
-        url: this.uri,
-        data: '',
-        dataType: 'json',
-        success: function (progress) {
-          // Display errors.
-          if (progress.status === 0) {
-            pb.displayError(progress.data);
-            return;
+      // This allows monitoring to be stopped from within the callback.
+      this.uri = null;
+    },
+
+    /**
+     * Request progress data from server.
+     */
+    sendPing: function () {
+      if (this.timer) {
+        clearTimeout(this.timer);
+      }
+      if (this.uri) {
+        var pb = this;
+        // When doing a post request, you need non-null data. Otherwise a
+        // HTTP 411 or HTTP 406 (with Apache mod_security) error may result.
+        $.ajax({
+          type: this.method,
+          url: this.uri,
+          data: '',
+          dataType: 'json',
+          success: function (progress) {
+            // Display errors.
+            if (progress.status === 0) {
+              pb.displayError(progress.data);
+              return;
+            }
+            // Update display.
+            pb.setProgress(progress.percentage, progress.message, progress.label);
+            // Schedule next timer.
+            pb.timer = setTimeout(function () { pb.sendPing(); }, pb.delay);
+          },
+          error: function (xmlhttp) {
+            var e = new Drupal.AjaxError(xmlhttp, pb.uri);
+            pb.displayError('<pre>' + e.message + '</pre>');
           }
-          // Update display.
-          pb.setProgress(progress.percentage, progress.message, progress.label);
-          // Schedule next timer.
-          pb.timer = setTimeout(function () { pb.sendPing(); }, pb.delay);
-        },
-        error: function (xmlhttp) {
-          var e = new Drupal.AjaxError(xmlhttp, pb.uri);
-          pb.displayError('<pre>' + e.message + '</pre>');
-        }
-      });
-    }
-  },
+        });
+      }
+    },
 
-  /**
-   * Display errors on the page.
-   */
-  displayError: function (string) {
-    var error = $('<div class="messages messages--error"></div>').html(string);
-    $(this.element).before(error).hide();
+    /**
+     * Display errors on the page.
+     */
+    displayError: function (string) {
+      var error = $('<div class="messages messages--error"></div>').html(string);
+      $(this.element).before(error).hide();
 
-    if (this.errorCallback) {
-      this.errorCallback(this);
+      if (this.errorCallback) {
+        this.errorCallback(this);
+      }
     }
-  }
-});
+  });
 
 
 })(jQuery);
diff --git a/core/misc/states.js b/core/misc/states.js
index 206d521..8b640e8 100644
--- a/core/misc/states.js
+++ b/core/misc/states.js
@@ -1,579 +1,579 @@
 (function ($) {
 
-"use strict";
-
-/**
- * The base States namespace.
- *
- * Having the local states variable allows us to use the States namespace
- * without having to always declare "Drupal.states".
- */
-var states = Drupal.states = {
-  // An array of functions that should be postponed.
-  postponed: []
-};
-
-/**
- * Attaches the states.
- */
-Drupal.behaviors.states = {
-  attach: function (context, settings) {
-    var $states = $(context).find('[data-drupal-states]');
-    var config, state;
-    for (var i = 0, il = $states.length; i < il; i += 1) {
-      config = JSON.parse($states[i].getAttribute('data-drupal-states'));
-      for (state in config) {
-        if (config.hasOwnProperty(state)) {
-          new states.Dependent({
-            element: $($states[i]),
-            state: states.State.sanitize(state),
-            constraints: config[state]
-          });
+  "use strict";
+
+  /**
+   * The base States namespace.
+   *
+   * Having the local states variable allows us to use the States namespace
+   * without having to always declare "Drupal.states".
+   */
+  var states = Drupal.states = {
+    // An array of functions that should be postponed.
+    postponed: []
+  };
+
+  /**
+   * Attaches the states.
+   */
+  Drupal.behaviors.states = {
+    attach: function (context, settings) {
+      var $states = $(context).find('[data-drupal-states]');
+      var config, state;
+      for (var i = 0, il = $states.length; i < il; i += 1) {
+        config = JSON.parse($states[i].getAttribute('data-drupal-states'));
+        for (state in config) {
+          if (config.hasOwnProperty(state)) {
+            new states.Dependent({
+              element: $($states[i]),
+              state: states.State.sanitize(state),
+              constraints: config[state]
+            });
+          }
         }
       }
-    }
 
-    // Execute all postponed functions now.
-    while (states.postponed.length) {
-      (states.postponed.shift())();
-    }
-  }
-};
-
-/**
- * Object representing an element that depends on other elements.
- *
- * @param args
- *   Object with the following keys (all of which are required):
- *   - element: A jQuery object of the dependent element
- *   - state: A State object describing the state that is dependent
- *   - constraints: An object with dependency specifications. Lists all elements
- *     that this element depends on. It can be nested and can contain arbitrary
- *     AND and OR clauses.
- */
-states.Dependent = function (args) {
-  $.extend(this, { values: {}, oldValue: null }, args);
-
-  this.dependees = this.getDependees();
-  for (var selector in this.dependees) {
-    if (this.dependees.hasOwnProperty(selector)) {
-      this.initializeDependee(selector, this.dependees[selector]);
+      // Execute all postponed functions now.
+      while (states.postponed.length) {
+        (states.postponed.shift())();
+      }
     }
-  }
-};
-
-/**
- * Comparison functions for comparing the value of an element with the
- * specification from the dependency settings. If the object type can't be
- * found in this list, the === operator is used by default.
- */
-states.Dependent.comparisons = {
-  'RegExp': function (reference, value) {
-    return reference.test(value);
-  },
-  'Function': function (reference, value) {
-    // The "reference" variable is a comparison function.
-    return reference(value);
-  },
-  'Number': function (reference, value) {
-    // If "reference" is a number and "value" is a string, then cast reference
-    // as a string before applying the strict comparison in compare(). Otherwise
-    // numeric keys in the form's #states array fail to match string values
-    // returned from jQuery's val().
-    return (typeof value === 'string') ? compare(reference.toString(), value) : compare(reference, value);
-  }
-};
+  };
 
-states.Dependent.prototype = {
   /**
-   * Initializes one of the elements this dependent depends on.
+   * Object representing an element that depends on other elements.
    *
-   * @param selector
-   *   The CSS selector describing the dependee.
-   * @param dependeeStates
-   *   The list of states that have to be monitored for tracking the
-   *   dependee's compliance status.
+   * @param args
+   *   Object with the following keys (all of which are required):
+   *   - element: A jQuery object of the dependent element
+   *   - state: A State object describing the state that is dependent
+   *   - constraints: An object with dependency specifications. Lists all elements
+   *     that this element depends on. It can be nested and can contain arbitrary
+   *     AND and OR clauses.
    */
-  initializeDependee: function (selector, dependeeStates) {
-    var state, self = this;
+  states.Dependent = function (args) {
+    $.extend(this, { values: {}, oldValue: null }, args);
 
-    function stateEventHandler(e) {
-      self.update(e.data.selector, e.data.state, e.value);
+    this.dependees = this.getDependees();
+    for (var selector in this.dependees) {
+      if (this.dependees.hasOwnProperty(selector)) {
+        this.initializeDependee(selector, this.dependees[selector]);
+      }
     }
+  };
 
-    // Cache for the states of this dependee.
-    this.values[selector] = {};
+  /**
+   * Comparison functions for comparing the value of an element with the
+   * specification from the dependency settings. If the object type can't be
+   * found in this list, the === operator is used by default.
+   */
+  states.Dependent.comparisons = {
+    'RegExp': function (reference, value) {
+      return reference.test(value);
+    },
+    'Function': function (reference, value) {
+      // The "reference" variable is a comparison function.
+      return reference(value);
+    },
+    'Number': function (reference, value) {
+      // If "reference" is a number and "value" is a string, then cast reference
+      // as a string before applying the strict comparison in compare(). Otherwise
+      // numeric keys in the form's #states array fail to match string values
+      // returned from jQuery's val().
+      return (typeof value === 'string') ? compare(reference.toString(), value) : compare(reference, value);
+    }
+  };
+
+  states.Dependent.prototype = {
+    /**
+     * Initializes one of the elements this dependent depends on.
+     *
+     * @param selector
+     *   The CSS selector describing the dependee.
+     * @param dependeeStates
+     *   The list of states that have to be monitored for tracking the
+     *   dependee's compliance status.
+     */
+    initializeDependee: function (selector, dependeeStates) {
+      var state, self = this;
+
+      function stateEventHandler(e) {
+        self.update(e.data.selector, e.data.state, e.value);
+      }
 
-    for (var i in dependeeStates) {
-      if (dependeeStates.hasOwnProperty(i)) {
-        state = dependeeStates[i];
-        // Make sure we're not initializing this selector/state combination twice.
-        if ($.inArray(state, dependeeStates) === -1) {
-          continue;
-        }
+      // Cache for the states of this dependee.
+      this.values[selector] = {};
 
-        state = states.State.sanitize(state);
+      for (var i in dependeeStates) {
+        if (dependeeStates.hasOwnProperty(i)) {
+          state = dependeeStates[i];
+          // Make sure we're not initializing this selector/state combination twice.
+          if ($.inArray(state, dependeeStates) === -1) {
+            continue;
+          }
+
+          state = states.State.sanitize(state);
 
-        // Initialize the value of this state.
-        this.values[selector][state.name] = null;
+          // Initialize the value of this state.
+          this.values[selector][state.name] = null;
 
-        // Monitor state changes of the specified state for this dependee.
-        $(selector).on('state:' + state, {selector: selector, state: state}, stateEventHandler);
+          // Monitor state changes of the specified state for this dependee.
+          $(selector).on('state:' + state, {selector: selector, state: state}, stateEventHandler);
 
-        // Make sure the event we just bound ourselves to is actually fired.
-        new states.Trigger({ selector: selector, state: state });
+          // Make sure the event we just bound ourselves to is actually fired.
+          new states.Trigger({ selector: selector, state: state });
+        }
       }
-    }
-  },
+    },
 
-  /**
-   * Compares a value with a reference value.
-   *
-   * @param reference
-   *   The value used for reference.
-   * @param selector
-   *   CSS selector describing the dependee.
-   * @param state
-   *   A State object describing the dependee's updated state.
-   *
-   * @return
-   *   true or false.
-   */
-  compare: function (reference, selector, state) {
-    var value = this.values[selector][state.name];
-    if (reference.constructor.name in states.Dependent.comparisons) {
-      // Use a custom compare function for certain reference value types.
-      return states.Dependent.comparisons[reference.constructor.name](reference, value);
-    }
-    else {
-      // Do a plain comparison otherwise.
-      return compare(reference, value);
-    }
-  },
+    /**
+     * Compares a value with a reference value.
+     *
+     * @param reference
+     *   The value used for reference.
+     * @param selector
+     *   CSS selector describing the dependee.
+     * @param state
+     *   A State object describing the dependee's updated state.
+     *
+     * @return
+     *   true or false.
+     */
+    compare: function (reference, selector, state) {
+      var value = this.values[selector][state.name];
+      if (reference.constructor.name in states.Dependent.comparisons) {
+        // Use a custom compare function for certain reference value types.
+        return states.Dependent.comparisons[reference.constructor.name](reference, value);
+      }
+      else {
+        // Do a plain comparison otherwise.
+        return compare(reference, value);
+      }
+    },
 
-  /**
-   * Update the value of a dependee's state.
-   *
-   * @param selector
-   *   CSS selector describing the dependee.
-   * @param state
-   *   A State object describing the dependee's updated state.
-   * @param value
-   *   The new value for the dependee's updated state.
-   */
-  update: function (selector, state, value) {
-    // Only act when the 'new' value is actually new.
-    if (value !== this.values[selector][state.name]) {
-      this.values[selector][state.name] = value;
-      this.reevaluate();
-    }
-  },
+    /**
+     * Update the value of a dependee's state.
+     *
+     * @param selector
+     *   CSS selector describing the dependee.
+     * @param state
+     *   A State object describing the dependee's updated state.
+     * @param value
+     *   The new value for the dependee's updated state.
+     */
+    update: function (selector, state, value) {
+      // Only act when the 'new' value is actually new.
+      if (value !== this.values[selector][state.name]) {
+        this.values[selector][state.name] = value;
+        this.reevaluate();
+      }
+    },
 
-  /**
-   * Triggers change events in case a state changed.
-   */
-  reevaluate: function () {
-    // Check whether any constraint for this dependent state is satisifed.
-    var value = this.verifyConstraints(this.constraints);
-
-    // Only invoke a state change event when the value actually changed.
-    if (value !== this.oldValue) {
-      // Store the new value so that we can compare later whether the value
-      // actually changed.
-      this.oldValue = value;
-
-      // Normalize the value to match the normalized state name.
-      value = invert(value, this.state.invert);
-
-      // By adding "trigger: true", we ensure that state changes don't go into
-      // infinite loops.
-      this.element.trigger({ type: 'state:' + this.state, value: value, trigger: true });
-    }
-  },
+    /**
+     * Triggers change events in case a state changed.
+     */
+    reevaluate: function () {
+      // Check whether any constraint for this dependent state is satisifed.
+      var value = this.verifyConstraints(this.constraints);
+
+      // Only invoke a state change event when the value actually changed.
+      if (value !== this.oldValue) {
+        // Store the new value so that we can compare later whether the value
+        // actually changed.
+        this.oldValue = value;
+
+        // Normalize the value to match the normalized state name.
+        value = invert(value, this.state.invert);
+
+        // By adding "trigger: true", we ensure that state changes don't go into
+        // infinite loops.
+        this.element.trigger({ type: 'state:' + this.state, value: value, trigger: true });
+      }
+    },
 
-  /**
-   * Evaluates child constraints to determine if a constraint is satisfied.
-   *
-   * @param constraints
-   *   A constraint object or an array of constraints.
-   * @param selector
-   *   The selector for these constraints. If undefined, there isn't yet a
-   *   selector that these constraints apply to. In that case, the keys of the
-   *   object are interpreted as the selector if encountered.
-   *
-   * @return
-   *   true or false, depending on whether these constraints are satisfied.
-   */
-  verifyConstraints: function(constraints, selector) {
-    var result;
-    if ($.isArray(constraints)) {
-      // This constraint is an array (OR or XOR).
-      var hasXor = $.inArray('xor', constraints) === -1;
-      for (var i = 0, len = constraints.length; i < len; i++) {
-        if (constraints[i] !== 'xor') {
-          var constraint = this.checkConstraints(constraints[i], selector, i);
-          // Return if this is OR and we have a satisfied constraint or if this
-          // is XOR and we have a second satisfied constraint.
-          if (constraint && (hasXor || result)) {
-            return hasXor;
+    /**
+     * Evaluates child constraints to determine if a constraint is satisfied.
+     *
+     * @param constraints
+     *   A constraint object or an array of constraints.
+     * @param selector
+     *   The selector for these constraints. If undefined, there isn't yet a
+     *   selector that these constraints apply to. In that case, the keys of the
+     *   object are interpreted as the selector if encountered.
+     *
+     * @return
+     *   true or false, depending on whether these constraints are satisfied.
+     */
+    verifyConstraints: function (constraints, selector) {
+      var result;
+      if ($.isArray(constraints)) {
+        // This constraint is an array (OR or XOR).
+        var hasXor = $.inArray('xor', constraints) === -1;
+        for (var i = 0, len = constraints.length; i < len; i++) {
+          if (constraints[i] !== 'xor') {
+            var constraint = this.checkConstraints(constraints[i], selector, i);
+            // Return if this is OR and we have a satisfied constraint or if this
+            // is XOR and we have a second satisfied constraint.
+            if (constraint && (hasXor || result)) {
+              return hasXor;
+            }
+            result = result || constraint;
           }
-          result = result || constraint;
         }
       }
-    }
-    // Make sure we don't try to iterate over things other than objects. This
-    // shouldn't normally occur, but in case the condition definition is bogus,
-    // we don't want to end up with an infinite loop.
-    else if ($.isPlainObject(constraints)) {
-      // This constraint is an object (AND).
-      for (var n in constraints) {
-        if (constraints.hasOwnProperty(n)) {
-          result = ternary(result, this.checkConstraints(constraints[n], selector, n));
-          // False and anything else will evaluate to false, so return when any
-          // false condition is found.
-          if (result === false) { return false; }
+      // Make sure we don't try to iterate over things other than objects. This
+      // shouldn't normally occur, but in case the condition definition is bogus,
+      // we don't want to end up with an infinite loop.
+      else if ($.isPlainObject(constraints)) {
+        // This constraint is an object (AND).
+        for (var n in constraints) {
+          if (constraints.hasOwnProperty(n)) {
+            result = ternary(result, this.checkConstraints(constraints[n], selector, n));
+            // False and anything else will evaluate to false, so return when any
+            // false condition is found.
+            if (result === false) { return false; }
+          }
         }
       }
-    }
-    return result;
-  },
+      return result;
+    },
 
-  /**
-   * Checks whether the value matches the requirements for this constraint.
-   *
-   * @param value
-   *   Either the value of a state or an array/object of constraints. In the
-   *   latter case, resolving the constraint continues.
-   * @param selector
-   *   The selector for this constraint. If undefined, there isn't yet a
-   *   selector that this constraint applies to. In that case, the state key is
-   *   propagates to a selector and resolving continues.
-   * @param state
-   *   The state to check for this constraint. If undefined, resolving
-   *   continues.
-   *   If both selector and state aren't undefined and valid non-numeric
-   *   strings, a lookup for the actual value of that selector's state is
-   *   performed. This parameter is not a State object but a pristine state
-   *   string.
-   *
-   * @return
-   *   true or false, depending on whether this constraint is satisfied.
-   */
-  checkConstraints: function(value, selector, state) {
-    // Normalize the last parameter. If it's non-numeric, we treat it either as
-    // a selector (in case there isn't one yet) or as a trigger/state.
-    if (typeof state !== 'string' || (/[0-9]/).test(state[0])) {
-      state = null;
-    }
-    else if (typeof selector === 'undefined') {
-      // Propagate the state to the selector when there isn't one yet.
-      selector = state;
-      state = null;
-    }
+    /**
+     * Checks whether the value matches the requirements for this constraint.
+     *
+     * @param value
+     *   Either the value of a state or an array/object of constraints. In the
+     *   latter case, resolving the constraint continues.
+     * @param selector
+     *   The selector for this constraint. If undefined, there isn't yet a
+     *   selector that this constraint applies to. In that case, the state key is
+     *   propagates to a selector and resolving continues.
+     * @param state
+     *   The state to check for this constraint. If undefined, resolving
+     *   continues.
+     *   If both selector and state aren't undefined and valid non-numeric
+     *   strings, a lookup for the actual value of that selector's state is
+     *   performed. This parameter is not a State object but a pristine state
+     *   string.
+     *
+     * @return
+     *   true or false, depending on whether this constraint is satisfied.
+     */
+    checkConstraints: function (value, selector, state) {
+      // Normalize the last parameter. If it's non-numeric, we treat it either as
+      // a selector (in case there isn't one yet) or as a trigger/state.
+      if (typeof state !== 'string' || (/[0-9]/).test(state[0])) {
+        state = null;
+      }
+      else if (typeof selector === 'undefined') {
+        // Propagate the state to the selector when there isn't one yet.
+        selector = state;
+        state = null;
+      }
 
-    if (state !== null) {
-      // constraints is the actual constraints of an element to check for.
-      state = states.State.sanitize(state);
-      return invert(this.compare(value, selector, state), state.invert);
-    }
-    else {
-      // Resolve this constraint as an AND/OR operator.
-      return this.verifyConstraints(value, selector);
-    }
-  },
+      if (state !== null) {
+        // constraints is the actual constraints of an element to check for.
+        state = states.State.sanitize(state);
+        return invert(this.compare(value, selector, state), state.invert);
+      }
+      else {
+        // Resolve this constraint as an AND/OR operator.
+        return this.verifyConstraints(value, selector);
+      }
+    },
 
-  /**
-   * Gathers information about all required triggers.
-   */
-  getDependees: function() {
-    var cache = {};
-    // Swivel the lookup function so that we can record all available selector-
-    // state combinations for initialization.
-    var _compare = this.compare;
-    this.compare = function(reference, selector, state) {
-      (cache[selector] || (cache[selector] = [])).push(state.name);
-      // Return nothing (=== undefined) so that the constraint loops are not
-      // broken.
-    };
-
-    // This call doesn't actually verify anything but uses the resolving
-    // mechanism to go through the constraints array, trying to look up each
-    // value. Since we swivelled the compare function, this comparison returns
-    // undefined and lookup continues until the very end. Instead of lookup up
-    // the value, we record that combination of selector and state so that we
-    // can initialize all triggers.
-    this.verifyConstraints(this.constraints);
-    // Restore the original function.
-    this.compare = _compare;
-
-    return cache;
-  }
-};
+    /**
+     * Gathers information about all required triggers.
+     */
+    getDependees: function () {
+      var cache = {};
+      // Swivel the lookup function so that we can record all available selector-
+      // state combinations for initialization.
+      var _compare = this.compare;
+      this.compare = function (reference, selector, state) {
+        (cache[selector] || (cache[selector] = [])).push(state.name);
+        // Return nothing (=== undefined) so that the constraint loops are not
+        // broken.
+      };
+
+      // This call doesn't actually verify anything but uses the resolving
+      // mechanism to go through the constraints array, trying to look up each
+      // value. Since we swivelled the compare function, this comparison returns
+      // undefined and lookup continues until the very end. Instead of lookup up
+      // the value, we record that combination of selector and state so that we
+      // can initialize all triggers.
+      this.verifyConstraints(this.constraints);
+      // Restore the original function.
+      this.compare = _compare;
+
+      return cache;
+    }
+  };
 
-states.Trigger = function (args) {
-  $.extend(this, args);
+  states.Trigger = function (args) {
+    $.extend(this, args);
 
-  if (this.state in states.Trigger.states) {
-    this.element = $(this.selector);
+    if (this.state in states.Trigger.states) {
+      this.element = $(this.selector);
 
-    // Only call the trigger initializer when it wasn't yet attached to this
-    // element. Otherwise we'd end up with duplicate events.
-    if (!this.element.data('trigger:' + this.state)) {
-      this.initialize();
+      // Only call the trigger initializer when it wasn't yet attached to this
+      // element. Otherwise we'd end up with duplicate events.
+      if (!this.element.data('trigger:' + this.state)) {
+        this.initialize();
+      }
     }
-  }
-};
+  };
 
-states.Trigger.prototype = {
-  initialize: function () {
-    var trigger = states.Trigger.states[this.state];
+  states.Trigger.prototype = {
+    initialize: function () {
+      var trigger = states.Trigger.states[this.state];
 
-    if (typeof trigger === 'function') {
-      // We have a custom trigger initialization function.
-      trigger.call(window, this.element);
-    }
-    else {
-      for (var event in trigger) {
-        if (trigger.hasOwnProperty(event)) {
-          this.defaultTrigger(event, trigger[event]);
+      if (typeof trigger === 'function') {
+        // We have a custom trigger initialization function.
+        trigger.call(window, this.element);
+      }
+      else {
+        for (var event in trigger) {
+          if (trigger.hasOwnProperty(event)) {
+            this.defaultTrigger(event, trigger[event]);
+          }
         }
       }
-    }
 
-    // Mark this trigger as initialized for this element.
-    this.element.data('trigger:' + this.state, true);
-  },
+      // Mark this trigger as initialized for this element.
+      this.element.data('trigger:' + this.state, true);
+    },
 
-  defaultTrigger: function (event, valueFn) {
-    var oldValue = valueFn.call(this.element);
+    defaultTrigger: function (event, valueFn) {
+      var oldValue = valueFn.call(this.element);
 
-    // Attach the event callback.
-    this.element.on(event, $.proxy(function (e) {
-      var value = valueFn.call(this.element, e);
-      // Only trigger the event if the value has actually changed.
-      if (oldValue !== value) {
-        this.element.trigger({ type: 'state:' + this.state, value: value, oldValue: oldValue });
-        oldValue = value;
-      }
-    }, this));
+      // Attach the event callback.
+      this.element.on(event, $.proxy(function (e) {
+        var value = valueFn.call(this.element, e);
+        // Only trigger the event if the value has actually changed.
+        if (oldValue !== value) {
+          this.element.trigger({ type: 'state:' + this.state, value: value, oldValue: oldValue });
+          oldValue = value;
+        }
+      }, this));
 
-    states.postponed.push($.proxy(function () {
-      // Trigger the event once for initialization purposes.
-      this.element.trigger({ type: 'state:' + this.state, value: oldValue, oldValue: null });
-    }, this));
-  }
-};
-
-/**
- * This list of states contains functions that are used to monitor the state
- * of an element. Whenever an element depends on the state of another element,
- * one of these trigger functions is added to the dependee so that the
- * dependent element can be updated.
- */
-states.Trigger.states = {
-  // 'empty' describes the state to be monitored
-  empty: {
-    // 'keyup' is the (native DOM) event that we watch for.
-    'keyup': function () {
-      // The function associated to that trigger returns the new value for the
-      // state.
-      return this.val() === '';
-    }
-  },
-
-  checked: {
-    'change': function () {
-      // prop() and attr() only takes the first element into account. To support
-      // selectors matching multiple checkboxes, iterate over all and return
-      // whether any is checked.
-      var checked = false;
-      this.each(function () {
-        // Use prop() here as we want a boolean of the checkbox state.
-        // @see http://api.jquery.com/prop/
-        checked = $(this).prop('checked');
-        // Break the each() loop if this is checked.
-        return !checked;
-      });
-      return checked;
+      states.postponed.push($.proxy(function () {
+        // Trigger the event once for initialization purposes.
+        this.element.trigger({ type: 'state:' + this.state, value: oldValue, oldValue: null });
+      }, this));
     }
-  },
-
-  // For radio buttons, only return the value if the radio button is selected.
-  value: {
-    'keyup': function () {
-      // Radio buttons share the same :input[name="key"] selector.
-      if (this.length > 1) {
-        // Initial checked value of radios is undefined, so we return false.
-        return this.filter(':checked').val() || false;
+  };
+
+  /**
+   * This list of states contains functions that are used to monitor the state
+   * of an element. Whenever an element depends on the state of another element,
+   * one of these trigger functions is added to the dependee so that the
+   * dependent element can be updated.
+   */
+  states.Trigger.states = {
+    // 'empty' describes the state to be monitored
+    empty: {
+      // 'keyup' is the (native DOM) event that we watch for.
+      'keyup': function () {
+        // The function associated to that trigger returns the new value for the
+        // state.
+        return this.val() === '';
       }
-      return this.val();
     },
-    'change': function () {
-      // Radio buttons share the same :input[name="key"] selector.
-      if (this.length > 1) {
-        // Initial checked value of radios is undefined, so we return false.
-        return this.filter(':checked').val() || false;
+
+    checked: {
+      'change': function () {
+        // prop() and attr() only takes the first element into account. To support
+        // selectors matching multiple checkboxes, iterate over all and return
+        // whether any is checked.
+        var checked = false;
+        this.each(function () {
+          // Use prop() here as we want a boolean of the checkbox state.
+          // @see http://api.jquery.com/prop/
+          checked = $(this).prop('checked');
+          // Break the each() loop if this is checked.
+          return !checked;
+        });
+        return checked;
       }
-      return this.val();
-    }
-  },
+    },
 
-  collapsed: {
-    'collapsed': function(e) {
-      return (typeof e !== 'undefined' && 'value' in e) ? e.value : !this.is('[open]');
+    // For radio buttons, only return the value if the radio button is selected.
+    value: {
+      'keyup': function () {
+        // Radio buttons share the same :input[name="key"] selector.
+        if (this.length > 1) {
+          // Initial checked value of radios is undefined, so we return false.
+          return this.filter(':checked').val() || false;
+        }
+        return this.val();
+      },
+      'change': function () {
+        // Radio buttons share the same :input[name="key"] selector.
+        if (this.length > 1) {
+          // Initial checked value of radios is undefined, so we return false.
+          return this.filter(':checked').val() || false;
+        }
+        return this.val();
+      }
+    },
+
+    collapsed: {
+      'collapsed': function (e) {
+        return (typeof e !== 'undefined' && 'value' in e) ? e.value : !this.is('[open]');
+      }
     }
-  }
-};
-
-
-/**
- * A state object is used for describing the state and performing aliasing.
- */
-states.State = function(state) {
-  // We may need the original unresolved name later.
-  this.pristine = this.name = state;
-
-  // Normalize the state name.
-  while (true) {
-    // Iteratively remove exclamation marks and invert the value.
-    while (this.name.charAt(0) === '!') {
-      this.name = this.name.substring(1);
-      this.invert = !this.invert;
+  };
+
+
+  /**
+   * A state object is used for describing the state and performing aliasing.
+   */
+  states.State = function (state) {
+    // We may need the original unresolved name later.
+    this.pristine = this.name = state;
+
+    // Normalize the state name.
+    while (true) {
+      // Iteratively remove exclamation marks and invert the value.
+      while (this.name.charAt(0) === '!') {
+        this.name = this.name.substring(1);
+        this.invert = !this.invert;
+      }
+
+      // Replace the state with its normalized name.
+      if (this.name in states.State.aliases) {
+        this.name = states.State.aliases[this.name];
+      }
+      else {
+        break;
+      }
     }
+  };
 
-    // Replace the state with its normalized name.
-    if (this.name in states.State.aliases) {
-      this.name = states.State.aliases[this.name];
+  /**
+   * Creates a new State object by sanitizing the passed value.
+   */
+  states.State.sanitize = function (state) {
+    if (state instanceof states.State) {
+      return state;
     }
     else {
-      break;
+      return new states.State(state);
     }
-  }
-};
-
-/**
- * Creates a new State object by sanitizing the passed value.
- */
-states.State.sanitize = function (state) {
-  if (state instanceof states.State) {
-    return state;
-  }
-  else {
-    return new states.State(state);
-  }
-};
-
-/**
- * This list of aliases is used to normalize states and associates negated names
- * with their respective inverse state.
- */
-states.State.aliases = {
-  'enabled': '!disabled',
-  'invisible': '!visible',
-  'invalid': '!valid',
-  'untouched': '!touched',
-  'optional': '!required',
-  'filled': '!empty',
-  'unchecked': '!checked',
-  'irrelevant': '!relevant',
-  'expanded': '!collapsed',
-  'open': '!collapsed',
-  'closed': 'collapsed',
-  'readwrite': '!readonly'
-};
-
-states.State.prototype = {
-  invert: false,
+  };
 
   /**
-   * Ensures that just using the state object returns the name.
+   * This list of aliases is used to normalize states and associates negated names
+   * with their respective inverse state.
    */
-  toString: function() {
-    return this.name;
-  }
-};
-
-/**
- * Global state change handlers. These are bound to "document" to cover all
- * elements whose state changes. Events sent to elements within the page
- * bubble up to these handlers. We use this system so that themes and modules
- * can override these state change handlers for particular parts of a page.
- */
-
-$(document).on('state:disabled', function (e) {
-  // Only act when this change was triggered by a dependency and not by the
-  // element monitoring itself.
-  if (e.trigger) {
-    $(e.target)
-      .prop('disabled', e.value)
+  states.State.aliases = {
+    'enabled': '!disabled',
+    'invisible': '!visible',
+    'invalid': '!valid',
+    'untouched': '!touched',
+    'optional': '!required',
+    'filled': '!empty',
+    'unchecked': '!checked',
+    'irrelevant': '!relevant',
+    'expanded': '!collapsed',
+    'open': '!collapsed',
+    'closed': 'collapsed',
+    'readwrite': '!readonly'
+  };
+
+  states.State.prototype = {
+    invert: false,
+
+    /**
+     * Ensures that just using the state object returns the name.
+     */
+    toString: function () {
+      return this.name;
+    }
+  };
+
+  /**
+   * Global state change handlers. These are bound to "document" to cover all
+   * elements whose state changes. Events sent to elements within the page
+   * bubble up to these handlers. We use this system so that themes and modules
+   * can override these state change handlers for particular parts of a page.
+   */
+
+  $(document).on('state:disabled', function (e) {
+    // Only act when this change was triggered by a dependency and not by the
+    // element monitoring itself.
+    if (e.trigger) {
+      $(e.target)
+        .prop('disabled', e.value)
         .closest('.form-item, .form-submit, .form-wrapper').toggleClass('form-disabled', e.value)
         .find('select, input, textarea').prop('disabled', e.value);
 
-    // Note: WebKit nightlies don't reflect that change correctly.
-    // See https://bugs.webkit.org/show_bug.cgi?id=23789
-  }
-});
-
-$(document).on('state:required', function (e) {
-  if (e.trigger) {
-    if (e.value) {
-      var $label = $(e.target).attr({ 'required': 'required', 'aria-required': 'aria-required' }).closest('.form-item, .form-wrapper').find('label');
-      // Avoids duplicate required markers on initialization.
-      if (!$label.find('.form-required').length) {
-        $label.append(Drupal.theme('requiredMarker'));
-      }
+      // Note: WebKit nightlies don't reflect that change correctly.
+      // See https://bugs.webkit.org/show_bug.cgi?id=23789
     }
-    else {
-      $(e.target).removeAttr('required aria-required').closest('.form-item, .form-wrapper').find('label .form-required').remove();
+  });
+
+  $(document).on('state:required', function (e) {
+    if (e.trigger) {
+      if (e.value) {
+        var $label = $(e.target).attr({ 'required': 'required', 'aria-required': 'aria-required' }).closest('.form-item, .form-wrapper').find('label');
+        // Avoids duplicate required markers on initialization.
+        if (!$label.find('.form-required').length) {
+          $label.append(Drupal.theme('requiredMarker'));
+        }
+      }
+      else {
+        $(e.target).removeAttr('required aria-required').closest('.form-item, .form-wrapper').find('label .form-required').remove();
+      }
     }
-  }
-});
+  });
 
-$(document).on('state:visible', function (e) {
-  if (e.trigger) {
-    $(e.target).closest('.form-item, .form-submit, .form-wrapper').toggle(e.value);
-  }
-});
+  $(document).on('state:visible', function (e) {
+    if (e.trigger) {
+      $(e.target).closest('.form-item, .form-submit, .form-wrapper').toggle(e.value);
+    }
+  });
 
-$(document).on('state:checked', function (e) {
-  if (e.trigger) {
-    $(e.target).prop('checked', e.value);
-  }
-});
+  $(document).on('state:checked', function (e) {
+    if (e.trigger) {
+      $(e.target).prop('checked', e.value);
+    }
+  });
 
-$(document).on('state:collapsed', function (e) {
-  if (e.trigger) {
-    if ($(e.target).is('[open]') === e.value) {
-      $(e.target).find('> summary a').trigger('click');
+  $(document).on('state:collapsed', function (e) {
+    if (e.trigger) {
+      if ($(e.target).is('[open]') === e.value) {
+        $(e.target).find('> summary a').trigger('click');
+      }
     }
-  }
-});
+  });
 
 
-/**
- * These are helper functions implementing addition "operators" and don't
- * implement any logic that is particular to states.
- */
+  /**
+   * These are helper functions implementing addition "operators" and don't
+   * implement any logic that is particular to states.
+   */
 
 // Bitwise AND with a third undefined state.
-function ternary (a, b) {
-  return typeof a === 'undefined' ? b : (typeof b === 'undefined' ? a : a && b);
-}
+  function ternary(a, b) {
+    return typeof a === 'undefined' ? b : (typeof b === 'undefined' ? a : a && b);
+  }
 
 // Inverts a (if it's not undefined) when invertState is true.
-function invert (a, invertState) {
-  return (invertState && typeof a !== 'undefined') ? !a : a;
-}
+  function invert(a, invertState) {
+    return (invertState && typeof a !== 'undefined') ? !a : a;
+  }
 
 // Compares two values while ignoring undefined values.
-function compare (a, b) {
-  return (a === b) ? (typeof a === 'undefined' ? a : true) : (typeof a === 'undefined' || typeof b === 'undefined');
-}
-
-$.extend(Drupal.theme, {
-  requiredMarker: function () {
-    return '<abbr class="form-required" title="' + Drupal.t('This field is required.') + '">*</abbr>';
+  function compare(a, b) {
+    return (a === b) ? (typeof a === 'undefined' ? a : true) : (typeof a === 'undefined' || typeof b === 'undefined');
   }
-});
+
+  $.extend(Drupal.theme, {
+    requiredMarker: function () {
+      return '<abbr class="form-required" title="' + Drupal.t('This field is required.') + '">*</abbr>';
+    }
+  });
 
 })(jQuery);
diff --git a/core/misc/tabbingmanager.js b/core/misc/tabbingmanager.js
index 0507120..5b98dab 100644
--- a/core/misc/tabbingmanager.js
+++ b/core/misc/tabbingmanager.js
@@ -5,288 +5,288 @@
 
 (function ($, Drupal) {
 
-"use strict";
+  "use strict";
 
-/**
- * Provides an API for managing page tabbing order modifications.
- */
-function TabbingManager () {
-  // Tabbing sets are stored as a stack. The active set is at the top of the
-  // stack. We use a JavaScript array as if it were a stack; we consider the
-  // first element to be the bottom and the last element to be the top. This
-  // allows us to use JavaScript's built-in Array.push() and Array.pop()
-  // methods.
-  this.stack = [];
-}
+  /**
+   * Provides an API for managing page tabbing order modifications.
+   */
+  function TabbingManager() {
+    // Tabbing sets are stored as a stack. The active set is at the top of the
+    // stack. We use a JavaScript array as if it were a stack; we consider the
+    // first element to be the bottom and the last element to be the top. This
+    // allows us to use JavaScript's built-in Array.push() and Array.pop()
+    // methods.
+    this.stack = [];
+  }
 
-/**
- * Add public methods to the TabbingManager class.
- */
-$.extend(TabbingManager.prototype, {
   /**
-   * Constrain tabbing to the specified set of elements only.
-   *
-   * Makes elements outside of the specified set of elements unreachable via the
-   * tab key.
-   *
-   * @param jQuery elements
-   *   The set of elements to which tabbing should be constrained. Can also be
-   *   a jQuery-compatible selector string.
-   *
-   * @return TabbingContext
+   * Add public methods to the TabbingManager class.
    */
-  constrain: function (elements) {
-    // Deactivate all tabbingContexts to prepare for the new constraint. A
-    // tabbingContext instance will only be reactivated if the stack is unwound
-    // to it in the _unwindStack() method.
-    for (var i = 0, il = this.stack.length; i < il; i++) {
-      this.stack[i].deactivate();
-    }
+  $.extend(TabbingManager.prototype, {
+    /**
+     * Constrain tabbing to the specified set of elements only.
+     *
+     * Makes elements outside of the specified set of elements unreachable via the
+     * tab key.
+     *
+     * @param jQuery elements
+     *   The set of elements to which tabbing should be constrained. Can also be
+     *   a jQuery-compatible selector string.
+     *
+     * @return TabbingContext
+     */
+    constrain: function (elements) {
+      // Deactivate all tabbingContexts to prepare for the new constraint. A
+      // tabbingContext instance will only be reactivated if the stack is unwound
+      // to it in the _unwindStack() method.
+      for (var i = 0, il = this.stack.length; i < il; i++) {
+        this.stack[i].deactivate();
+      }
 
-    // The "active tabbing set" are the elements tabbing should be constrained
-    // to.
-    var $elements = $(elements).find(':tabbable').addBack(':tabbable');
+      // The "active tabbing set" are the elements tabbing should be constrained
+      // to.
+      var $elements = $(elements).find(':tabbable').addBack(':tabbable');
 
-    var tabbingContext = new TabbingContext({
-      // The level is the current height of the stack before this new
-      // tabbingContext is pushed on top of the stack.
-      level: this.stack.length,
-      $tabbableElements: $elements
-    });
+      var tabbingContext = new TabbingContext({
+        // The level is the current height of the stack before this new
+        // tabbingContext is pushed on top of the stack.
+        level: this.stack.length,
+        $tabbableElements: $elements
+      });
 
-    this.stack.push(tabbingContext);
+      this.stack.push(tabbingContext);
 
-    // Activates the tabbingContext; this will manipulate the DOM to constrain
-    // tabbing.
-    tabbingContext.activate();
+      // Activates the tabbingContext; this will manipulate the DOM to constrain
+      // tabbing.
+      tabbingContext.activate();
 
-    // Allow modules to respond to the constrain event.
-    $(document).trigger('drupalTabbingConstrained', tabbingContext);
+      // Allow modules to respond to the constrain event.
+      $(document).trigger('drupalTabbingConstrained', tabbingContext);
 
-    return tabbingContext;
-  },
+      return tabbingContext;
+    },
 
-  /**
-   * Restores a former tabbingContext when an active tabbingContext is released.
-   *
-   * The TabbingManager stack of tabbingContext instances will be unwound from
-   * the top-most released tabbingContext down to the first non-released
-   * tabbingContext instance. This non-released instance is then activated.
-   */
-  release: function () {
-    // Unwind as far as possible: find the topmost non-released tabbingContext.
-    var toActivate = this.stack.length - 1;
-    while (toActivate >= 0 && this.stack[toActivate].released) {
-      toActivate--;
-    }
+    /**
+     * Restores a former tabbingContext when an active tabbingContext is released.
+     *
+     * The TabbingManager stack of tabbingContext instances will be unwound from
+     * the top-most released tabbingContext down to the first non-released
+     * tabbingContext instance. This non-released instance is then activated.
+     */
+    release: function () {
+      // Unwind as far as possible: find the topmost non-released tabbingContext.
+      var toActivate = this.stack.length - 1;
+      while (toActivate >= 0 && this.stack[toActivate].released) {
+        toActivate--;
+      }
 
-    // Delete all tabbingContexts after the to be activated one. They have
-    // already been deactivated, so their effect on the DOM has been reversed.
-    this.stack.splice(toActivate + 1);
+      // Delete all tabbingContexts after the to be activated one. They have
+      // already been deactivated, so their effect on the DOM has been reversed.
+      this.stack.splice(toActivate + 1);
 
-    // Get topmost tabbingContext, if one exists, and activate it.
-    if (toActivate >= 0) {
-      this.stack[toActivate].activate();
-    }
-  },
+      // Get topmost tabbingContext, if one exists, and activate it.
+      if (toActivate >= 0) {
+        this.stack[toActivate].activate();
+      }
+    },
 
-  /**
-   * Makes all elements outside the of the tabbingContext's set untabbable.
-   *
-   * Elements made untabble have their original tabindex and autfocus values
-   * stored so that they might be restored later when this tabbingContext
-   * is deactivated.
-   *
-   * @param TabbingContext tabbingContext
-   *   The TabbingContext instance that has been activated.
-   */
-  activate: function (tabbingContext) {
-    var $set = tabbingContext.$tabbableElements;
-    var level = tabbingContext.level;
-    // Determine which elements are reachable via tabbing by default.
-    var $disabledSet = $(':tabbable')
-      // Exclude elements of the active tabbing set.
-      .not($set);
-    // Set the disabled set on the tabbingContext.
-    tabbingContext.$disabledElements = $disabledSet;
-    // Record the tabindex for each element, so we can restore it later.
-    for (var i = 0, il = $disabledSet.length; i < il; i++) {
-      this.recordTabindex($disabledSet.eq(i), level);
-    }
-    // Make all tabbable elements outside of the active tabbing set unreachable.
-    $disabledSet
-      .prop('tabindex', -1)
-      .prop('autofocus', false);
+    /**
+     * Makes all elements outside the of the tabbingContext's set untabbable.
+     *
+     * Elements made untabble have their original tabindex and autfocus values
+     * stored so that they might be restored later when this tabbingContext
+     * is deactivated.
+     *
+     * @param TabbingContext tabbingContext
+     *   The TabbingContext instance that has been activated.
+     */
+    activate: function (tabbingContext) {
+      var $set = tabbingContext.$tabbableElements;
+      var level = tabbingContext.level;
+      // Determine which elements are reachable via tabbing by default.
+      var $disabledSet = $(':tabbable')
+        // Exclude elements of the active tabbing set.
+        .not($set);
+      // Set the disabled set on the tabbingContext.
+      tabbingContext.$disabledElements = $disabledSet;
+      // Record the tabindex for each element, so we can restore it later.
+      for (var i = 0, il = $disabledSet.length; i < il; i++) {
+        this.recordTabindex($disabledSet.eq(i), level);
+      }
+      // Make all tabbable elements outside of the active tabbing set unreachable.
+      $disabledSet
+        .prop('tabindex', -1)
+        .prop('autofocus', false);
 
-    // Set focus on an element in the tabbingContext's set of tabbable elements.
-    // First, check if there is an element with an autofocus attribute. Select
-    // the last one from the DOM order.
-    var $hasFocus = $set.filter('[autofocus]').eq(-1);
-    // If no element in the tabbable set has an autofocus attribute, select the
-    // first element in the set.
-    if ($hasFocus.length === 0) {
-      $hasFocus = $set.eq(0);
-    }
-    $hasFocus.trigger('focus');
-  },
+      // Set focus on an element in the tabbingContext's set of tabbable elements.
+      // First, check if there is an element with an autofocus attribute. Select
+      // the last one from the DOM order.
+      var $hasFocus = $set.filter('[autofocus]').eq(-1);
+      // If no element in the tabbable set has an autofocus attribute, select the
+      // first element in the set.
+      if ($hasFocus.length === 0) {
+        $hasFocus = $set.eq(0);
+      }
+      $hasFocus.trigger('focus');
+    },
 
-  /**
-   * Restores that tabbable state of a tabbingContext's disabled elements.
-   *
-   * Elements that were made untabble have their original tabindex and autfocus
-   * values restored.
-   *
-   * @param TabbingContext tabbingContext
-   *   The TabbingContext instance that has been deactivated.
-   */
-  deactivate: function (tabbingContext) {
-    var $set = tabbingContext.$disabledElements;
-    var level = tabbingContext.level;
-    for (var i = 0, il = $set.length; i < il; i++) {
-      this.restoreTabindex($set.eq(i), level);
-    }
-  },
+    /**
+     * Restores that tabbable state of a tabbingContext's disabled elements.
+     *
+     * Elements that were made untabble have their original tabindex and autfocus
+     * values restored.
+     *
+     * @param TabbingContext tabbingContext
+     *   The TabbingContext instance that has been deactivated.
+     */
+    deactivate: function (tabbingContext) {
+      var $set = tabbingContext.$disabledElements;
+      var level = tabbingContext.level;
+      for (var i = 0, il = $set.length; i < il; i++) {
+        this.restoreTabindex($set.eq(i), level);
+      }
+    },
 
-  /**
-   * Records the tabindex and autofocus values of an untabbable element.
-   *
-   * @param jQuery $set
-   *   The set of elements that have been disabled.
-   * @param Number level
-   *   The stack level for which the tabindex attribute should be recorded.
-   */
-  recordTabindex: function ($el, level) {
-    var tabInfo = $el.data('drupalOriginalTabIndices') || {};
-    tabInfo[level] = {
-      tabindex: $el[0].getAttribute('tabindex'),
-      autofocus: $el[0].hasAttribute('autofocus')
-    };
-    $el.data('drupalOriginalTabIndices', tabInfo);
-  },
+    /**
+     * Records the tabindex and autofocus values of an untabbable element.
+     *
+     * @param jQuery $set
+     *   The set of elements that have been disabled.
+     * @param Number level
+     *   The stack level for which the tabindex attribute should be recorded.
+     */
+    recordTabindex: function ($el, level) {
+      var tabInfo = $el.data('drupalOriginalTabIndices') || {};
+      tabInfo[level] = {
+        tabindex: $el[0].getAttribute('tabindex'),
+        autofocus: $el[0].hasAttribute('autofocus')
+      };
+      $el.data('drupalOriginalTabIndices', tabInfo);
+    },
 
-  /**
-   * Restores the tabindex and autofocus values of a reactivated element.
-   *
-   * @param jQuery $el
-   *   The element that is being reactivated.
-   * @param Number level
-   *   The stack level for which the tabindex attribute should be restored.
-   */
-  restoreTabindex: function ($el, level) {
-    var tabInfo = $el.data('drupalOriginalTabIndices');
-    if (tabInfo && tabInfo[level]) {
-      var data = tabInfo[level];
-      if (data.tabindex) {
-        $el[0].setAttribute('tabindex', data.tabindex);
-      }
-      // If the element did not have a tabindex at this stack level then
-      // remove it.
-      else {
-        $el[0].removeAttribute('tabindex');
-      }
-      if (data.autofocus) {
-        $el[0].setAttribute('autofocus', 'autofocus');
-      }
+    /**
+     * Restores the tabindex and autofocus values of a reactivated element.
+     *
+     * @param jQuery $el
+     *   The element that is being reactivated.
+     * @param Number level
+     *   The stack level for which the tabindex attribute should be restored.
+     */
+    restoreTabindex: function ($el, level) {
+      var tabInfo = $el.data('drupalOriginalTabIndices');
+      if (tabInfo && tabInfo[level]) {
+        var data = tabInfo[level];
+        if (data.tabindex) {
+          $el[0].setAttribute('tabindex', data.tabindex);
+        }
+        // If the element did not have a tabindex at this stack level then
+        // remove it.
+        else {
+          $el[0].removeAttribute('tabindex');
+        }
+        if (data.autofocus) {
+          $el[0].setAttribute('autofocus', 'autofocus');
+        }
 
-      // Clean up $.data.
-      if (level === 0) {
-        // Remove all data.
-       $el.removeData('drupalOriginalTabIndices');
-      }
-      else {
-        // Remove the data for this stack level and higher.
-        var levelToDelete = level;
-        while (tabInfo.hasOwnProperty(levelToDelete)) {
-          delete tabInfo[levelToDelete];
-          levelToDelete++;
+        // Clean up $.data.
+        if (level === 0) {
+          // Remove all data.
+          $el.removeData('drupalOriginalTabIndices');
+        }
+        else {
+          // Remove the data for this stack level and higher.
+          var levelToDelete = level;
+          while (tabInfo.hasOwnProperty(levelToDelete)) {
+            delete tabInfo[levelToDelete];
+            levelToDelete++;
+          }
+          $el.data('drupalOriginalTabIndices', tabInfo);
         }
-        $el.data('drupalOriginalTabIndices', tabInfo);
       }
     }
-  }
-});
-
-/**
- * Stores a set of tabbable elements.
- *
- * This constraint can be removed with the release() method.
- *
- * @param Object options
- *   A set of initiating values that include:
- *   - Number level: The level in the TabbingManager's stack of this
- *   tabbingContext.
- *   - jQuery $tabbableElements: The DOM elements that should be reachable via
- *   the tab key when this tabbingContext is active.
- *   - jQuery $disabledElements: The DOM elements that should not be reachable
- *   via the tab key when this tabbingContext is active.
- *   - Boolean released: A released tabbingContext can never be activated again.
- *   It will be cleaned up when the TabbingManager unwinds its stack.
- *   - Boolean active: When true, the tabbable elements of this tabbingContext
- *   will be reachable via the tab key and the disabled elements will not. Only
- *   one tabbingContext can be active at a time.
- */
-function TabbingContext (options) {
-  $.extend(this, {
-    level: null,
-    $tabbableElements: $(),
-    $disabledElements: $(),
-    released: false,
-    active: false
-  }, options);
-}
+  });
 
-/**
- * Add public methods to the TabbingContext class.
- */
-$.extend(TabbingContext.prototype, {
   /**
-   * Releases this TabbingContext.
+   * Stores a set of tabbable elements.
    *
-   * Once a TabbingContext object is released, it can never be activated again.
+   * This constraint can be removed with the release() method.
+   *
+   * @param Object options
+   *   A set of initiating values that include:
+   *   - Number level: The level in the TabbingManager's stack of this
+   *   tabbingContext.
+   *   - jQuery $tabbableElements: The DOM elements that should be reachable via
+   *   the tab key when this tabbingContext is active.
+   *   - jQuery $disabledElements: The DOM elements that should not be reachable
+   *   via the tab key when this tabbingContext is active.
+   *   - Boolean released: A released tabbingContext can never be activated again.
+   *   It will be cleaned up when the TabbingManager unwinds its stack.
+   *   - Boolean active: When true, the tabbable elements of this tabbingContext
+   *   will be reachable via the tab key and the disabled elements will not. Only
+   *   one tabbingContext can be active at a time.
    */
-  release: function () {
-    if (!this.released) {
-      this.deactivate();
-      this.released = true;
-      Drupal.tabbingManager.release(this);
-      // Allow modules to respond to the tabbingContext release event.
-      $(document).trigger('drupalTabbingContextReleased', this);
-    }
-  },
+  function TabbingContext(options) {
+    $.extend(this, {
+      level: null,
+      $tabbableElements: $(),
+      $disabledElements: $(),
+      released: false,
+      active: false
+    }, options);
+  }
 
   /**
-   * Activates this TabbingContext.
+   * Add public methods to the TabbingContext class.
    */
-  activate: function () {
-    // A released TabbingContext object can never be activated again.
-    if (!this.active && !this.released) {
-      this.active = true;
-      Drupal.tabbingManager.activate(this);
-      // Allow modules to respond to the constrain event.
-      $(document).trigger('drupalTabbingContextActivated', this);
-    }
-  },
+  $.extend(TabbingContext.prototype, {
+    /**
+     * Releases this TabbingContext.
+     *
+     * Once a TabbingContext object is released, it can never be activated again.
+     */
+    release: function () {
+      if (!this.released) {
+        this.deactivate();
+        this.released = true;
+        Drupal.tabbingManager.release(this);
+        // Allow modules to respond to the tabbingContext release event.
+        $(document).trigger('drupalTabbingContextReleased', this);
+      }
+    },
 
-  /**
-   * Deactivates this TabbingContext.
-   */
-  deactivate: function () {
-    if (this.active) {
-      this.active = false;
-      Drupal.tabbingManager.deactivate(this);
-      // Allow modules to respond to the constrain event.
-      $(document).trigger('drupalTabbingContextDeactivated', this);
+    /**
+     * Activates this TabbingContext.
+     */
+    activate: function () {
+      // A released TabbingContext object can never be activated again.
+      if (!this.active && !this.released) {
+        this.active = true;
+        Drupal.tabbingManager.activate(this);
+        // Allow modules to respond to the constrain event.
+        $(document).trigger('drupalTabbingContextActivated', this);
+      }
+    },
+
+    /**
+     * Deactivates this TabbingContext.
+     */
+    deactivate: function () {
+      if (this.active) {
+        this.active = false;
+        Drupal.tabbingManager.deactivate(this);
+        // Allow modules to respond to the constrain event.
+        $(document).trigger('drupalTabbingContextDeactivated', this);
+      }
     }
-  }
-});
+  });
 
 
 // Mark this behavior as processed on the first pass and return if it is
 // already processed.
-if (Drupal.tabbingManager) {
-  return;
-}
-Drupal.tabbingManager = new TabbingManager();
+  if (Drupal.tabbingManager) {
+    return;
+  }
+  Drupal.tabbingManager = new TabbingManager();
 
 
 }(jQuery, Drupal));
diff --git a/core/misc/tabledrag.js b/core/misc/tabledrag.js
index 68be8f6..f3c6c9e 100644
--- a/core/misc/tabledrag.js
+++ b/core/misc/tabledrag.js
@@ -1,1266 +1,1266 @@
 (function ($, Drupal, drupalSettings) {
 
-"use strict";
-
-/**
- * Store the state of weight columns display for all tables.
- * Default value is to hide weight columns.
- */
-var showWeight = JSON.parse(localStorage.getItem('Drupal.tableDrag.showWeight'));
-
-/**
- * Drag and drop table rows with field manipulation.
- *
- * Using the drupal_attach_tabledrag() function, any table with weights or
- * parent relationships may be made into draggable tables. Columns containing a
- * field may optionally be hidden, providing a better user experience.
- *
- * Created tableDrag instances may be modified with custom behaviors by
- * overriding the .onDrag, .onDrop, .row.onSwap, and .row.onIndent methods.
- * See blocks.js for an example of adding additional functionality to tableDrag.
- */
-Drupal.behaviors.tableDrag = {
-  attach: function (context, settings) {
-    function initTableDrag(table, base) {
-      if (table.length) {
-        // Create the new tableDrag instance. Save in the Drupal variable
-        // to allow other scripts access to the object.
-        Drupal.tableDrag[base] = new Drupal.tableDrag(table[0], settings.tableDrag[base]);
+  "use strict";
+
+  /**
+   * Store the state of weight columns display for all tables.
+   * Default value is to hide weight columns.
+   */
+  var showWeight = JSON.parse(localStorage.getItem('Drupal.tableDrag.showWeight'));
+
+  /**
+   * Drag and drop table rows with field manipulation.
+   *
+   * Using the drupal_attach_tabledrag() function, any table with weights or
+   * parent relationships may be made into draggable tables. Columns containing a
+   * field may optionally be hidden, providing a better user experience.
+   *
+   * Created tableDrag instances may be modified with custom behaviors by
+   * overriding the .onDrag, .onDrop, .row.onSwap, and .row.onIndent methods.
+   * See blocks.js for an example of adding additional functionality to tableDrag.
+   */
+  Drupal.behaviors.tableDrag = {
+    attach: function (context, settings) {
+      function initTableDrag(table, base) {
+        if (table.length) {
+          // Create the new tableDrag instance. Save in the Drupal variable
+          // to allow other scripts access to the object.
+          Drupal.tableDrag[base] = new Drupal.tableDrag(table[0], settings.tableDrag[base]);
+        }
       }
-    }
 
-    for (var base in settings.tableDrag) {
-      if (settings.tableDrag.hasOwnProperty(base)) {
-        initTableDrag($(context).find('#' + base).once('tabledrag'), base);
+      for (var base in settings.tableDrag) {
+        if (settings.tableDrag.hasOwnProperty(base)) {
+          initTableDrag($(context).find('#' + base).once('tabledrag'), base);
+        }
       }
     }
-  }
-};
-
-/**
- * Constructor for the tableDrag object. Provides table and field manipulation.
- *
- * @param table
- *   DOM object for the table to be made draggable.
- * @param tableSettings
- *   Settings for the table added via drupal_add_dragtable().
- */
-Drupal.tableDrag = function (table, tableSettings) {
-  var self = this;
-  var $table = $(table);
-
-  // Required object variables.
-  this.$table = $(table);
-  this.table = table;
-  this.tableSettings = tableSettings;
-  this.dragObject = null; // Used to hold information about a current drag operation.
-  this.rowObject = null; // Provides operations for row manipulation.
-  this.oldRowElement = null; // Remember the previous element.
-  this.oldY = 0; // Used to determine up or down direction from last mouse move.
-  this.changed = false; // Whether anything in the entire table has changed.
-  this.maxDepth = 0; // Maximum amount of allowed parenting.
-  this.rtl = $(this.table).css('direction') === 'rtl' ? -1 : 1; // Direction of the table.
-
-  // Configure the scroll settings.
-  this.scrollSettings = { amount: 4, interval: 50, trigger: 70 };
-  this.scrollInterval = null;
-  this.scrollY = 0;
-  this.windowHeight = 0;
-
-  // Check this table's settings to see if there are parent relationships in
-  // this table. For efficiency, large sections of code can be skipped if we
-  // don't need to track horizontal movement and indentations.
-  this.indentEnabled = false;
-  for (var group in tableSettings) {
-    if (tableSettings.hasOwnProperty(group)) {
-      for (var n in tableSettings[group]) {
-        if (tableSettings[group].hasOwnProperty(n)) {
-          if (tableSettings[group][n].relationship === 'parent') {
-            this.indentEnabled = true;
-          }
-          if (tableSettings[group][n].limit > 0) {
-            this.maxDepth = tableSettings[group][n].limit;
+  };
+
+  /**
+   * Constructor for the tableDrag object. Provides table and field manipulation.
+   *
+   * @param table
+   *   DOM object for the table to be made draggable.
+   * @param tableSettings
+   *   Settings for the table added via drupal_add_dragtable().
+   */
+  Drupal.tableDrag = function (table, tableSettings) {
+    var self = this;
+    var $table = $(table);
+
+    // Required object variables.
+    this.$table = $(table);
+    this.table = table;
+    this.tableSettings = tableSettings;
+    this.dragObject = null; // Used to hold information about a current drag operation.
+    this.rowObject = null; // Provides operations for row manipulation.
+    this.oldRowElement = null; // Remember the previous element.
+    this.oldY = 0; // Used to determine up or down direction from last mouse move.
+    this.changed = false; // Whether anything in the entire table has changed.
+    this.maxDepth = 0; // Maximum amount of allowed parenting.
+    this.rtl = $(this.table).css('direction') === 'rtl' ? -1 : 1; // Direction of the table.
+
+    // Configure the scroll settings.
+    this.scrollSettings = { amount: 4, interval: 50, trigger: 70 };
+    this.scrollInterval = null;
+    this.scrollY = 0;
+    this.windowHeight = 0;
+
+    // Check this table's settings to see if there are parent relationships in
+    // this table. For efficiency, large sections of code can be skipped if we
+    // don't need to track horizontal movement and indentations.
+    this.indentEnabled = false;
+    for (var group in tableSettings) {
+      if (tableSettings.hasOwnProperty(group)) {
+        for (var n in tableSettings[group]) {
+          if (tableSettings[group].hasOwnProperty(n)) {
+            if (tableSettings[group][n].relationship === 'parent') {
+              this.indentEnabled = true;
+            }
+            if (tableSettings[group][n].limit > 0) {
+              this.maxDepth = tableSettings[group][n].limit;
+            }
           }
         }
       }
     }
-  }
-  if (this.indentEnabled) {
-    this.indentCount = 1; // Total width of indents, set in makeDraggable.
-    // Find the width of indentations to measure mouse movements against.
-    // Because the table doesn't need to start with any indentations, we
-    // manually append 2 indentations in the first draggable row, measure
-    // the offset, then remove.
-    var indent = Drupal.theme('tableDragIndentation');
-    var testRow = $('<tr/>').addClass('draggable').appendTo(table);
-    var testCell = $('<td/>').appendTo(testRow).prepend(indent).prepend(indent);
-    var $indentation = testCell.find('.indentation');
-    this.indentAmount = $indentation.get(1).offsetLeft - $indentation.get(0).offsetLeft;
-    testRow.remove();
-  }
-
-  // Make each applicable row draggable.
-  // Match immediate children of the parent element to allow nesting.
-  $table.find('> tr.draggable, > tbody > tr.draggable').each(function () { self.makeDraggable(this); });
-
-  // Add a link before the table for users to show or hide weight columns.
-  $table.before($('<button type="button" class="link tabledrag-toggle-weight"></button>')
-    .attr('title', Drupal.t('Re-order rows by numerical weight instead of dragging.'))
-    .on('click', $.proxy(function (e) {
-      e.preventDefault();
-      this.toggleColumns();
-    }, this))
-    .wrap('<div class="tabledrag-toggle-weight-wrapper"></div>')
-    .parent()
-  );
-
-  // Initialize the specified columns (for example, weight or parent columns)
-  // to show or hide according to user preference. This aids accessibility
-  // so that, e.g., screen reader users can choose to enter weight values and
-  // manipulate form elements directly, rather than using drag-and-drop..
-  self.initColumns();
-
-  // Add event bindings to the document. The self variable is passed along
-  // as event handlers do not have direct access to the tableDrag object.
-  if (Modernizr.touch) {
-    $(document).on('touchmove', function (event) { return self.dragRow(event.originalEvent.touches[0], self); });
-    $(document).on('touchend', function (event) { return self.dropRow(event.originalEvent.touches[0], self); });
-  }
-  else {
-    $(document).on('mousemove', function (event) { return self.dragRow(event, self); });
-    $(document).on('mouseup', function (event) { return self.dropRow(event, self); });
-  }
-
-  // React to localStorage event showing or hiding weight columns.
-  $(window).on('storage', $.proxy(function (e) {
-    // Only react to 'Drupal.tableDrag.showWeight' value change.
-    if (e.originalEvent.key === 'Drupal.tableDrag.showWeight') {
-      // This was changed in another window, get the new value for this window.
-      showWeight = JSON.parse(e.originalEvent.newValue);
-      this.displayColumns(showWeight);
+    if (this.indentEnabled) {
+      this.indentCount = 1; // Total width of indents, set in makeDraggable.
+      // Find the width of indentations to measure mouse movements against.
+      // Because the table doesn't need to start with any indentations, we
+      // manually append 2 indentations in the first draggable row, measure
+      // the offset, then remove.
+      var indent = Drupal.theme('tableDragIndentation');
+      var testRow = $('<tr/>').addClass('draggable').appendTo(table);
+      var testCell = $('<td/>').appendTo(testRow).prepend(indent).prepend(indent);
+      var $indentation = testCell.find('.indentation');
+      this.indentAmount = $indentation.get(1).offsetLeft - $indentation.get(0).offsetLeft;
+      testRow.remove();
     }
-  }, this));
-};
-
-/**
- * Initialize columns containing form elements to be hidden by default,
- * according to the settings for this tableDrag instance.
- *
- * Identify and mark each cell with a CSS class so we can easily toggle
- * show/hide it. Finally, hide columns if user does not have a
- * 'Drupal.tableDrag.showWeight' localStorage value.
- */
-Drupal.tableDrag.prototype.initColumns = function () {
-  var $table = this.$table;
-  var hidden, cell, columnIndex;
-  for (var group in this.tableSettings) {
-    if (this.tableSettings.hasOwnProperty(group)) { // Find the first field in this group.
-      for (var d in this.tableSettings[group]) {
-        if (this.tableSettings[group].hasOwnProperty(d)) {
-          var field = $table.find('.' + this.tableSettings[group][d].target + ':first');
-          if (field.length && this.tableSettings[group][d].hidden) {
-            hidden = this.tableSettings[group][d].hidden;
-            cell = field.closest('td');
-            break;
-          }
-        }
-      }
 
-      // Mark the column containing this field so it can be hidden.
-      if (hidden && cell[0]) {
-        // Add 1 to our indexes. The nth-child selector is 1 based, not 0 based.
-        // Match immediate children of the parent element to allow nesting.
-        columnIndex = cell.parent().find('> td').index(cell.get(0)) + 1;
-        $table.find('> thead > tr, > tbody > tr, > tr').each(this.addColspanClass(columnIndex));
-      }
+    // Make each applicable row draggable.
+    // Match immediate children of the parent element to allow nesting.
+    $table.find('> tr.draggable, > tbody > tr.draggable').each(function () { self.makeDraggable(this); });
+
+    // Add a link before the table for users to show or hide weight columns.
+    $table.before($('<button type="button" class="link tabledrag-toggle-weight"></button>')
+      .attr('title', Drupal.t('Re-order rows by numerical weight instead of dragging.'))
+      .on('click', $.proxy(function (e) {
+        e.preventDefault();
+        this.toggleColumns();
+      }, this))
+      .wrap('<div class="tabledrag-toggle-weight-wrapper"></div>')
+      .parent()
+    );
+
+    // Initialize the specified columns (for example, weight or parent columns)
+    // to show or hide according to user preference. This aids accessibility
+    // so that, e.g., screen reader users can choose to enter weight values and
+    // manipulate form elements directly, rather than using drag-and-drop..
+    self.initColumns();
+
+    // Add event bindings to the document. The self variable is passed along
+    // as event handlers do not have direct access to the tableDrag object.
+    if (Modernizr.touch) {
+      $(document).on('touchmove', function (event) { return self.dragRow(event.originalEvent.touches[0], self); });
+      $(document).on('touchend', function (event) { return self.dropRow(event.originalEvent.touches[0], self); });
     }
-  }
-  this.displayColumns(showWeight);
-};
-
-/**
- * Mark cells that have colspan so we can adjust the colspan
- * instead of hiding them altogether.
- */
-Drupal.tableDrag.prototype.addColspanClass = function(columnIndex) {
-  return function () {
-    // Get the columnIndex and adjust for any colspans in this row.
-    var $row = $(this);
-    var index = columnIndex;
-    var cells = $row.children();
-    var cell;
-    cells.each(function (n) {
-      if (n < index && this.colSpan && this.colSpan > 1) {
-        index -= this.colSpan - 1;
-      }
-    });
-    if (index > 0) {
-      cell = cells.filter(':nth-child(' + index + ')');
-      if (cell[0].colSpan && cell[0].colSpan > 1) {
-        // If this cell has a colspan, mark it so we can reduce the colspan.
-        cell.addClass('tabledrag-has-colspan');
+    else {
+      $(document).on('mousemove', function (event) { return self.dragRow(event, self); });
+      $(document).on('mouseup', function (event) { return self.dropRow(event, self); });
+    }
+
+    // React to localStorage event showing or hiding weight columns.
+    $(window).on('storage', $.proxy(function (e) {
+      // Only react to 'Drupal.tableDrag.showWeight' value change.
+      if (e.originalEvent.key === 'Drupal.tableDrag.showWeight') {
+        // This was changed in another window, get the new value for this window.
+        showWeight = JSON.parse(e.originalEvent.newValue);
+        this.displayColumns(showWeight);
       }
-      else {
-        // Mark this cell so we can hide it.
-        cell.addClass('tabledrag-hide');
+    }, this));
+  };
+
+  /**
+   * Initialize columns containing form elements to be hidden by default,
+   * according to the settings for this tableDrag instance.
+   *
+   * Identify and mark each cell with a CSS class so we can easily toggle
+   * show/hide it. Finally, hide columns if user does not have a
+   * 'Drupal.tableDrag.showWeight' localStorage value.
+   */
+  Drupal.tableDrag.prototype.initColumns = function () {
+    var $table = this.$table;
+    var hidden, cell, columnIndex;
+    for (var group in this.tableSettings) {
+      if (this.tableSettings.hasOwnProperty(group)) { // Find the first field in this group.
+        for (var d in this.tableSettings[group]) {
+          if (this.tableSettings[group].hasOwnProperty(d)) {
+            var field = $table.find('.' + this.tableSettings[group][d].target + ':first');
+            if (field.length && this.tableSettings[group][d].hidden) {
+              hidden = this.tableSettings[group][d].hidden;
+              cell = field.closest('td');
+              break;
+            }
+          }
+        }
+
+        // Mark the column containing this field so it can be hidden.
+        if (hidden && cell[0]) {
+          // Add 1 to our indexes. The nth-child selector is 1 based, not 0 based.
+          // Match immediate children of the parent element to allow nesting.
+          columnIndex = cell.parent().find('> td').index(cell.get(0)) + 1;
+          $table.find('> thead > tr, > tbody > tr, > tr').each(this.addColspanClass(columnIndex));
+        }
       }
     }
+    this.displayColumns(showWeight);
   };
-};
-
-/**
- * Hide or display weight columns. Triggers an event on change.
- *
- * @param bool displayWeight
- *   'true' will show weight columns.
- */
-Drupal.tableDrag.prototype.displayColumns = function (displayWeight) {
-  if (displayWeight) {
-    this.showColumns();
-  }
-  // Default action is to hide columns.
-  else {
-    this.hideColumns();
-  }
-  // Trigger an event to allow other scripts to react to this display change.
-  // Force the extra parameter as a bool.
-  $('table.tabledrag-processed').trigger('columnschange', !!displayWeight);
-};
-
-/**
- * Toggle the weight column depending on 'showWeight' value.
- * Store only default override.
- */
-Drupal.tableDrag.prototype.toggleColumns = function () {
-  showWeight = !showWeight;
-  this.displayColumns(showWeight);
-  if (showWeight) {
-    // Save default override.
-    localStorage.setItem('Drupal.tableDrag.showWeight', showWeight);
-  }
-  else {
-    // Reset the value to its default.
-    localStorage.removeItem('Drupal.tableDrag.showWeight');
-  }
-};
-
-/**
- * Hide the columns containing weight/parent form elements.
- * Undo showColumns().
- */
-Drupal.tableDrag.prototype.hideColumns = function () {
-  var $tables = $('table.tabledrag-processed');
-  // Hide weight/parent cells and headers.
-  $tables.find('.tabledrag-hide').css('display', 'none');
-  // Show TableDrag handles.
-  $tables.find('.tabledrag-handle').css('display', '');
-  // Reduce the colspan of any effected multi-span columns.
-  $tables.find('.tabledrag-has-colspan').each(function () {
-    this.colSpan = this.colSpan - 1;
-  });
-  // Change link text.
-  $('.tabledrag-toggle-weight').text(Drupal.t('Show row weights'));
-};
-
-/**
- * Show the columns containing weight/parent form elements
- * Undo hideColumns().
- */
-Drupal.tableDrag.prototype.showColumns = function () {
-  var $tables = $('table.tabledrag-processed');
-  // Show weight/parent cells and headers.
-  $tables.find('.tabledrag-hide').css('display', '');
-  // Hide TableDrag handles.
-  $tables.find('.tabledrag-handle').css('display', 'none');
-  // Increase the colspan for any columns where it was previously reduced.
-  $tables.find('.tabledrag-has-colspan').each(function () {
-    this.colSpan = this.colSpan + 1;
-  });
-  // Change link text.
-  $('.tabledrag-toggle-weight').text(Drupal.t('Hide row weights'));
-};
-
-/**
- * Find the target used within a particular row and group.
- */
-Drupal.tableDrag.prototype.rowSettings = function (group, row) {
-  var field = $(row).find('.' + group);
-  var tableSettingsGroup = this.tableSettings[group];
-  for (var delta in tableSettingsGroup) {
-    if (tableSettingsGroup.hasOwnProperty(delta)) {
-      var targetClass = tableSettingsGroup[delta].target;
-      if (field.is('.' + targetClass)) {
-        // Return a copy of the row settings.
-        var rowSettings = {};
-        for (var n in tableSettingsGroup[delta]) {
-          if (tableSettingsGroup[delta].hasOwnProperty(n)) {
-            rowSettings[n] = tableSettingsGroup[delta][n];
-          }
+
+  /**
+   * Mark cells that have colspan so we can adjust the colspan
+   * instead of hiding them altogether.
+   */
+  Drupal.tableDrag.prototype.addColspanClass = function (columnIndex) {
+    return function () {
+      // Get the columnIndex and adjust for any colspans in this row.
+      var $row = $(this);
+      var index = columnIndex;
+      var cells = $row.children();
+      var cell;
+      cells.each(function (n) {
+        if (n < index && this.colSpan && this.colSpan > 1) {
+          index -= this.colSpan - 1;
+        }
+      });
+      if (index > 0) {
+        cell = cells.filter(':nth-child(' + index + ')');
+        if (cell[0].colSpan && cell[0].colSpan > 1) {
+          // If this cell has a colspan, mark it so we can reduce the colspan.
+          cell.addClass('tabledrag-has-colspan');
+        }
+        else {
+          // Mark this cell so we can hide it.
+          cell.addClass('tabledrag-hide');
         }
-        return rowSettings;
       }
+    };
+  };
+
+  /**
+   * Hide or display weight columns. Triggers an event on change.
+   *
+   * @param bool displayWeight
+   *   'true' will show weight columns.
+   */
+  Drupal.tableDrag.prototype.displayColumns = function (displayWeight) {
+    if (displayWeight) {
+      this.showColumns();
     }
-  }
-};
-
-/**
- * Take an item and add event handlers to make it become draggable.
- */
-Drupal.tableDrag.prototype.makeDraggable = function (item) {
-  var self = this;
-  var $item = $(item);
-  //Add a class to the title link
-  $item.find('td:first a').addClass('menu-item__link');
-  // Create the handle.
-  var handle = $('<a href="#" class="tabledrag-handle"><div class="handle">&nbsp;</div></a>').attr('title', Drupal.t('Drag to re-order'));
-  // Insert the handle after indentations (if any).
-  var $indentationLast = $item.find('td:first .indentation:last');
-  if ($indentationLast.length) {
-    $indentationLast.after(handle);
-    // Update the total width of indentation in this entire table.
-    self.indentCount = Math.max($item.find('.indentation').length, self.indentCount);
-  }
-  else {
-    $item.find('td:first').prepend(handle);
-  }
-
-  if (Modernizr.touch) {
-    handle.on('touchstart', function (event) {
-      event.preventDefault();
-      event = event.originalEvent.touches[0];
-      self.dragStart(event, self, item);
-    });
-  }
-  else {
-    handle.on('mousedown', function (event) {
-      event.preventDefault();
-      self.dragStart(event, self, item);
+    // Default action is to hide columns.
+    else {
+      this.hideColumns();
+    }
+    // Trigger an event to allow other scripts to react to this display change.
+    // Force the extra parameter as a bool.
+    $('table.tabledrag-processed').trigger('columnschange', !!displayWeight);
+  };
+
+  /**
+   * Toggle the weight column depending on 'showWeight' value.
+   * Store only default override.
+   */
+  Drupal.tableDrag.prototype.toggleColumns = function () {
+    showWeight = !showWeight;
+    this.displayColumns(showWeight);
+    if (showWeight) {
+      // Save default override.
+      localStorage.setItem('Drupal.tableDrag.showWeight', showWeight);
+    }
+    else {
+      // Reset the value to its default.
+      localStorage.removeItem('Drupal.tableDrag.showWeight');
+    }
+  };
+
+  /**
+   * Hide the columns containing weight/parent form elements.
+   * Undo showColumns().
+   */
+  Drupal.tableDrag.prototype.hideColumns = function () {
+    var $tables = $('table.tabledrag-processed');
+    // Hide weight/parent cells and headers.
+    $tables.find('.tabledrag-hide').css('display', 'none');
+    // Show TableDrag handles.
+    $tables.find('.tabledrag-handle').css('display', '');
+    // Reduce the colspan of any effected multi-span columns.
+    $tables.find('.tabledrag-has-colspan').each(function () {
+      this.colSpan = this.colSpan - 1;
     });
-  }
+    // Change link text.
+    $('.tabledrag-toggle-weight').text(Drupal.t('Show row weights'));
+  };
 
-  // Prevent the anchor tag from jumping us to the top of the page.
-  handle.on('click', function (e) {
-    e.preventDefault();
-  });
+  /**
+   * Show the columns containing weight/parent form elements
+   * Undo hideColumns().
+   */
+  Drupal.tableDrag.prototype.showColumns = function () {
+    var $tables = $('table.tabledrag-processed');
+    // Show weight/parent cells and headers.
+    $tables.find('.tabledrag-hide').css('display', '');
+    // Hide TableDrag handles.
+    $tables.find('.tabledrag-handle').css('display', 'none');
+    // Increase the colspan for any columns where it was previously reduced.
+    $tables.find('.tabledrag-has-colspan').each(function () {
+      this.colSpan = this.colSpan + 1;
+    });
+    // Change link text.
+    $('.tabledrag-toggle-weight').text(Drupal.t('Hide row weights'));
+  };
 
-  // Set blur cleanup when a handle is focused.
-  handle.on('focus', function () {
-    self.safeBlur = true;
-  });
+  /**
+   * Find the target used within a particular row and group.
+   */
+  Drupal.tableDrag.prototype.rowSettings = function (group, row) {
+    var field = $(row).find('.' + group);
+    var tableSettingsGroup = this.tableSettings[group];
+    for (var delta in tableSettingsGroup) {
+      if (tableSettingsGroup.hasOwnProperty(delta)) {
+        var targetClass = tableSettingsGroup[delta].target;
+        if (field.is('.' + targetClass)) {
+          // Return a copy of the row settings.
+          var rowSettings = {};
+          for (var n in tableSettingsGroup[delta]) {
+            if (tableSettingsGroup[delta].hasOwnProperty(n)) {
+              rowSettings[n] = tableSettingsGroup[delta][n];
+            }
+          }
+          return rowSettings;
+        }
+      }
+    }
+  };
 
-  // On blur, fire the same function as a touchend/mouseup. This is used to
-  // update values after a row has been moved through the keyboard support.
-  handle.on('blur', function (event) {
-    if (self.rowObject && self.safeBlur) {
-      self.dropRow(event, self);
+  /**
+   * Take an item and add event handlers to make it become draggable.
+   */
+  Drupal.tableDrag.prototype.makeDraggable = function (item) {
+    var self = this;
+    var $item = $(item);
+    //Add a class to the title link
+    $item.find('td:first a').addClass('menu-item__link');
+    // Create the handle.
+    var handle = $('<a href="#" class="tabledrag-handle"><div class="handle">&nbsp;</div></a>').attr('title', Drupal.t('Drag to re-order'));
+    // Insert the handle after indentations (if any).
+    var $indentationLast = $item.find('td:first .indentation:last');
+    if ($indentationLast.length) {
+      $indentationLast.after(handle);
+      // Update the total width of indentation in this entire table.
+      self.indentCount = Math.max($item.find('.indentation').length, self.indentCount);
+    }
+    else {
+      $item.find('td:first').prepend(handle);
     }
-  });
 
-  // Add arrow-key support to the handle.
-  handle.on('keydown', function (event) {
-    // If a rowObject doesn't yet exist and this isn't the tab key.
-    if (event.keyCode !== 9 && !self.rowObject) {
-      self.rowObject = new self.row(item, 'keyboard', self.indentEnabled, self.maxDepth, true);
+    if (Modernizr.touch) {
+      handle.on('touchstart', function (event) {
+        event.preventDefault();
+        event = event.originalEvent.touches[0];
+        self.dragStart(event, self, item);
+      });
+    }
+    else {
+      handle.on('mousedown', function (event) {
+        event.preventDefault();
+        self.dragStart(event, self, item);
+      });
     }
 
-    var keyChange = false;
-    var groupHeight;
-    switch (event.keyCode) {
-      case 37: // Left arrow.
-      case 63234: // Safari left arrow.
-        keyChange = true;
-        self.rowObject.indent(-1 * self.rtl);
-        break;
-      case 38: // Up arrow.
-      case 63232: // Safari up arrow.
-        var $previousRow = $(self.rowObject.element).prev('tr').eq(0);
-        var previousRow = $previousRow.get(0);
-        while (previousRow && $previousRow.is(':hidden')) {
-          $previousRow = $(previousRow).prev('tr').eq(0);
-          previousRow = $previousRow.get(0);
-        }
-        if (previousRow) {
-          self.safeBlur = false; // Do not allow the onBlur cleanup.
-          self.rowObject.direction = 'up';
-          keyChange = true;
+    // Prevent the anchor tag from jumping us to the top of the page.
+    handle.on('click', function (e) {
+      e.preventDefault();
+    });
+
+    // Set blur cleanup when a handle is focused.
+    handle.on('focus', function () {
+      self.safeBlur = true;
+    });
+
+    // On blur, fire the same function as a touchend/mouseup. This is used to
+    // update values after a row has been moved through the keyboard support.
+    handle.on('blur', function (event) {
+      if (self.rowObject && self.safeBlur) {
+        self.dropRow(event, self);
+      }
+    });
+
+    // Add arrow-key support to the handle.
+    handle.on('keydown', function (event) {
+      // If a rowObject doesn't yet exist and this isn't the tab key.
+      if (event.keyCode !== 9 && !self.rowObject) {
+        self.rowObject = new self.row(item, 'keyboard', self.indentEnabled, self.maxDepth, true);
+      }
 
-          if ($(item).is('.tabledrag-root')) {
-            // Swap with the previous top-level row.
-            groupHeight = 0;
-            while (previousRow && $previousRow.find('.indentation').length) {
-              $previousRow = $(previousRow).prev('tr').eq(0);
-              previousRow = $previousRow.get(0);
-              groupHeight += $previousRow.is(':hidden') ? 0 : previousRow.offsetHeight;
+      var keyChange = false;
+      var groupHeight;
+      switch (event.keyCode) {
+        case 37: // Left arrow.
+        case 63234: // Safari left arrow.
+          keyChange = true;
+          self.rowObject.indent(-1 * self.rtl);
+          break;
+        case 38: // Up arrow.
+        case 63232: // Safari up arrow.
+          var $previousRow = $(self.rowObject.element).prev('tr').eq(0);
+          var previousRow = $previousRow.get(0);
+          while (previousRow && $previousRow.is(':hidden')) {
+            $previousRow = $(previousRow).prev('tr').eq(0);
+            previousRow = $previousRow.get(0);
+          }
+          if (previousRow) {
+            self.safeBlur = false; // Do not allow the onBlur cleanup.
+            self.rowObject.direction = 'up';
+            keyChange = true;
+
+            if ($(item).is('.tabledrag-root')) {
+              // Swap with the previous top-level row.
+              groupHeight = 0;
+              while (previousRow && $previousRow.find('.indentation').length) {
+                $previousRow = $(previousRow).prev('tr').eq(0);
+                previousRow = $previousRow.get(0);
+                groupHeight += $previousRow.is(':hidden') ? 0 : previousRow.offsetHeight;
+              }
+              if (previousRow) {
+                self.rowObject.swap('before', previousRow);
+                // No need to check for indentation, 0 is the only valid one.
+                window.scrollBy(0, -groupHeight);
+              }
             }
-            if (previousRow) {
+            else if (self.table.tBodies[0].rows[0] !== previousRow || $previousRow.is('.draggable')) {
+              // Swap with the previous row (unless previous row is the first one
+              // and undraggable).
               self.rowObject.swap('before', previousRow);
-              // No need to check for indentation, 0 is the only valid one.
-              window.scrollBy(0, -groupHeight);
+              self.rowObject.interval = null;
+              self.rowObject.indent(0);
+              window.scrollBy(0, -parseInt(item.offsetHeight, 10));
             }
+            handle.trigger('focus'); // Regain focus after the DOM manipulation.
           }
-          else if (self.table.tBodies[0].rows[0] !== previousRow || $previousRow.is('.draggable')) {
-            // Swap with the previous row (unless previous row is the first one
-            // and undraggable).
-            self.rowObject.swap('before', previousRow);
-            self.rowObject.interval = null;
-            self.rowObject.indent(0);
-            window.scrollBy(0, -parseInt(item.offsetHeight, 10));
-          }
-          handle.trigger('focus'); // Regain focus after the DOM manipulation.
-        }
-        break;
-      case 39: // Right arrow.
-      case 63235: // Safari right arrow.
-        keyChange = true;
-        self.rowObject.indent(self.rtl);
-        break;
-      case 40: // Down arrow.
-      case 63233: // Safari down arrow.
-        var $nextRow = $(self.rowObject.group).filter(':last').next('tr').eq(0);
-        var nextRow = $nextRow.get(0);
-        while (nextRow && $nextRow.is(':hidden')) {
-          $nextRow = $(nextRow).next('tr').eq(0);
-          nextRow = $nextRow.get(0);
-        }
-        if (nextRow) {
-          self.safeBlur = false; // Do not allow the onBlur cleanup.
-          self.rowObject.direction = 'down';
+          break;
+        case 39: // Right arrow.
+        case 63235: // Safari right arrow.
           keyChange = true;
-
-          if ($(item).is('.tabledrag-root')) {
-            // Swap with the next group (necessarily a top-level one).
-            groupHeight = 0;
-            var nextGroup = new self.row(nextRow, 'keyboard', self.indentEnabled, self.maxDepth, false);
-            if (nextGroup) {
-              $(nextGroup.group).each(function () {
-                groupHeight += $(this).is(':hidden') ? 0 : this.offsetHeight;
-              });
-              var nextGroupRow = $(nextGroup.group).filter(':last').get(0);
-              self.rowObject.swap('after', nextGroupRow);
-              // No need to check for indentation, 0 is the only valid one.
-              window.scrollBy(0, parseInt(groupHeight, 10));
-            }
+          self.rowObject.indent(self.rtl);
+          break;
+        case 40: // Down arrow.
+        case 63233: // Safari down arrow.
+          var $nextRow = $(self.rowObject.group).filter(':last').next('tr').eq(0);
+          var nextRow = $nextRow.get(0);
+          while (nextRow && $nextRow.is(':hidden')) {
+            $nextRow = $(nextRow).next('tr').eq(0);
+            nextRow = $nextRow.get(0);
           }
-          else {
-            // Swap with the next row.
-            self.rowObject.swap('after', nextRow);
-            self.rowObject.interval = null;
-            self.rowObject.indent(0);
-            window.scrollBy(0, parseInt(item.offsetHeight, 10));
+          if (nextRow) {
+            self.safeBlur = false; // Do not allow the onBlur cleanup.
+            self.rowObject.direction = 'down';
+            keyChange = true;
+
+            if ($(item).is('.tabledrag-root')) {
+              // Swap with the next group (necessarily a top-level one).
+              groupHeight = 0;
+              var nextGroup = new self.row(nextRow, 'keyboard', self.indentEnabled, self.maxDepth, false);
+              if (nextGroup) {
+                $(nextGroup.group).each(function () {
+                  groupHeight += $(this).is(':hidden') ? 0 : this.offsetHeight;
+                });
+                var nextGroupRow = $(nextGroup.group).filter(':last').get(0);
+                self.rowObject.swap('after', nextGroupRow);
+                // No need to check for indentation, 0 is the only valid one.
+                window.scrollBy(0, parseInt(groupHeight, 10));
+              }
+            }
+            else {
+              // Swap with the next row.
+              self.rowObject.swap('after', nextRow);
+              self.rowObject.interval = null;
+              self.rowObject.indent(0);
+              window.scrollBy(0, parseInt(item.offsetHeight, 10));
+            }
+            handle.trigger('focus'); // Regain focus after the DOM manipulation.
           }
-          handle.trigger('focus'); // Regain focus after the DOM manipulation.
+          break;
+      }
+
+      if (self.rowObject && self.rowObject.changed === true) {
+        $(item).addClass('drag');
+        if (self.oldRowElement) {
+          $(self.oldRowElement).removeClass('drag-previous');
         }
-        break;
-    }
+        self.oldRowElement = item;
+        self.restripeTable();
+        self.onDrag();
+      }
 
-    if (self.rowObject && self.rowObject.changed === true) {
-      $(item).addClass('drag');
-      if (self.oldRowElement) {
-        $(self.oldRowElement).removeClass('drag-previous');
+      // Returning false if we have an arrow key to prevent scrolling.
+      if (keyChange) {
+        return false;
       }
-      self.oldRowElement = item;
-      self.restripeTable();
-      self.onDrag();
+    });
+
+    // Compatibility addition, return false on keypress to prevent unwanted scrolling.
+    // IE and Safari will suppress scrolling on keydown, but all other browsers
+    // need to return false on keypress. http://www.quirksmode.org/js/keys.html
+    handle.on('keypress', function (event) {
+      switch (event.keyCode) {
+        case 37: // Left arrow.
+        case 38: // Up arrow.
+        case 39: // Right arrow.
+        case 40: // Down arrow.
+          return false;
+      }
+    });
+  };
+
+  /**
+   * Pointer event initiator, creates drag object and information.
+   *
+   * @param jQuery.Event event
+   *   The event object that trigger the drag.
+   * @param Drupal.tableDrag self
+   *   The drag handle.
+   * @param DOM item
+   *   The item that that is being dragged.
+   */
+  Drupal.tableDrag.prototype.dragStart = function (event, self, item) {
+    // Create a new dragObject recording the pointer information.
+    self.dragObject = {};
+    self.dragObject.initOffset = self.getPointerOffset(item, event);
+    self.dragObject.initPointerCoords = self.pointerCoords(event);
+    if (self.indentEnabled) {
+      self.dragObject.indentPointerPos = self.dragObject.initPointerCoords;
     }
 
-    // Returning false if we have an arrow key to prevent scrolling.
-    if (keyChange) {
-      return false;
+    // If there's a lingering row object from the keyboard, remove its focus.
+    if (self.rowObject) {
+      $(self.rowObject.element).find('a.tabledrag-handle').trigger('blur');
     }
-  });
 
-  // Compatibility addition, return false on keypress to prevent unwanted scrolling.
-  // IE and Safari will suppress scrolling on keydown, but all other browsers
-  // need to return false on keypress. http://www.quirksmode.org/js/keys.html
-  handle.on('keypress', function (event) {
-    switch (event.keyCode) {
-      case 37: // Left arrow.
-      case 38: // Up arrow.
-      case 39: // Right arrow.
-      case 40: // Down arrow.
-        return false;
+    // Create a new rowObject for manipulation of this row.
+    self.rowObject = new self.row(item, 'pointer', self.indentEnabled, self.maxDepth, true);
+
+    // Save the position of the table.
+    self.table.topY = $(self.table).offset().top;
+    self.table.bottomY = self.table.topY + self.table.offsetHeight;
+
+    // Add classes to the handle and row.
+    $(item).addClass('drag');
+
+    // Set the document to use the move cursor during drag.
+    $('body').addClass('drag');
+    if (self.oldRowElement) {
+      $(self.oldRowElement).removeClass('drag-previous');
     }
-  });
-};
-
-/**
- * Pointer event initiator, creates drag object and information.
- *
- * @param jQuery.Event event
- *   The event object that trigger the drag.
- * @param Drupal.tableDrag self
- *   The drag handle.
- * @param DOM item
- *   The item that that is being dragged.
- */
-Drupal.tableDrag.prototype.dragStart = function (event, self, item) {
-  // Create a new dragObject recording the pointer information.
-  self.dragObject = {};
-  self.dragObject.initOffset = self.getPointerOffset(item, event);
-  self.dragObject.initPointerCoords = self.pointerCoords(event);
-  if (self.indentEnabled) {
-    self.dragObject.indentPointerPos = self.dragObject.initPointerCoords;
-  }
-
-  // If there's a lingering row object from the keyboard, remove its focus.
-  if (self.rowObject) {
-    $(self.rowObject.element).find('a.tabledrag-handle').trigger('blur');
-  }
-
-  // Create a new rowObject for manipulation of this row.
-  self.rowObject = new self.row(item, 'pointer', self.indentEnabled, self.maxDepth, true);
-
-  // Save the position of the table.
-  self.table.topY = $(self.table).offset().top;
-  self.table.bottomY = self.table.topY + self.table.offsetHeight;
-
-  // Add classes to the handle and row.
-  $(item).addClass('drag');
-
-  // Set the document to use the move cursor during drag.
-  $('body').addClass('drag');
-  if (self.oldRowElement) {
-    $(self.oldRowElement).removeClass('drag-previous');
-  }
-};
-
-/**
- * Pointer movement handler, bound to document.
- */
-Drupal.tableDrag.prototype.dragRow = function (event, self) {
-  if (self.dragObject) {
-    self.currentPointerCoords = self.pointerCoords(event);
-    var y = self.currentPointerCoords.y - self.dragObject.initOffset.y;
-    var x = self.currentPointerCoords.x - self.dragObject.initOffset.x;
-
-    // Check for row swapping and vertical scrolling.
-    if (y !== self.oldY) {
-      self.rowObject.direction = y > self.oldY ? 'down' : 'up';
-      self.oldY = y; // Update the old value.
-
-      // Check if the window should be scrolled (and how fast).
-      var scrollAmount = self.checkScroll(self.currentPointerCoords.y);
-      // Stop any current scrolling.
-      clearInterval(self.scrollInterval);
-      // Continue scrolling if the mouse has moved in the scroll direction.
-      if (scrollAmount > 0 && self.rowObject.direction === 'down' || scrollAmount < 0 && self.rowObject.direction === 'up') {
-        self.setScroll(scrollAmount);
-      }
+  };
 
-      // If we have a valid target, perform the swap and restripe the table.
-      var currentRow = self.findDropTargetRow(x, y);
-      if (currentRow) {
-        if (self.rowObject.direction === 'down') {
-          self.rowObject.swap('after', currentRow, self);
+  /**
+   * Pointer movement handler, bound to document.
+   */
+  Drupal.tableDrag.prototype.dragRow = function (event, self) {
+    if (self.dragObject) {
+      self.currentPointerCoords = self.pointerCoords(event);
+      var y = self.currentPointerCoords.y - self.dragObject.initOffset.y;
+      var x = self.currentPointerCoords.x - self.dragObject.initOffset.x;
+
+      // Check for row swapping and vertical scrolling.
+      if (y !== self.oldY) {
+        self.rowObject.direction = y > self.oldY ? 'down' : 'up';
+        self.oldY = y; // Update the old value.
+
+        // Check if the window should be scrolled (and how fast).
+        var scrollAmount = self.checkScroll(self.currentPointerCoords.y);
+        // Stop any current scrolling.
+        clearInterval(self.scrollInterval);
+        // Continue scrolling if the mouse has moved in the scroll direction.
+        if (scrollAmount > 0 && self.rowObject.direction === 'down' || scrollAmount < 0 && self.rowObject.direction === 'up') {
+          self.setScroll(scrollAmount);
         }
-        else {
-          self.rowObject.swap('before', currentRow, self);
+
+        // If we have a valid target, perform the swap and restripe the table.
+        var currentRow = self.findDropTargetRow(x, y);
+        if (currentRow) {
+          if (self.rowObject.direction === 'down') {
+            self.rowObject.swap('after', currentRow, self);
+          }
+          else {
+            self.rowObject.swap('before', currentRow, self);
+          }
+          self.restripeTable();
         }
-        self.restripeTable();
       }
-    }
 
-    // Similar to row swapping, handle indentations.
-    if (self.indentEnabled) {
-      var xDiff = self.currentPointerCoords.x - self.dragObject.indentPointerPos.x;
-      // Set the number of indentations the pointer has been moved left or right.
-      var indentDiff = Math.round(xDiff / self.indentAmount * self.rtl);
-      // Indent the row with our estimated diff, which may be further
-      // restricted according to the rows around this row.
-      var indentChange = self.rowObject.indent(indentDiff);
-      // Update table and pointer indentations.
-      self.dragObject.indentPointerPos.x += self.indentAmount * indentChange * self.rtl;
-      self.indentCount = Math.max(self.indentCount, self.rowObject.indents);
+      // Similar to row swapping, handle indentations.
+      if (self.indentEnabled) {
+        var xDiff = self.currentPointerCoords.x - self.dragObject.indentPointerPos.x;
+        // Set the number of indentations the pointer has been moved left or right.
+        var indentDiff = Math.round(xDiff / self.indentAmount * self.rtl);
+        // Indent the row with our estimated diff, which may be further
+        // restricted according to the rows around this row.
+        var indentChange = self.rowObject.indent(indentDiff);
+        // Update table and pointer indentations.
+        self.dragObject.indentPointerPos.x += self.indentAmount * indentChange * self.rtl;
+        self.indentCount = Math.max(self.indentCount, self.rowObject.indents);
+      }
+
+      return false;
     }
+  };
 
-    return false;
-  }
-};
-
-/**
- * Pointerup behavior.
- */
-Drupal.tableDrag.prototype.dropRow = function (event, self) {
-  var droppedRow, $droppedRow;
-
-  // Drop row functionality.
-  if (self.rowObject !== null) {
-    droppedRow = self.rowObject.element;
-    $droppedRow = $(droppedRow);
-    // The row is already in the right place so we just release it.
-    if (self.rowObject.changed === true) {
-      // Update the fields in the dropped row.
-      self.updateFields(droppedRow);
-
-      // If a setting exists for affecting the entire group, update all the
-      // fields in the entire dragged group.
-      for (var group in self.tableSettings) {
-        if (self.tableSettings.hasOwnProperty(group)) {
-          var rowSettings = self.rowSettings(group, droppedRow);
-          if (rowSettings.relationship === 'group') {
-            for (var n in self.rowObject.children) {
-              if (self.rowObject.children.hasOwnProperty(n)) {
-                self.updateField(self.rowObject.children[n], group);
+  /**
+   * Pointerup behavior.
+   */
+  Drupal.tableDrag.prototype.dropRow = function (event, self) {
+    var droppedRow, $droppedRow;
+
+    // Drop row functionality.
+    if (self.rowObject !== null) {
+      droppedRow = self.rowObject.element;
+      $droppedRow = $(droppedRow);
+      // The row is already in the right place so we just release it.
+      if (self.rowObject.changed === true) {
+        // Update the fields in the dropped row.
+        self.updateFields(droppedRow);
+
+        // If a setting exists for affecting the entire group, update all the
+        // fields in the entire dragged group.
+        for (var group in self.tableSettings) {
+          if (self.tableSettings.hasOwnProperty(group)) {
+            var rowSettings = self.rowSettings(group, droppedRow);
+            if (rowSettings.relationship === 'group') {
+              for (var n in self.rowObject.children) {
+                if (self.rowObject.children.hasOwnProperty(n)) {
+                  self.updateField(self.rowObject.children[n], group);
+                }
               }
             }
           }
         }
+
+        self.rowObject.markChanged();
+        if (self.changed === false) {
+          $(Drupal.theme('tableDragChangedWarning')).insertBefore(self.table).hide().fadeIn('slow');
+          self.changed = true;
+        }
       }
 
-      self.rowObject.markChanged();
-      if (self.changed === false) {
-        $(Drupal.theme('tableDragChangedWarning')).insertBefore(self.table).hide().fadeIn('slow');
-        self.changed = true;
+      if (self.indentEnabled) {
+        self.rowObject.removeIndentClasses();
+      }
+      if (self.oldRowElement) {
+        $(self.oldRowElement).removeClass('drag-previous');
       }
+      $droppedRow.removeClass('drag').addClass('drag-previous');
+      self.oldRowElement = droppedRow;
+      self.onDrop();
+      self.rowObject = null;
     }
 
-    if (self.indentEnabled) {
-      self.rowObject.removeIndentClasses();
-    }
-    if (self.oldRowElement) {
-      $(self.oldRowElement).removeClass('drag-previous');
+    // Functionality specific only to pointerup events.
+    if (self.dragObject !== null) {
+      self.dragObject = null;
+      $('body').removeClass('drag');
+      clearInterval(self.scrollInterval);
     }
-    $droppedRow.removeClass('drag').addClass('drag-previous');
-    self.oldRowElement = droppedRow;
-    self.onDrop();
-    self.rowObject = null;
-  }
-
-  // Functionality specific only to pointerup events.
-  if (self.dragObject !== null) {
-    self.dragObject = null;
-    $('body').removeClass('drag');
-    clearInterval(self.scrollInterval);
-  }
-};
-
-/**
- * Get the coordinates from the event (allowing for browser differences).
- */
-Drupal.tableDrag.prototype.pointerCoords = function (event) {
-  if (event.pageX || event.pageY) {
-    return { x: event.pageX, y: event.pageY };
-  }
-  return {
-    x: event.clientX + document.body.scrollLeft - document.body.clientLeft,
-    y: event.clientY + document.body.scrollTop  - document.body.clientTop
   };
-};
-
-/**
- * Given a target element and a pointer event, get the event offset from that
- * element. To do this we need the element's position and the target position.
- */
-Drupal.tableDrag.prototype.getPointerOffset = function (target, event) {
-  var docPos = $(target).offset();
-  var pointerPos = this.pointerCoords(event);
-  return { x: pointerPos.x - docPos.left, y: pointerPos.y - docPos.top };
-};
-
-/**
- * Find the row the mouse is currently over. This row is then taken and swapped
- * with the one being dragged.
- *
- * @param x
- *   The x coordinate of the mouse on the page (not the screen).
- * @param y
- *   The y coordinate of the mouse on the page (not the screen).
- */
-Drupal.tableDrag.prototype.findDropTargetRow = function (x, y) {
-  var rows = $(this.table.tBodies[0].rows).not(':hidden');
-  for (var n = 0; n < rows.length; n++) {
-    var row = rows[n];
-    var $row = $(row);
-    var rowY = $row.offset().top;
-    var rowHeight;
-    // Because Safari does not report offsetHeight on table rows, but does on
-    // table cells, grab the firstChild of the row and use that instead.
-    // http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari.
-    if (row.offsetHeight === 0) {
-      rowHeight = parseInt(row.firstChild.offsetHeight, 10) / 2;
-    }
-    // Other browsers.
-    else {
-      rowHeight = parseInt(row.offsetHeight, 10) / 2;
+
+  /**
+   * Get the coordinates from the event (allowing for browser differences).
+   */
+  Drupal.tableDrag.prototype.pointerCoords = function (event) {
+    if (event.pageX || event.pageY) {
+      return { x: event.pageX, y: event.pageY };
     }
+    return {
+      x: event.clientX + document.body.scrollLeft - document.body.clientLeft,
+      y: event.clientY + document.body.scrollTop - document.body.clientTop
+    };
+  };
+
+  /**
+   * Given a target element and a pointer event, get the event offset from that
+   * element. To do this we need the element's position and the target position.
+   */
+  Drupal.tableDrag.prototype.getPointerOffset = function (target, event) {
+    var docPos = $(target).offset();
+    var pointerPos = this.pointerCoords(event);
+    return { x: pointerPos.x - docPos.left, y: pointerPos.y - docPos.top };
+  };
+
+  /**
+   * Find the row the mouse is currently over. This row is then taken and swapped
+   * with the one being dragged.
+   *
+   * @param x
+   *   The x coordinate of the mouse on the page (not the screen).
+   * @param y
+   *   The y coordinate of the mouse on the page (not the screen).
+   */
+  Drupal.tableDrag.prototype.findDropTargetRow = function (x, y) {
+    var rows = $(this.table.tBodies[0].rows).not(':hidden');
+    for (var n = 0; n < rows.length; n++) {
+      var row = rows[n];
+      var $row = $(row);
+      var rowY = $row.offset().top;
+      var rowHeight;
+      // Because Safari does not report offsetHeight on table rows, but does on
+      // table cells, grab the firstChild of the row and use that instead.
+      // http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari.
+      if (row.offsetHeight === 0) {
+        rowHeight = parseInt(row.firstChild.offsetHeight, 10) / 2;
+      }
+      // Other browsers.
+      else {
+        rowHeight = parseInt(row.offsetHeight, 10) / 2;
+      }
 
-    // Because we always insert before, we need to offset the height a bit.
-    if ((y > (rowY - rowHeight)) && (y < (rowY + rowHeight))) {
-      if (this.indentEnabled) {
-        // Check that this row is not a child of the row being dragged.
-        for (n in this.rowObject.group) {
-          if (this.rowObject.group[n] === row) {
+      // Because we always insert before, we need to offset the height a bit.
+      if ((y > (rowY - rowHeight)) && (y < (rowY + rowHeight))) {
+        if (this.indentEnabled) {
+          // Check that this row is not a child of the row being dragged.
+          for (n in this.rowObject.group) {
+            if (this.rowObject.group[n] === row) {
+              return null;
+            }
+          }
+        }
+        else {
+          // Do not allow a row to be swapped with itself.
+          if (row === this.rowObject.element) {
             return null;
           }
         }
-      }
-      else {
-        // Do not allow a row to be swapped with itself.
-        if (row === this.rowObject.element) {
+
+        // Check that swapping with this row is allowed.
+        if (!this.rowObject.isValidSwap(row)) {
           return null;
         }
-      }
 
-      // Check that swapping with this row is allowed.
-      if (!this.rowObject.isValidSwap(row)) {
-        return null;
+        // We may have found the row the mouse just passed over, but it doesn't
+        // take into account hidden rows. Skip backwards until we find a draggable
+        // row.
+        while ($row.is(':hidden') && $row.prev('tr').is(':hidden')) {
+          $row = $row.prev('tr').eq(0);
+          row = $row.get(0);
+        }
+        return row;
       }
+    }
+    return null;
+  };
 
-      // We may have found the row the mouse just passed over, but it doesn't
-      // take into account hidden rows. Skip backwards until we find a draggable
-      // row.
-      while ($row.is(':hidden') && $row.prev('tr').is(':hidden')) {
-        $row = $row.prev('tr').eq(0);
-        row = $row.get(0);
+  /**
+   * After the row is dropped, update the table fields according to the settings
+   * set for this table.
+   *
+   * @param changedRow
+   *   DOM object for the row that was just dropped.
+   */
+  Drupal.tableDrag.prototype.updateFields = function (changedRow) {
+    for (var group in this.tableSettings) {
+      if (this.tableSettings.hasOwnProperty(group)) {
+        // Each group may have a different setting for relationship, so we find
+        // the source rows for each separately.
+        this.updateField(changedRow, group);
       }
-      return row;
     }
-  }
-  return null;
-};
-
-/**
- * After the row is dropped, update the table fields according to the settings
- * set for this table.
- *
- * @param changedRow
- *   DOM object for the row that was just dropped.
- */
-Drupal.tableDrag.prototype.updateFields = function (changedRow) {
-  for (var group in this.tableSettings) {
-    if (this.tableSettings.hasOwnProperty(group)) {
-      // Each group may have a different setting for relationship, so we find
-      // the source rows for each separately.
-      this.updateField(changedRow, group);
+  };
+
+  /**
+   * After the row is dropped, update a single table field according to specific
+   * settings.
+   *
+   * @param changedRow
+   *   DOM object for the row that was just dropped.
+   * @param group
+   *   The settings group on which field updates will occur.
+   */
+  Drupal.tableDrag.prototype.updateField = function (changedRow, group) {
+    var rowSettings = this.rowSettings(group, changedRow);
+    var $changedRow = $(changedRow);
+    var sourceRow;
+    var $previousRow;
+    var previousRow;
+    var useSibling;
+    // Set the row as its own target.
+    if (rowSettings.relationship === 'self' || rowSettings.relationship === 'group') {
+      sourceRow = changedRow;
     }
-  }
-};
-
-/**
- * After the row is dropped, update a single table field according to specific
- * settings.
- *
- * @param changedRow
- *   DOM object for the row that was just dropped.
- * @param group
- *   The settings group on which field updates will occur.
- */
-Drupal.tableDrag.prototype.updateField = function (changedRow, group) {
-  var rowSettings = this.rowSettings(group, changedRow);
-  var $changedRow = $(changedRow);
-  var sourceRow;
-  var $previousRow;
-  var previousRow;
-  var useSibling;
-  // Set the row as its own target.
-  if (rowSettings.relationship === 'self' || rowSettings.relationship === 'group') {
-    sourceRow = changedRow;
-  }
-  // Siblings are easy, check previous and next rows.
-  else if (rowSettings.relationship === 'sibling') {
-    $previousRow = $changedRow.prev('tr').eq(0);
-    previousRow = $previousRow.get(0);
-    var $nextRow = $changedRow.next('tr').eq(0);
-    var nextRow = $nextRow.get(0);
-    sourceRow = changedRow;
-    if ($previousRow.is('.draggable') && $previousRow.find('.' + group).length) {
-      if (this.indentEnabled) {
-        if ($previousRow.find('.indentations').length === $changedRow.find('.indentations').length) {
+    // Siblings are easy, check previous and next rows.
+    else if (rowSettings.relationship === 'sibling') {
+      $previousRow = $changedRow.prev('tr').eq(0);
+      previousRow = $previousRow.get(0);
+      var $nextRow = $changedRow.next('tr').eq(0);
+      var nextRow = $nextRow.get(0);
+      sourceRow = changedRow;
+      if ($previousRow.is('.draggable') && $previousRow.find('.' + group).length) {
+        if (this.indentEnabled) {
+          if ($previousRow.find('.indentations').length === $changedRow.find('.indentations').length) {
+            sourceRow = previousRow;
+          }
+        }
+        else {
           sourceRow = previousRow;
         }
       }
-      else {
-        sourceRow = previousRow;
-      }
-    }
-    else if ($nextRow.is('.draggable') && $nextRow.find('.' + group).length) {
-      if (this.indentEnabled) {
-        if ($nextRow.find('.indentations').length === $changedRow.find('.indentations').length) {
+      else if ($nextRow.is('.draggable') && $nextRow.find('.' + group).length) {
+        if (this.indentEnabled) {
+          if ($nextRow.find('.indentations').length === $changedRow.find('.indentations').length) {
+            sourceRow = nextRow;
+          }
+        }
+        else {
           sourceRow = nextRow;
         }
       }
-      else {
-        sourceRow = nextRow;
-      }
     }
-  }
-  // Parents, look up the tree until we find a field not in this group.
-  // Go up as many parents as indentations in the changed row.
-  else if (rowSettings.relationship === 'parent') {
-    $previousRow = $changedRow.prev('tr');
-    previousRow = $previousRow;
-    while ($previousRow.length && $previousRow.find('.indentation').length >= this.rowObject.indents) {
-      $previousRow = $previousRow.prev('tr');
+    // Parents, look up the tree until we find a field not in this group.
+    // Go up as many parents as indentations in the changed row.
+    else if (rowSettings.relationship === 'parent') {
+      $previousRow = $changedRow.prev('tr');
       previousRow = $previousRow;
+      while ($previousRow.length && $previousRow.find('.indentation').length >= this.rowObject.indents) {
+        $previousRow = $previousRow.prev('tr');
+        previousRow = $previousRow;
+      }
+      // If we found a row.
+      if ($previousRow.length) {
+        sourceRow = $previousRow.get(0);
+      }
+      // Otherwise we went all the way to the left of the table without finding
+      // a parent, meaning this item has been placed at the root level.
+      else {
+        // Use the first row in the table as source, because it's guaranteed to
+        // be at the root level. Find the first item, then compare this row
+        // against it as a sibling.
+        sourceRow = $(this.table).find('tr.draggable:first').get(0);
+        if (sourceRow === this.rowObject.element) {
+          sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0);
+        }
+        useSibling = true;
+      }
     }
-    // If we found a row.
-    if ($previousRow.length) {
-      sourceRow = $previousRow.get(0);
+
+    // Because we may have moved the row from one category to another,
+    // take a look at our sibling and borrow its sources and targets.
+    this.copyDragClasses(sourceRow, changedRow, group);
+    rowSettings = this.rowSettings(group, changedRow);
+
+    // In the case that we're looking for a parent, but the row is at the top
+    // of the tree, copy our sibling's values.
+    if (useSibling) {
+      rowSettings.relationship = 'sibling';
+      rowSettings.source = rowSettings.target;
     }
-    // Otherwise we went all the way to the left of the table without finding
-    // a parent, meaning this item has been placed at the root level.
-    else {
-      // Use the first row in the table as source, because it's guaranteed to
-      // be at the root level. Find the first item, then compare this row
-      // against it as a sibling.
-      sourceRow = $(this.table).find('tr.draggable:first').get(0);
-      if (sourceRow === this.rowObject.element) {
-        sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0);
+
+    var targetClass = '.' + rowSettings.target;
+    var targetElement = $changedRow.find(targetClass).get(0);
+
+    // Check if a target element exists in this row.
+    if (targetElement) {
+      var sourceClass = '.' + rowSettings.source;
+      var sourceElement = $(sourceClass, sourceRow).get(0);
+      switch (rowSettings.action) {
+        case 'depth':
+          // Get the depth of the target row.
+          targetElement.value = $(sourceElement).closest('tr').find('.indentation').length;
+          break;
+        case 'match':
+          // Update the value.
+          targetElement.value = sourceElement.value;
+          break;
+        case 'order':
+          var siblings = this.rowObject.findSiblings(rowSettings);
+          if ($(targetElement).is('select')) {
+            // Get a list of acceptable values.
+            var values = [];
+            $(targetElement).find('option').each(function () {
+              values.push(this.value);
+            });
+            var maxVal = values[values.length - 1];
+            // Populate the values in the siblings.
+            $(siblings).find(targetClass).each(function () {
+              // If there are more items than possible values, assign the maximum value to the row.
+              if (values.length > 0) {
+                this.value = values.shift();
+              }
+              else {
+                this.value = maxVal;
+              }
+            });
+          }
+          else {
+            // Assume a numeric input field.
+            var weight = parseInt($(siblings[0]).find(targetClass).val(), 10) || 0;
+            $(siblings).find(targetClass).each(function () {
+              this.value = weight;
+              weight++;
+            });
+          }
+          break;
       }
-      useSibling = true;
     }
-  }
-
-  // Because we may have moved the row from one category to another,
-  // take a look at our sibling and borrow its sources and targets.
-  this.copyDragClasses(sourceRow, changedRow, group);
-  rowSettings = this.rowSettings(group, changedRow);
-
-  // In the case that we're looking for a parent, but the row is at the top
-  // of the tree, copy our sibling's values.
-  if (useSibling) {
-    rowSettings.relationship = 'sibling';
-    rowSettings.source = rowSettings.target;
-  }
-
-  var targetClass = '.' + rowSettings.target;
-  var targetElement = $changedRow.find(targetClass).get(0);
-
-  // Check if a target element exists in this row.
-  if (targetElement) {
-    var sourceClass = '.' + rowSettings.source;
-    var sourceElement = $(sourceClass, sourceRow).get(0);
-    switch (rowSettings.action) {
-      case 'depth':
-        // Get the depth of the target row.
-        targetElement.value = $(sourceElement).closest('tr').find('.indentation').length;
-        break;
-      case 'match':
-        // Update the value.
-        targetElement.value = sourceElement.value;
-        break;
-      case 'order':
-        var siblings = this.rowObject.findSiblings(rowSettings);
-        if ($(targetElement).is('select')) {
-          // Get a list of acceptable values.
-          var values = [];
-          $(targetElement).find('option').each(function () {
-            values.push(this.value);
-          });
-          var maxVal = values[values.length - 1];
-          // Populate the values in the siblings.
-          $(siblings).find(targetClass).each(function () {
-            // If there are more items than possible values, assign the maximum value to the row.
-            if (values.length > 0) {
-              this.value = values.shift();
-            }
-            else {
-              this.value = maxVal;
-            }
-          });
-        }
-        else {
-          // Assume a numeric input field.
-          var weight = parseInt($(siblings[0]).find(targetClass).val(), 10) || 0;
-          $(siblings).find(targetClass).each(function () {
-            this.value = weight;
-            weight++;
-          });
-        }
-        break;
+  };
+
+  /**
+   * Copy all special tableDrag classes from one row's form elements to a
+   * different one, removing any special classes that the destination row
+   * may have had.
+   */
+  Drupal.tableDrag.prototype.copyDragClasses = function (sourceRow, targetRow, group) {
+    var sourceElement = $(sourceRow).find('.' + group);
+    var targetElement = $(targetRow).find('.' + group);
+    if (sourceElement.length && targetElement.length) {
+      targetElement[0].className = sourceElement[0].className;
     }
-  }
-};
-
-/**
- * Copy all special tableDrag classes from one row's form elements to a
- * different one, removing any special classes that the destination row
- * may have had.
- */
-Drupal.tableDrag.prototype.copyDragClasses = function (sourceRow, targetRow, group) {
-  var sourceElement = $(sourceRow).find('.' + group);
-  var targetElement = $(targetRow).find('.' + group);
-  if (sourceElement.length && targetElement.length) {
-    targetElement[0].className = sourceElement[0].className;
-  }
-};
-
-Drupal.tableDrag.prototype.checkScroll = function (cursorY) {
-  var de  = document.documentElement;
-  var b  = document.body;
-
-  var windowHeight = this.windowHeight = window.innerHeight || (de.clientHeight && de.clientWidth !== 0 ? de.clientHeight : b.offsetHeight);
-  var scrollY = this.scrollY = (document.all ? (!de.scrollTop ? b.scrollTop : de.scrollTop) : (window.pageYOffset ? window.pageYOffset : window.scrollY));
-  var trigger = this.scrollSettings.trigger;
-  var delta = 0;
-
-  // Return a scroll speed relative to the edge of the screen.
-  if (cursorY - scrollY > windowHeight - trigger) {
-    delta = trigger / (windowHeight + scrollY - cursorY);
-    delta = (delta > 0 && delta < trigger) ? delta : trigger;
-    return delta * this.scrollSettings.amount;
-  }
-  else if (cursorY - scrollY < trigger) {
-    delta = trigger / (cursorY - scrollY);
-    delta = (delta > 0 && delta < trigger) ? delta : trigger;
-    return -delta * this.scrollSettings.amount;
-  }
-};
-
-Drupal.tableDrag.prototype.setScroll = function (scrollAmount) {
-  var self = this;
-
-  this.scrollInterval = setInterval(function () {
-    // Update the scroll values stored in the object.
-    self.checkScroll(self.currentPointerCoords.y);
-    var aboveTable = self.scrollY > self.table.topY;
-    var belowTable = self.scrollY + self.windowHeight < self.table.bottomY;
-    if (scrollAmount > 0 && belowTable || scrollAmount < 0 && aboveTable) {
-      window.scrollBy(0, scrollAmount);
+  };
+
+  Drupal.tableDrag.prototype.checkScroll = function (cursorY) {
+    var de = document.documentElement;
+    var b = document.body;
+
+    var windowHeight = this.windowHeight = window.innerHeight || (de.clientHeight && de.clientWidth !== 0 ? de.clientHeight : b.offsetHeight);
+    var scrollY = this.scrollY = (document.all ? (!de.scrollTop ? b.scrollTop : de.scrollTop) : (window.pageYOffset ? window.pageYOffset : window.scrollY));
+    var trigger = this.scrollSettings.trigger;
+    var delta = 0;
+
+    // Return a scroll speed relative to the edge of the screen.
+    if (cursorY - scrollY > windowHeight - trigger) {
+      delta = trigger / (windowHeight + scrollY - cursorY);
+      delta = (delta > 0 && delta < trigger) ? delta : trigger;
+      return delta * this.scrollSettings.amount;
     }
-  }, this.scrollSettings.interval);
-};
-
-Drupal.tableDrag.prototype.restripeTable = function () {
-  // :even and :odd are reversed because jQuery counts from 0 and
-  // we count from 1, so we're out of sync.
-  // Match immediate children of the parent element to allow nesting.
-  $(this.table).find('> tbody > tr.draggable:visible, > tr.draggable:visible')
-    .removeClass('odd even')
-    .filter(':odd').addClass('even').end()
-    .filter(':even').addClass('odd');
-};
-
-/**
- * Stub function. Allows a custom handler when a row begins dragging.
- */
-Drupal.tableDrag.prototype.onDrag = function () {
-  return null;
-};
-
-/**
- * Stub function. Allows a custom handler when a row is dropped.
- */
-Drupal.tableDrag.prototype.onDrop = function () {
-  return null;
-};
-
-/**
- * Constructor to make a new object to manipulate a table row.
- *
- * @param tableRow
- *   The DOM element for the table row we will be manipulating.
- * @param method
- *   The method in which this row is being moved. Either 'keyboard' or 'mouse'.
- * @param indentEnabled
- *   Whether the containing table uses indentations. Used for optimizations.
- * @param maxDepth
- *   The maximum amount of indentations this row may contain.
- * @param addClasses
- *   Whether we want to add classes to this row to indicate child relationships.
- */
-Drupal.tableDrag.prototype.row = function (tableRow, method, indentEnabled, maxDepth, addClasses) {
-  var $tableRow = $(tableRow);
-
-  this.element = tableRow;
-  this.method = method;
-  this.group = [tableRow];
-  this.groupDepth = $tableRow.find('.indentation').length;
-  this.changed = false;
-  this.table = $tableRow.closest('table')[0];
-  this.indentEnabled = indentEnabled;
-  this.maxDepth = maxDepth;
-  this.direction = ''; // Direction the row is being moved.
-
-  if (this.indentEnabled) {
-    this.indents = $tableRow.find('.indentation').length;
-    this.children = this.findChildren(addClasses);
-    this.group = $.merge(this.group, this.children);
-    // Find the depth of this entire group.
-    for (var n = 0; n < this.group.length; n++) {
-      this.groupDepth = Math.max($(this.group[n]).find('.indentation').length, this.groupDepth);
+    else if (cursorY - scrollY < trigger) {
+      delta = trigger / (cursorY - scrollY);
+      delta = (delta > 0 && delta < trigger) ? delta : trigger;
+      return -delta * this.scrollSettings.amount;
     }
-  }
-};
-
-/**
- * Find all children of rowObject by indentation.
- *
- * @param addClasses
- *   Whether we want to add classes to this row to indicate child relationships.
- */
-Drupal.tableDrag.prototype.row.prototype.findChildren = function (addClasses) {
-  var parentIndentation = this.indents;
-  var currentRow = $(this.element, this.table).next('tr.draggable');
-  var rows = [];
-  var child = 0;
-  function rowIndentation(el, indentNum) {
-    var self = $(el);
-    if (child === 1 && (indentNum === parentIndentation)) {
-      self.addClass('tree-child-first');
+  };
+
+  Drupal.tableDrag.prototype.setScroll = function (scrollAmount) {
+    var self = this;
+
+    this.scrollInterval = setInterval(function () {
+      // Update the scroll values stored in the object.
+      self.checkScroll(self.currentPointerCoords.y);
+      var aboveTable = self.scrollY > self.table.topY;
+      var belowTable = self.scrollY + self.windowHeight < self.table.bottomY;
+      if (scrollAmount > 0 && belowTable || scrollAmount < 0 && aboveTable) {
+        window.scrollBy(0, scrollAmount);
+      }
+    }, this.scrollSettings.interval);
+  };
+
+  Drupal.tableDrag.prototype.restripeTable = function () {
+    // :even and :odd are reversed because jQuery counts from 0 and
+    // we count from 1, so we're out of sync.
+    // Match immediate children of the parent element to allow nesting.
+    $(this.table).find('> tbody > tr.draggable:visible, > tr.draggable:visible')
+      .removeClass('odd even')
+      .filter(':odd').addClass('even').end()
+      .filter(':even').addClass('odd');
+  };
+
+  /**
+   * Stub function. Allows a custom handler when a row begins dragging.
+   */
+  Drupal.tableDrag.prototype.onDrag = function () {
+    return null;
+  };
+
+  /**
+   * Stub function. Allows a custom handler when a row is dropped.
+   */
+  Drupal.tableDrag.prototype.onDrop = function () {
+    return null;
+  };
+
+  /**
+   * Constructor to make a new object to manipulate a table row.
+   *
+   * @param tableRow
+   *   The DOM element for the table row we will be manipulating.
+   * @param method
+   *   The method in which this row is being moved. Either 'keyboard' or 'mouse'.
+   * @param indentEnabled
+   *   Whether the containing table uses indentations. Used for optimizations.
+   * @param maxDepth
+   *   The maximum amount of indentations this row may contain.
+   * @param addClasses
+   *   Whether we want to add classes to this row to indicate child relationships.
+   */
+  Drupal.tableDrag.prototype.row = function (tableRow, method, indentEnabled, maxDepth, addClasses) {
+    var $tableRow = $(tableRow);
+
+    this.element = tableRow;
+    this.method = method;
+    this.group = [tableRow];
+    this.groupDepth = $tableRow.find('.indentation').length;
+    this.changed = false;
+    this.table = $tableRow.closest('table')[0];
+    this.indentEnabled = indentEnabled;
+    this.maxDepth = maxDepth;
+    this.direction = ''; // Direction the row is being moved.
+
+    if (this.indentEnabled) {
+      this.indents = $tableRow.find('.indentation').length;
+      this.children = this.findChildren(addClasses);
+      this.group = $.merge(this.group, this.children);
+      // Find the depth of this entire group.
+      for (var n = 0; n < this.group.length; n++) {
+        this.groupDepth = Math.max($(this.group[n]).find('.indentation').length, this.groupDepth);
+      }
     }
-    if (indentNum === parentIndentation) {
-      self.addClass('tree-child');
+  };
+
+  /**
+   * Find all children of rowObject by indentation.
+   *
+   * @param addClasses
+   *   Whether we want to add classes to this row to indicate child relationships.
+   */
+  Drupal.tableDrag.prototype.row.prototype.findChildren = function (addClasses) {
+    var parentIndentation = this.indents;
+    var currentRow = $(this.element, this.table).next('tr.draggable');
+    var rows = [];
+    var child = 0;
+    function rowIndentation(el, indentNum) {
+      var self = $(el);
+      if (child === 1 && (indentNum === parentIndentation)) {
+        self.addClass('tree-child-first');
+      }
+      if (indentNum === parentIndentation) {
+        self.addClass('tree-child');
+      }
+      else if (indentNum > parentIndentation) {
+        self.addClass('tree-child-horizontal');
+      }
     }
-    else if (indentNum > parentIndentation) {
-      self.addClass('tree-child-horizontal');
+    while (currentRow.length) {
+      // A greater indentation indicates this is a child.
+      if (currentRow.find('.indentation').length > parentIndentation) {
+        child++;
+        rows.push(currentRow[0]);
+        if (addClasses) {
+          currentRow.find('.indentation').each(rowIndentation);
+        }
+      }
+      else {
+        break;
+      }
+      currentRow = currentRow.next('tr.draggable');
     }
-  }
-  while (currentRow.length) {
-    // A greater indentation indicates this is a child.
-    if (currentRow.find('.indentation').length > parentIndentation) {
-      child++;
-      rows.push(currentRow[0]);
-      if (addClasses) {
-        currentRow.find('.indentation').each(rowIndentation);
+    if (addClasses && rows.length) {
+      $(rows[rows.length - 1]).find('.indentation:nth-child(' + (parentIndentation + 1) + ')').addClass('tree-child-last');
+    }
+    return rows;
+  };
+
+  /**
+   * Ensure that two rows are allowed to be swapped.
+   *
+   * @param row
+   *   DOM object for the row being considered for swapping.
+   */
+  Drupal.tableDrag.prototype.row.prototype.isValidSwap = function (row) {
+    var $row = $(row);
+    if (this.indentEnabled) {
+      var prevRow, nextRow;
+      if (this.direction === 'down') {
+        prevRow = row;
+        nextRow = $row.next('tr').get(0);
+      }
+      else {
+        prevRow = $row.prev('tr').get(0);
+        nextRow = row;
+      }
+      this.interval = this.validIndentInterval(prevRow, nextRow);
+
+      // We have an invalid swap if the valid indentations interval is empty.
+      if (this.interval.min > this.interval.max) {
+        return false;
       }
     }
-    else {
-      break;
+
+    // Do not let an un-draggable first row have anything put before it.
+    if (this.table.tBodies[0].rows[0] === row && $row.is(':not(.draggable)')) {
+      return false;
     }
-    currentRow = currentRow.next('tr.draggable');
-  }
-  if (addClasses && rows.length) {
-    $(rows[rows.length - 1]).find('.indentation:nth-child(' + (parentIndentation + 1) + ')').addClass('tree-child-last');
-  }
-  return rows;
-};
-
-/**
- * Ensure that two rows are allowed to be swapped.
- *
- * @param row
- *   DOM object for the row being considered for swapping.
- */
-Drupal.tableDrag.prototype.row.prototype.isValidSwap = function (row) {
-  var $row = $(row);
-  if (this.indentEnabled) {
-    var prevRow, nextRow;
-    if (this.direction === 'down') {
-      prevRow = row;
-      nextRow = $row.next('tr').get(0);
+
+    return true;
+  };
+
+  /**
+   * Perform the swap between two rows.
+   *
+   * @param position
+   *   Whether the swap will occur 'before' or 'after' the given row.
+   * @param row
+   *   DOM element what will be swapped with the row group.
+   */
+  Drupal.tableDrag.prototype.row.prototype.swap = function (position, row) {
+    // Makes sure only DOM object are passed to Drupal.detachBehaviors().
+    this.group.forEach(function (row) {
+      Drupal.detachBehaviors(row, drupalSettings, 'move');
+    });
+    $(row)[position](this.group);
+    // Makes sure only DOM object are passed to Drupal.attachBehaviors()s.
+    this.group.forEach(function (row) {
+      Drupal.attachBehaviors(row, drupalSettings);
+    });
+    this.changed = true;
+    this.onSwap(row);
+  };
+
+  /**
+   * Determine the valid indentations interval for the row at a given position
+   * in the table.
+   *
+   * @param prevRow
+   *   DOM object for the row before the tested position
+   *   (or null for first position in the table).
+   * @param nextRow
+   *   DOM object for the row after the tested position
+   *   (or null for last position in the table).
+   */
+  Drupal.tableDrag.prototype.row.prototype.validIndentInterval = function (prevRow, nextRow) {
+    var $prevRow = $(prevRow);
+    var minIndent, maxIndent;
+
+    // Minimum indentation:
+    // Do not orphan the next row.
+    minIndent = nextRow ? $(nextRow).find('.indentation').length : 0;
+
+    // Maximum indentation:
+    if (!prevRow || $prevRow.is(':not(.draggable)') || $(this.element).is('.tabledrag-root')) {
+      // Do not indent:
+      // - the first row in the table,
+      // - rows dragged below a non-draggable row,
+      // - 'root' rows.
+      maxIndent = 0;
     }
     else {
-      prevRow = $row.prev('tr').get(0);
-      nextRow = row;
+      // Do not go deeper than as a child of the previous row.
+      maxIndent = $prevRow.find('.indentation').length + ($prevRow.is('.tabledrag-leaf') ? 0 : 1);
+      // Limit by the maximum allowed depth for the table.
+      if (this.maxDepth) {
+        maxIndent = Math.min(maxIndent, this.maxDepth - (this.groupDepth - this.indents));
+      }
     }
-    this.interval = this.validIndentInterval(prevRow, nextRow);
 
-    // We have an invalid swap if the valid indentations interval is empty.
-    if (this.interval.min > this.interval.max) {
-      return false;
-    }
-  }
-
-  // Do not let an un-draggable first row have anything put before it.
-  if (this.table.tBodies[0].rows[0] === row && $row.is(':not(.draggable)')) {
-    return false;
-  }
-
-  return true;
-};
-
-/**
- * Perform the swap between two rows.
- *
- * @param position
- *   Whether the swap will occur 'before' or 'after' the given row.
- * @param row
- *   DOM element what will be swapped with the row group.
- */
-Drupal.tableDrag.prototype.row.prototype.swap = function (position, row) {
-  // Makes sure only DOM object are passed to Drupal.detachBehaviors().
-  this.group.forEach(function (row) {
-    Drupal.detachBehaviors(row, drupalSettings, 'move');
-  });
-  $(row)[position](this.group);
-  // Makes sure only DOM object are passed to Drupal.attachBehaviors()s.
-  this.group.forEach(function (row) {
-    Drupal.attachBehaviors(row, drupalSettings);
-  });
-  this.changed = true;
-  this.onSwap(row);
-};
-
-/**
- * Determine the valid indentations interval for the row at a given position
- * in the table.
- *
- * @param prevRow
- *   DOM object for the row before the tested position
- *   (or null for first position in the table).
- * @param nextRow
- *   DOM object for the row after the tested position
- *   (or null for last position in the table).
- */
-Drupal.tableDrag.prototype.row.prototype.validIndentInterval = function (prevRow, nextRow) {
-  var $prevRow = $(prevRow);
-  var minIndent, maxIndent;
-
-  // Minimum indentation:
-  // Do not orphan the next row.
-  minIndent = nextRow ? $(nextRow).find('.indentation').length : 0;
-
-  // Maximum indentation:
-  if (!prevRow || $prevRow.is(':not(.draggable)') || $(this.element).is('.tabledrag-root')) {
-    // Do not indent:
-    // - the first row in the table,
-    // - rows dragged below a non-draggable row,
-    // - 'root' rows.
-    maxIndent = 0;
-  }
-  else {
-    // Do not go deeper than as a child of the previous row.
-    maxIndent = $prevRow.find('.indentation').length + ($prevRow.is('.tabledrag-leaf') ? 0 : 1);
-    // Limit by the maximum allowed depth for the table.
-    if (this.maxDepth) {
-      maxIndent = Math.min(maxIndent, this.maxDepth - (this.groupDepth - this.indents));
+    return { 'min': minIndent, 'max': maxIndent };
+  };
+
+  /**
+   * Indent a row within the legal bounds of the table.
+   *
+   * @param indentDiff
+   *   The number of additional indentations proposed for the row (can be
+   *   positive or negative). This number will be adjusted to nearest valid
+   *   indentation level for the row.
+   */
+  Drupal.tableDrag.prototype.row.prototype.indent = function (indentDiff) {
+    var $group = $(this.group);
+    // Determine the valid indentations interval if not available yet.
+    if (!this.interval) {
+      var prevRow = $(this.element).prev('tr').get(0);
+      var nextRow = $group.filter(':last').next('tr').get(0);
+      this.interval = this.validIndentInterval(prevRow, nextRow);
     }
-  }
-
-  return { 'min': minIndent, 'max': maxIndent };
-};
-
-/**
- * Indent a row within the legal bounds of the table.
- *
- * @param indentDiff
- *   The number of additional indentations proposed for the row (can be
- *   positive or negative). This number will be adjusted to nearest valid
- *   indentation level for the row.
- */
-Drupal.tableDrag.prototype.row.prototype.indent = function (indentDiff) {
-  var $group = $(this.group);
-  // Determine the valid indentations interval if not available yet.
-  if (!this.interval) {
-    var prevRow = $(this.element).prev('tr').get(0);
-    var nextRow = $group.filter(':last').next('tr').get(0);
-    this.interval = this.validIndentInterval(prevRow, nextRow);
-  }
-
-  // Adjust to the nearest valid indentation.
-  var indent = this.indents + indentDiff;
-  indent = Math.max(indent, this.interval.min);
-  indent = Math.min(indent, this.interval.max);
-  indentDiff = indent - this.indents;
-
-  for (var n = 1; n <= Math.abs(indentDiff); n++) {
-    // Add or remove indentations.
-    if (indentDiff < 0) {
-      $group.find('.indentation:first').remove();
-      this.indents--;
+
+    // Adjust to the nearest valid indentation.
+    var indent = this.indents + indentDiff;
+    indent = Math.max(indent, this.interval.min);
+    indent = Math.min(indent, this.interval.max);
+    indentDiff = indent - this.indents;
+
+    for (var n = 1; n <= Math.abs(indentDiff); n++) {
+      // Add or remove indentations.
+      if (indentDiff < 0) {
+        $group.find('.indentation:first').remove();
+        this.indents--;
+      }
+      else {
+        $group.find('td:first').prepend(Drupal.theme('tableDragIndentation'));
+        this.indents++;
+      }
     }
-    else {
-      $group.find('td:first').prepend(Drupal.theme('tableDragIndentation'));
-      this.indents++;
+    if (indentDiff) {
+      // Update indentation for this row.
+      this.changed = true;
+      this.groupDepth += indentDiff;
+      this.onIndent();
     }
-  }
-  if (indentDiff) {
-    // Update indentation for this row.
-    this.changed = true;
-    this.groupDepth += indentDiff;
-    this.onIndent();
-  }
-
-  return indentDiff;
-};
-
-/**
- * Find all siblings for a row, either according to its subgroup or indentation.
- * Note that the passed-in row is included in the list of siblings.
- *
- * @param settings
- *   The field settings we're using to identify what constitutes a sibling.
- */
-Drupal.tableDrag.prototype.row.prototype.findSiblings = function (rowSettings) {
-  var siblings = [];
-  var directions = ['prev', 'next'];
-  var rowIndentation = this.indents;
-  var checkRowIndentation;
-  for (var d = 0; d < directions.length; d++) {
-    var checkRow = $(this.element)[directions[d]]();
-    while (checkRow.length) {
-      // Check that the sibling contains a similar target field.
-      if (checkRow.find('.' + rowSettings.target)) {
-        // Either add immediately if this is a flat table, or check to ensure
-        // that this row has the same level of indentation.
-        if (this.indentEnabled) {
-          checkRowIndentation = checkRow.find('.indentation').length;
-        }
 
-        if (!(this.indentEnabled) || (checkRowIndentation === rowIndentation)) {
-          siblings.push(checkRow[0]);
+    return indentDiff;
+  };
+
+  /**
+   * Find all siblings for a row, either according to its subgroup or indentation.
+   * Note that the passed-in row is included in the list of siblings.
+   *
+   * @param settings
+   *   The field settings we're using to identify what constitutes a sibling.
+   */
+  Drupal.tableDrag.prototype.row.prototype.findSiblings = function (rowSettings) {
+    var siblings = [];
+    var directions = ['prev', 'next'];
+    var rowIndentation = this.indents;
+    var checkRowIndentation;
+    for (var d = 0; d < directions.length; d++) {
+      var checkRow = $(this.element)[directions[d]]();
+      while (checkRow.length) {
+        // Check that the sibling contains a similar target field.
+        if (checkRow.find('.' + rowSettings.target)) {
+          // Either add immediately if this is a flat table, or check to ensure
+          // that this row has the same level of indentation.
+          if (this.indentEnabled) {
+            checkRowIndentation = checkRow.find('.indentation').length;
+          }
+
+          if (!(this.indentEnabled) || (checkRowIndentation === rowIndentation)) {
+            siblings.push(checkRow[0]);
+          }
+          else if (checkRowIndentation < rowIndentation) {
+            // No need to keep looking for siblings when we get to a parent.
+            break;
+          }
         }
-        else if (checkRowIndentation < rowIndentation) {
-          // No need to keep looking for siblings when we get to a parent.
+        else {
           break;
         }
+        checkRow = checkRow[directions[d]]();
       }
-      else {
-        break;
+      // Since siblings are added in reverse order for previous, reverse the
+      // completed list of previous siblings. Add the current row and continue.
+      if (directions[d] === 'prev') {
+        siblings.reverse();
+        siblings.push(this.element);
+      }
+    }
+    return siblings;
+  };
+
+  /**
+   * Remove indentation helper classes from the current row group.
+   */
+  Drupal.tableDrag.prototype.row.prototype.removeIndentClasses = function () {
+    for (var n in this.children) {
+      if (this.children.hasOwnProperty(n)) {
+        $(this.children[n]).find('.indentation')
+          .removeClass('tree-child')
+          .removeClass('tree-child-first')
+          .removeClass('tree-child-last')
+          .removeClass('tree-child-horizontal');
       }
-      checkRow = checkRow[directions[d]]();
     }
-    // Since siblings are added in reverse order for previous, reverse the
-    // completed list of previous siblings. Add the current row and continue.
-    if (directions[d] === 'prev') {
-      siblings.reverse();
-      siblings.push(this.element);
+  };
+
+  /**
+   * Add an asterisk or other marker to the changed row.
+   */
+  Drupal.tableDrag.prototype.row.prototype.markChanged = function () {
+    var marker = Drupal.theme('tableDragChangedMarker');
+    var cell = $(this.element).find('td:first');
+    if (cell.find('abbr.tabledrag-changed').length === 0) {
+      cell.append(marker);
     }
-  }
-  return siblings;
-};
-
-/**
- * Remove indentation helper classes from the current row group.
- */
-Drupal.tableDrag.prototype.row.prototype.removeIndentClasses = function () {
-  for (var n in this.children) {
-    if (this.children.hasOwnProperty(n)) {
-      $(this.children[n]).find('.indentation')
-        .removeClass('tree-child')
-        .removeClass('tree-child-first')
-        .removeClass('tree-child-last')
-        .removeClass('tree-child-horizontal');
+  };
+
+  /**
+   * Stub function. Allows a custom handler when a row is indented.
+   */
+  Drupal.tableDrag.prototype.row.prototype.onIndent = function () {
+    return null;
+  };
+
+  /**
+   * Stub function. Allows a custom handler when a row is swapped.
+   */
+  Drupal.tableDrag.prototype.row.prototype.onSwap = function (swappedRow) {
+    return null;
+  };
+
+  $.extend(Drupal.theme, {
+    tableDragChangedMarker: function () {
+      return '<abbr class="warning tabledrag-changed" title="' + Drupal.t('Changed') + '">*</abbr>';
+    },
+    tableDragIndentation: function () {
+      return '<div class="indentation">&nbsp;</div>';
+    },
+    tableDragChangedWarning: function () {
+      return '<div class="tabledrag-changed-warning messages messages--warning" role="alert">' + Drupal.theme('tableDragChangedMarker') + ' ' + Drupal.t('You have unsaved changes.') + '</div>';
     }
-  }
-};
-
-/**
- * Add an asterisk or other marker to the changed row.
- */
-Drupal.tableDrag.prototype.row.prototype.markChanged = function () {
-  var marker = Drupal.theme('tableDragChangedMarker');
-  var cell = $(this.element).find('td:first');
-  if (cell.find('abbr.tabledrag-changed').length === 0) {
-    cell.append(marker);
-  }
-};
-
-/**
- * Stub function. Allows a custom handler when a row is indented.
- */
-Drupal.tableDrag.prototype.row.prototype.onIndent = function () {
-  return null;
-};
-
-/**
- * Stub function. Allows a custom handler when a row is swapped.
- */
-Drupal.tableDrag.prototype.row.prototype.onSwap = function (swappedRow) {
-  return null;
-};
-
-$.extend(Drupal.theme, {
-  tableDragChangedMarker: function () {
-    return '<abbr class="warning tabledrag-changed" title="' + Drupal.t('Changed') + '">*</abbr>';
-  },
-  tableDragIndentation: function () {
-    return '<div class="indentation">&nbsp;</div>';
-  },
-  tableDragChangedWarning: function () {
-    return '<div class="tabledrag-changed-warning messages messages--warning" role="alert">' + Drupal.theme('tableDragChangedMarker') + ' ' + Drupal.t('You have unsaved changes.') + '</div>';
-  }
-});
+  });
 
 })(jQuery, Drupal, drupalSettings);
diff --git a/core/misc/tableheader.js b/core/misc/tableheader.js
index 2519304..a558dd6 100644
--- a/core/misc/tableheader.js
+++ b/core/misc/tableheader.js
@@ -1,254 +1,254 @@
 (function ($, Drupal, displace) {
 
-"use strict";
-
-/**
- * Attaches sticky table headers.
- */
-Drupal.behaviors.tableHeader = {
-  attach: function (context) {
-    $(window).one('scroll.TableHeaderInit', {context: context}, tableHeaderInitHandler);
-  }
-};
+  "use strict";
 
-function scrollValue(position) {
-  return document.documentElement[position] || document.body[position];
-}
+  /**
+   * Attaches sticky table headers.
+   */
+  Drupal.behaviors.tableHeader = {
+    attach: function (context) {
+      $(window).one('scroll.TableHeaderInit', {context: context}, tableHeaderInitHandler);
+    }
+  };
 
-// Select and initialize sticky table headers.
-function tableHeaderInitHandler(e) {
-  var $tables = $(e.data.context).find('table.sticky-enabled').once('tableheader');
-  for (var i = 0, il = $tables.length; i < il; i++) {
-    TableHeader.tables.push(new TableHeader($tables[i]));
+  function scrollValue(position) {
+    return document.documentElement[position] || document.body[position];
   }
-}
 
-// Helper method to loop through tables and execute a method.
-function forTables(method, arg) {
-  var tables = TableHeader.tables;
-  for (var i = 0, il = tables.length; i < il; i++) {
-    tables[i][method](arg);
+  // Select and initialize sticky table headers.
+  function tableHeaderInitHandler(e) {
+    var $tables = $(e.data.context).find('table.sticky-enabled').once('tableheader');
+    for (var i = 0, il = $tables.length; i < il; i++) {
+      TableHeader.tables.push(new TableHeader($tables[i]));
+    }
   }
-}
-
-function tableHeaderResizeHandler(e) {
-  forTables('recalculateSticky');
-}
 
-function tableHeaderOnScrollHandler(e) {
-  forTables('onScroll');
-}
+  // Helper method to loop through tables and execute a method.
+  function forTables(method, arg) {
+    var tables = TableHeader.tables;
+    for (var i = 0, il = tables.length; i < il; i++) {
+      tables[i][method](arg);
+    }
+  }
 
-function tableHeaderOffsetChangeHandler(e, offsets) {
-  forTables('stickyPosition', offsets.top);
-}
+  function tableHeaderResizeHandler(e) {
+    forTables('recalculateSticky');
+  }
 
-// Bind event that need to change all tables.
-$(window).on({
-  /**
-   * When resizing table width can change, recalculate everything.
-   */
-  'resize.TableHeader': tableHeaderResizeHandler,
+  function tableHeaderOnScrollHandler(e) {
+    forTables('onScroll');
+  }
 
-  /**
-   * Bind only one event to take care of calling all scroll callbacks.
-   */
-  'scroll.TableHeader': tableHeaderOnScrollHandler
-});
-// Bind to custom Drupal events.
-$(document).on({
-  /**
-   * Recalculate columns width when window is resized and when show/hide
-   * weight is triggered.
-   */
-  'columnschange.TableHeader': tableHeaderResizeHandler,
+  function tableHeaderOffsetChangeHandler(e, offsets) {
+    forTables('stickyPosition', offsets.top);
+  }
 
-  /**
-   * Recalculate TableHeader.topOffset when viewport is resized
-   */
-  'drupalViewportOffsetChange.TableHeader': tableHeaderOffsetChangeHandler
-});
-
-/**
- * Constructor for the tableHeader object. Provides sticky table headers.
- *
- * TableHeader will make the current table header stick to the top of the page
- * if the table is very long.
- *
- * @param table
- *   DOM object for the table to add a sticky header to.
- *
- * @constructor
- */
-function TableHeader(table) {
-  var $table = $(table);
-
-  this.$originalTable = $table;
-  this.$originalHeader = $table.children('thead');
-  this.$originalHeaderCells = this.$originalHeader.find('> tr > th');
-  this.displayWeight = null;
-
-  this.$originalTable.addClass('sticky-table');
-  this.tableHeight = $table[0].clientHeight;
-  this.tableOffset = this.$originalTable.offset();
-
-  // React to columns change to avoid making checks in the scroll callback.
-  this.$originalTable.on('columnschange', {tableHeader: this}, function (e, display) {
-    var tableHeader = e.data.tableHeader;
-    if (tableHeader.displayWeight === null || tableHeader.displayWeight !== display) {
-      tableHeader.recalculateSticky();
-    }
-    tableHeader.displayWeight = display;
+  // Bind event that need to change all tables.
+  $(window).on({
+    /**
+     * When resizing table width can change, recalculate everything.
+     */
+    'resize.TableHeader': tableHeaderResizeHandler,
+
+    /**
+     * Bind only one event to take care of calling all scroll callbacks.
+     */
+    'scroll.TableHeader': tableHeaderOnScrollHandler
+  });
+  // Bind to custom Drupal events.
+  $(document).on({
+    /**
+     * Recalculate columns width when window is resized and when show/hide
+     * weight is triggered.
+     */
+    'columnschange.TableHeader': tableHeaderResizeHandler,
+
+    /**
+     * Recalculate TableHeader.topOffset when viewport is resized
+     */
+    'drupalViewportOffsetChange.TableHeader': tableHeaderOffsetChangeHandler
   });
 
-  // Create and display sticky header.
-  this.createSticky();
-}
-
-/**
- * Store the state of TableHeader.
- */
-$.extend(TableHeader, {
   /**
-   * This will store the state of all processed tables.
+   * Constructor for the tableHeader object. Provides sticky table headers.
    *
-   * @type {Array}
-   */
-  tables: []
-});
-
-/**
- * Extend TableHeader prototype.
- */
-$.extend(TableHeader.prototype, {
-  /**
-   * Minimum height in pixels for the table to have a sticky header.
-   */
-  minHeight: 100,
-
-  /**
-   * Absolute position of the table on the page.
-   */
-  tableOffset: null,
-
-  /**
-   * Absolute position of the table on the page.
-   */
-  tableHeight: null,
-
-  /**
-   * Boolean storing the sticky header visibility state.
+   * TableHeader will make the current table header stick to the top of the page
+   * if the table is very long.
+   *
+   * @param table
+   *   DOM object for the table to add a sticky header to.
+   *
+   * @constructor
    */
-  stickyVisible: false,
+  function TableHeader(table) {
+    var $table = $(table);
 
-  /**
-   * Create the duplicate header.
-   */
-  createSticky: function () {
-    // Clone the table header so it inherits original jQuery properties.
-    var $stickyHeader = this.$originalHeader.clone(true);
-    // Hide the table to avoid a flash of the header clone upon page load.
-    this.$stickyTable = $('<table class="sticky-header"/>')
-      .css({
-        visibility: 'hidden',
-        position: 'fixed',
-        top: '0px'
-      })
-      .append($stickyHeader)
-      .insertBefore(this.$originalTable);
-
-    this.$stickyHeaderCells = $stickyHeader.find('> tr > th');
-
-    // Initialize all computations.
-    this.recalculateSticky();
-  },
+    this.$originalTable = $table;
+    this.$originalHeader = $table.children('thead');
+    this.$originalHeaderCells = this.$originalHeader.find('> tr > th');
+    this.displayWeight = null;
 
-  /**
-   * Set absolute position of sticky.
-   *
-   * @param offsetTop
-   * @param offsetLeft
-   */
-  stickyPosition: function (offsetTop, offsetLeft) {
-    var css = {};
-    if (!isNaN(offsetTop)) {
-      css.top = offsetTop + 'px';
-    }
-    if (!isNaN(offsetLeft)) {
-      css.left = (this.tableOffset.left - offsetLeft) + 'px';
-    }
-    return this.$stickyTable.css(css);
-  },
+    this.$originalTable.addClass('sticky-table');
+    this.tableHeight = $table[0].clientHeight;
+    this.tableOffset = this.$originalTable.offset();
 
-  /**
-   * Returns true if sticky is currently visible.
-   */
-  checkStickyVisible: function () {
-    var scrollTop = scrollValue('scrollTop');
-    var tableTop = this.tableOffset.top - displace.offsets.top;
-    var tableBottom = tableTop + this.tableHeight;
-    var visible = false;
-
-    if (tableTop < scrollTop && scrollTop < (tableBottom - this.minHeight)) {
-      visible = true;
-    }
+    // React to columns change to avoid making checks in the scroll callback.
+    this.$originalTable.on('columnschange', {tableHeader: this}, function (e, display) {
+      var tableHeader = e.data.tableHeader;
+      if (tableHeader.displayWeight === null || tableHeader.displayWeight !== display) {
+        tableHeader.recalculateSticky();
+      }
+      tableHeader.displayWeight = display;
+    });
 
-    this.stickyVisible = visible;
-    return visible;
-  },
+    // Create and display sticky header.
+    this.createSticky();
+  }
 
   /**
-   * Check if sticky header should be displayed.
-   *
-   * This function is throttled to once every 250ms to avoid unnecessary calls.
-   *
-   * @param event
+   * Store the state of TableHeader.
    */
-  onScroll: function (e) {
-    this.checkStickyVisible();
-    // Track horizontal positioning relative to the viewport.
-    this.stickyPosition(null, scrollValue('scrollLeft'));
-    this.$stickyTable.css('visibility', this.stickyVisible ? 'visible' : 'hidden');
-  },
+  $.extend(TableHeader, {
+    /**
+     * This will store the state of all processed tables.
+     *
+     * @type {Array}
+     */
+    tables: []
+  });
 
   /**
-   * Event handler: recalculates position of the sticky table header.
-   *
-   * @param event
-   *   Event being triggered.
+   * Extend TableHeader prototype.
    */
-  recalculateSticky: function (event) {
-    // Update table size.
-    this.tableHeight = this.$originalTable[0].clientHeight;
-
-    // Update offset top.
-    displace.offsets.top = displace.calculateOffset('top');
-    this.tableOffset = this.$originalTable.offset();
-    this.stickyPosition(displace.offsets.top, scrollValue('scrollLeft'));
-
-    // Update columns width.
-    var $that = null;
-    var $stickyCell = null;
-    var display = null;
-    // Resize header and its cell widths.
-    // Only apply width to visible table cells. This prevents the header from
-    // displaying incorrectly when the sticky header is no longer visible.
-    for (var i = 0, il = this.$originalHeaderCells.length; i < il; i++) {
-      $that = $(this.$originalHeaderCells[i]);
-      $stickyCell = this.$stickyHeaderCells.eq($that.index());
-      display = $that.css('display');
-      if (display !== 'none') {
-        $stickyCell.css({'width': $that.css('width'), 'display': display});
+  $.extend(TableHeader.prototype, {
+    /**
+     * Minimum height in pixels for the table to have a sticky header.
+     */
+    minHeight: 100,
+
+    /**
+     * Absolute position of the table on the page.
+     */
+    tableOffset: null,
+
+    /**
+     * Absolute position of the table on the page.
+     */
+    tableHeight: null,
+
+    /**
+     * Boolean storing the sticky header visibility state.
+     */
+    stickyVisible: false,
+
+    /**
+     * Create the duplicate header.
+     */
+    createSticky: function () {
+      // Clone the table header so it inherits original jQuery properties.
+      var $stickyHeader = this.$originalHeader.clone(true);
+      // Hide the table to avoid a flash of the header clone upon page load.
+      this.$stickyTable = $('<table class="sticky-header"/>')
+        .css({
+          visibility: 'hidden',
+          position: 'fixed',
+          top: '0px'
+        })
+        .append($stickyHeader)
+        .insertBefore(this.$originalTable);
+
+      this.$stickyHeaderCells = $stickyHeader.find('> tr > th');
+
+      // Initialize all computations.
+      this.recalculateSticky();
+    },
+
+    /**
+     * Set absolute position of sticky.
+     *
+     * @param offsetTop
+     * @param offsetLeft
+     */
+    stickyPosition: function (offsetTop, offsetLeft) {
+      var css = {};
+      if (!isNaN(offsetTop)) {
+        css.top = offsetTop + 'px';
       }
-      else {
-        $stickyCell.css('display', 'none');
+      if (!isNaN(offsetLeft)) {
+        css.left = (this.tableOffset.left - offsetLeft) + 'px';
       }
+      return this.$stickyTable.css(css);
+    },
+
+    /**
+     * Returns true if sticky is currently visible.
+     */
+    checkStickyVisible: function () {
+      var scrollTop = scrollValue('scrollTop');
+      var tableTop = this.tableOffset.top - displace.offsets.top;
+      var tableBottom = tableTop + this.tableHeight;
+      var visible = false;
+
+      if (tableTop < scrollTop && scrollTop < (tableBottom - this.minHeight)) {
+        visible = true;
+      }
+
+      this.stickyVisible = visible;
+      return visible;
+    },
+
+    /**
+     * Check if sticky header should be displayed.
+     *
+     * This function is throttled to once every 250ms to avoid unnecessary calls.
+     *
+     * @param event
+     */
+    onScroll: function (e) {
+      this.checkStickyVisible();
+      // Track horizontal positioning relative to the viewport.
+      this.stickyPosition(null, scrollValue('scrollLeft'));
+      this.$stickyTable.css('visibility', this.stickyVisible ? 'visible' : 'hidden');
+    },
+
+    /**
+     * Event handler: recalculates position of the sticky table header.
+     *
+     * @param event
+     *   Event being triggered.
+     */
+    recalculateSticky: function (event) {
+      // Update table size.
+      this.tableHeight = this.$originalTable[0].clientHeight;
+
+      // Update offset top.
+      displace.offsets.top = displace.calculateOffset('top');
+      this.tableOffset = this.$originalTable.offset();
+      this.stickyPosition(displace.offsets.top, scrollValue('scrollLeft'));
+
+      // Update columns width.
+      var $that = null;
+      var $stickyCell = null;
+      var display = null;
+      // Resize header and its cell widths.
+      // Only apply width to visible table cells. This prevents the header from
+      // displaying incorrectly when the sticky header is no longer visible.
+      for (var i = 0, il = this.$originalHeaderCells.length; i < il; i++) {
+        $that = $(this.$originalHeaderCells[i]);
+        $stickyCell = this.$stickyHeaderCells.eq($that.index());
+        display = $that.css('display');
+        if (display !== 'none') {
+          $stickyCell.css({'width': $that.css('width'), 'display': display});
+        }
+        else {
+          $stickyCell.css('display', 'none');
+        }
+      }
+      this.$stickyTable.css('width', this.$originalTable.outerWidth());
     }
-    this.$stickyTable.css('width', this.$originalTable.outerWidth());
-  }
-});
+  });
 
-// Expose constructor in the public space.
-Drupal.TableHeader = TableHeader;
+  // Expose constructor in the public space.
+  Drupal.TableHeader = TableHeader;
 
 }(jQuery, Drupal, window.parent.Drupal.displace));
diff --git a/core/misc/tableresponsive.js b/core/misc/tableresponsive.js
index 4f45dee..33e7b8e 100644
--- a/core/misc/tableresponsive.js
+++ b/core/misc/tableresponsive.js
@@ -1,139 +1,139 @@
 (function ($, Drupal, window) {
 
-"use strict";
+  "use strict";
 
-/**
- * Attach the tableResponsive function to Drupal.behaviors.
- */
-Drupal.behaviors.tableResponsive = {
-  attach: function (context, settings) {
-    var $tables = $(context).find('table.responsive-enabled').once('tableresponsive');
-    if ($tables.length) {
-      for (var i = 0, il = $tables.length; i < il; i++) {
-        TableResponsive.tables.push(new TableResponsive($tables[i]));
+  /**
+   * Attach the tableResponsive function to Drupal.behaviors.
+   */
+  Drupal.behaviors.tableResponsive = {
+    attach: function (context, settings) {
+      var $tables = $(context).find('table.responsive-enabled').once('tableresponsive');
+      if ($tables.length) {
+        for (var i = 0, il = $tables.length; i < il; i++) {
+          TableResponsive.tables.push(new TableResponsive($tables[i]));
+        }
       }
     }
-  }
-};
+  };
 
-/**
- * The TableResponsive object optimizes table presentation for all screen sizes.
- *
- * A responsive table hides columns at small screen sizes, leaving the most
- * important columns visible to the end user. Users should not be prevented from
- * accessing all columns, however. This class adds a toggle to a table with
- * hidden columns that exposes the columns. Exposing the columns will likely
- * break layouts, but it provides the user with a means to access data, which
- * is a guiding principle of responsive design.
- */
-function TableResponsive (table) {
-  this.table = table;
-  this.$table = $(table);
-  this.showText = Drupal.t('Show all columns');
-  this.hideText = Drupal.t('Hide unimportant columns');
-  // Store a reference to the header elements of the table so that the DOM is
-  // traversed only once to find them.
-  this.$headers = this.$table.find('th');
-  // Add a link before the table for users to show or hide weight columns.
-  this.$link = $('<button type="button" class="link tableresponsive-toggle"></button>')
-    .attr('title', Drupal.t('Show table cells that were hidden to make the table fit within a small screen.'))
-    .on('click', $.proxy(this, 'eventhandlerToggleColumns'));
+  /**
+   * The TableResponsive object optimizes table presentation for all screen sizes.
+   *
+   * A responsive table hides columns at small screen sizes, leaving the most
+   * important columns visible to the end user. Users should not be prevented from
+   * accessing all columns, however. This class adds a toggle to a table with
+   * hidden columns that exposes the columns. Exposing the columns will likely
+   * break layouts, but it provides the user with a means to access data, which
+   * is a guiding principle of responsive design.
+   */
+  function TableResponsive(table) {
+    this.table = table;
+    this.$table = $(table);
+    this.showText = Drupal.t('Show all columns');
+    this.hideText = Drupal.t('Hide unimportant columns');
+    // Store a reference to the header elements of the table so that the DOM is
+    // traversed only once to find them.
+    this.$headers = this.$table.find('th');
+    // Add a link before the table for users to show or hide weight columns.
+    this.$link = $('<button type="button" class="link tableresponsive-toggle"></button>')
+      .attr('title', Drupal.t('Show table cells that were hidden to make the table fit within a small screen.'))
+      .on('click', $.proxy(this, 'eventhandlerToggleColumns'));
 
-  this.$table.before($('<div class="tableresponsive-toggle-columns"></div>').append(this.$link));
+    this.$table.before($('<div class="tableresponsive-toggle-columns"></div>').append(this.$link));
 
-  // Attach a resize handler to the window.
-  $(window)
-    .on('resize.tableresponsive', $.proxy(this, 'eventhandlerEvaluateColumnVisibility'))
-    .trigger('resize.tableresponsive');
-}
+    // Attach a resize handler to the window.
+    $(window)
+      .on('resize.tableresponsive', $.proxy(this, 'eventhandlerEvaluateColumnVisibility'))
+      .trigger('resize.tableresponsive');
+  }
 
-/**
- * Extend the TableResponsive function with a list of managed tables.
- */
-$.extend(TableResponsive, {
-  tables: []
-});
+  /**
+   * Extend the TableResponsive function with a list of managed tables.
+   */
+  $.extend(TableResponsive, {
+    tables: []
+  });
 
-/**
- * Associates an action link with the table that will show hidden columns.
- *
- * Columns are assumed to be hidden if their header has the class priority-low
- * or priority-medium.
- */
-$.extend(TableResponsive.prototype, {
-  eventhandlerEvaluateColumnVisibility: function (e) {
-    var pegged = parseInt(this.$link.data('pegged'), 10);
-    var hiddenLength = this.$headers.filter('.priority-medium:hidden, .priority-low:hidden').length;
-    // If the table has hidden columns, associate an action link with the table
-    // to show the columns.
-    if (hiddenLength > 0) {
-      this.$link.show().text(this.showText);
-    }
-    // When the toggle is pegged, its presence is maintained because the user
-    // has interacted with it. This is necessary to keep the link visible if the
-    // user adjusts screen size and changes the visibilty of columns.
-    if (!pegged && hiddenLength === 0) {
-      this.$link.hide().text(this.hideText);
-    }
-  },
-  // Toggle the visibility of columns classed with either 'priority-low' or
-  // 'priority-medium'.
-  eventhandlerToggleColumns: function (e) {
-    e.preventDefault();
-    var self = this;
-    var $hiddenHeaders = this.$headers.filter('.priority-medium:hidden, .priority-low:hidden');
-    this.$revealedCells = this.$revealedCells || $();
-    // Reveal hidden columns.
-    if ($hiddenHeaders.length > 0) {
-      $hiddenHeaders.each(function (index, element) {
-        var $header = $(this);
-        var position = $header.prevAll('th').length;
-        self.$table.find('tbody tr').each(function () {
-          var $cells = $(this).find('td:eq(' + position + ')');
-          $cells.show();
-          // Keep track of the revealed cells, so they can be hidden later.
-          self.$revealedCells = $().add(self.$revealedCells).add($cells);
+  /**
+   * Associates an action link with the table that will show hidden columns.
+   *
+   * Columns are assumed to be hidden if their header has the class priority-low
+   * or priority-medium.
+   */
+  $.extend(TableResponsive.prototype, {
+    eventhandlerEvaluateColumnVisibility: function (e) {
+      var pegged = parseInt(this.$link.data('pegged'), 10);
+      var hiddenLength = this.$headers.filter('.priority-medium:hidden, .priority-low:hidden').length;
+      // If the table has hidden columns, associate an action link with the table
+      // to show the columns.
+      if (hiddenLength > 0) {
+        this.$link.show().text(this.showText);
+      }
+      // When the toggle is pegged, its presence is maintained because the user
+      // has interacted with it. This is necessary to keep the link visible if the
+      // user adjusts screen size and changes the visibilty of columns.
+      if (!pegged && hiddenLength === 0) {
+        this.$link.hide().text(this.hideText);
+      }
+    },
+    // Toggle the visibility of columns classed with either 'priority-low' or
+    // 'priority-medium'.
+    eventhandlerToggleColumns: function (e) {
+      e.preventDefault();
+      var self = this;
+      var $hiddenHeaders = this.$headers.filter('.priority-medium:hidden, .priority-low:hidden');
+      this.$revealedCells = this.$revealedCells || $();
+      // Reveal hidden columns.
+      if ($hiddenHeaders.length > 0) {
+        $hiddenHeaders.each(function (index, element) {
+          var $header = $(this);
+          var position = $header.prevAll('th').length;
+          self.$table.find('tbody tr').each(function () {
+            var $cells = $(this).find('td:eq(' + position + ')');
+            $cells.show();
+            // Keep track of the revealed cells, so they can be hidden later.
+            self.$revealedCells = $().add(self.$revealedCells).add($cells);
+          });
+          $header.show();
+          // Keep track of the revealed headers, so they can be hidden later.
+          self.$revealedCells = $().add(self.$revealedCells).add($header);
         });
-        $header.show();
-        // Keep track of the revealed headers, so they can be hidden later.
-        self.$revealedCells = $().add(self.$revealedCells).add($header);
-      });
-      this.$link.text(this.hideText).data('pegged', 1);
-    }
-    // Hide revealed columns.
-    else {
-      this.$revealedCells.hide();
-      // Strip the 'display:none' declaration from the style attributes of
-      // the table cells that .hide() added.
-      this.$revealedCells.each(function (index, element) {
-        var $cell = $(this);
-        var properties = $cell.attr('style').split(';');
-        var newProps = [];
-        // The hide method adds display none to the element. The element should
-        // be returned to the same state it was in before the columns were
-        // revealed, so it is necessary to remove the display none
-        // value from the style attribute.
-        var match =  /^display\s*\:\s*none$/;
-        for (var i = 0; i < properties.length; i++) {
-          var prop = properties[i];
-          prop.trim();
-          // Find the display:none property and remove it.
-          var isDisplayNone = match.exec(prop);
-          if (isDisplayNone) {
-            continue;
+        this.$link.text(this.hideText).data('pegged', 1);
+      }
+      // Hide revealed columns.
+      else {
+        this.$revealedCells.hide();
+        // Strip the 'display:none' declaration from the style attributes of
+        // the table cells that .hide() added.
+        this.$revealedCells.each(function (index, element) {
+          var $cell = $(this);
+          var properties = $cell.attr('style').split(';');
+          var newProps = [];
+          // The hide method adds display none to the element. The element should
+          // be returned to the same state it was in before the columns were
+          // revealed, so it is necessary to remove the display none
+          // value from the style attribute.
+          var match = /^display\s*\:\s*none$/;
+          for (var i = 0; i < properties.length; i++) {
+            var prop = properties[i];
+            prop.trim();
+            // Find the display:none property and remove it.
+            var isDisplayNone = match.exec(prop);
+            if (isDisplayNone) {
+              continue;
+            }
+            newProps.push(prop);
           }
-          newProps.push(prop);
-        }
-        // Return the rest of the style attribute values to the element.
-        $cell.attr('style', newProps.join(';'));
-      });
-      this.$link.text(this.showText).data('pegged', 0);
-      // Refresh the toggle link.
-      $(window).trigger('resize.tableresponsive');
+          // Return the rest of the style attribute values to the element.
+          $cell.attr('style', newProps.join(';'));
+        });
+        this.$link.text(this.showText).data('pegged', 0);
+        // Refresh the toggle link.
+        $(window).trigger('resize.tableresponsive');
+      }
     }
-  }
-});
-// Make the TableResponsive object available in the Drupal namespace.
-Drupal.TableResponsive = TableResponsive;
+  });
+  // Make the TableResponsive object available in the Drupal namespace.
+  Drupal.TableResponsive = TableResponsive;
 
 })(jQuery, Drupal, window);
diff --git a/core/misc/tableselect.js b/core/misc/tableselect.js
index da3a722..a55b97c 100644
--- a/core/misc/tableselect.js
+++ b/core/misc/tableselect.js
@@ -1,93 +1,93 @@
 (function ($, Drupal) {
 
-"use strict";
+  "use strict";
 
-Drupal.behaviors.tableSelect = {
-  attach: function (context, settings) {
-    // Select the inner-most table in case of nested tables.
-    $(context).find('th.select-all').closest('table').once('table-select', Drupal.tableSelect);
-  }
-};
-
-Drupal.tableSelect = function () {
-  // Do not add a "Select all" checkbox if there are no rows with checkboxes in the table
-  if ($(this).find('td input[type="checkbox"]').length === 0) {
-    return;
-  }
-
-  // Keep track of the table, which checkbox is checked and alias the settings.
-  var table = this, checkboxes, lastChecked;
-  var $table = $(table);
-  var strings = { 'selectAll': Drupal.t('Select all rows in this table'), 'selectNone': Drupal.t('Deselect all rows in this table') };
-  var updateSelectAll = function (state) {
-    // Update table's select-all checkbox (and sticky header's if available).
-    $table.prev('table.sticky-header').addBack().find('th.select-all input[type="checkbox"]').each(function() {
-      $(this).attr('title', state ? strings.selectNone : strings.selectAll);
-      this.checked = state;
-    });
+  Drupal.behaviors.tableSelect = {
+    attach: function (context, settings) {
+      // Select the inner-most table in case of nested tables.
+      $(context).find('th.select-all').closest('table').once('table-select', Drupal.tableSelect);
+    }
   };
 
-  // Find all <th> with class select-all, and insert the check all checkbox.
-  $table.find('th.select-all').prepend($('<input type="checkbox" class="form-checkbox" />').attr('title', strings.selectAll)).on('click', function (event) {
-    if ($(event.target).is('input[type="checkbox"]')) {
-      // Loop through all checkboxes and set their state to the select all checkbox' state.
-      checkboxes.each(function () {
-        this.checked = event.target.checked;
-        // Either add or remove the selected class based on the state of the check all checkbox.
-        $(this).closest('tr').toggleClass('selected', this.checked);
-      });
-      // Update the title and the state of the check all box.
-      updateSelectAll(event.target.checked);
+  Drupal.tableSelect = function () {
+    // Do not add a "Select all" checkbox if there are no rows with checkboxes in the table
+    if ($(this).find('td input[type="checkbox"]').length === 0) {
+      return;
     }
-  });
 
-  // For each of the checkboxes within the table that are not disabled.
-  checkboxes = $table.find('td input[type="checkbox"]:enabled').on('click', function (e) {
-    // Either add or remove the selected class based on the state of the check all checkbox.
-    $(this).closest('tr').toggleClass('selected', this.checked);
+    // Keep track of the table, which checkbox is checked and alias the settings.
+    var table = this, checkboxes, lastChecked;
+    var $table = $(table);
+    var strings = { 'selectAll': Drupal.t('Select all rows in this table'), 'selectNone': Drupal.t('Deselect all rows in this table') };
+    var updateSelectAll = function (state) {
+      // Update table's select-all checkbox (and sticky header's if available).
+      $table.prev('table.sticky-header').addBack().find('th.select-all input[type="checkbox"]').each(function () {
+        $(this).attr('title', state ? strings.selectNone : strings.selectAll);
+        this.checked = state;
+      });
+    };
 
-    // If this is a shift click, we need to highlight everything in the range.
-    // Also make sure that we are actually checking checkboxes over a range and
-    // that a checkbox has been checked or unchecked before.
-    if (e.shiftKey && lastChecked && lastChecked !== e.target) {
-      // We use the checkbox's parent TR to do our range searching.
-      Drupal.tableSelectRange($(e.target).closest('tr')[0], $(lastChecked).closest('tr')[0], e.target.checked);
-    }
+    // Find all <th> with class select-all, and insert the check all checkbox.
+    $table.find('th.select-all').prepend($('<input type="checkbox" class="form-checkbox" />').attr('title', strings.selectAll)).on('click', function (event) {
+      if ($(event.target).is('input[type="checkbox"]')) {
+        // Loop through all checkboxes and set their state to the select all checkbox' state.
+        checkboxes.each(function () {
+          this.checked = event.target.checked;
+          // Either add or remove the selected class based on the state of the check all checkbox.
+          $(this).closest('tr').toggleClass('selected', this.checked);
+        });
+        // Update the title and the state of the check all box.
+        updateSelectAll(event.target.checked);
+      }
+    });
 
-    // If all checkboxes are checked, make sure the select-all one is checked too, otherwise keep unchecked.
-    updateSelectAll((checkboxes.length === checkboxes.filter(':checked').length));
+    // For each of the checkboxes within the table that are not disabled.
+    checkboxes = $table.find('td input[type="checkbox"]:enabled').on('click', function (e) {
+      // Either add or remove the selected class based on the state of the check all checkbox.
+      $(this).closest('tr').toggleClass('selected', this.checked);
 
-    // Keep track of the last checked checkbox.
-    lastChecked = e.target;
-  });
-};
+      // If this is a shift click, we need to highlight everything in the range.
+      // Also make sure that we are actually checking checkboxes over a range and
+      // that a checkbox has been checked or unchecked before.
+      if (e.shiftKey && lastChecked && lastChecked !== e.target) {
+        // We use the checkbox's parent TR to do our range searching.
+        Drupal.tableSelectRange($(e.target).closest('tr')[0], $(lastChecked).closest('tr')[0], e.target.checked);
+      }
 
-Drupal.tableSelectRange = function (from, to, state) {
-  // We determine the looping mode based on the the order of from and to.
-  var mode = from.rowIndex > to.rowIndex ? 'previousSibling' : 'nextSibling';
+      // If all checkboxes are checked, make sure the select-all one is checked too, otherwise keep unchecked.
+      updateSelectAll((checkboxes.length === checkboxes.filter(':checked').length));
 
-  // Traverse through the sibling nodes.
-  for (var i = from[mode], $i; i; i = i[mode]) {
-    // Make sure that we're only dealing with elements.
-    if (i.nodeType !== 1) {
-      continue;
-    }
-    $i = $(i);
-    // Either add or remove the selected class based on the state of the target checkbox.
-    $i.toggleClass('selected', state);
-    $i.find('input[type="checkbox"]').prop('checked', state);
+      // Keep track of the last checked checkbox.
+      lastChecked = e.target;
+    });
+  };
 
-    if (to.nodeType) {
-      // If we are at the end of the range, stop.
-      if (i === to) {
+  Drupal.tableSelectRange = function (from, to, state) {
+    // We determine the looping mode based on the the order of from and to.
+    var mode = from.rowIndex > to.rowIndex ? 'previousSibling' : 'nextSibling';
+
+    // Traverse through the sibling nodes.
+    for (var i = from[mode], $i; i; i = i[mode]) {
+      // Make sure that we're only dealing with elements.
+      if (i.nodeType !== 1) {
+        continue;
+      }
+      $i = $(i);
+      // Either add or remove the selected class based on the state of the target checkbox.
+      $i.toggleClass('selected', state);
+      $i.find('input[type="checkbox"]').prop('checked', state);
+
+      if (to.nodeType) {
+        // If we are at the end of the range, stop.
+        if (i === to) {
+          break;
+        }
+      }
+      // A faster alternative to doing $(i).filter(to).length.
+      else if ($.filter(to, [i]).r.length) {
         break;
       }
     }
-    // A faster alternative to doing $(i).filter(to).length.
-    else if ($.filter(to, [i]).r.length) {
-      break;
-    }
-  }
-};
+  };
 
 })(jQuery, Drupal);
diff --git a/core/misc/timezone.js b/core/misc/timezone.js
index 01f1820..9978971 100644
--- a/core/misc/timezone.js
+++ b/core/misc/timezone.js
@@ -1,68 +1,68 @@
 (function ($) {
 
-"use strict";
+  "use strict";
 
-/**
- * Set the client's system time zone as default values of form fields.
- */
-Drupal.behaviors.setTimezone = {
-  attach: function (context, settings) {
-    var $timezone = $(context).find('.timezone-detect').once('timezone');
-    if ($timezone.length) {
-      var dateString = Date();
-      // In some client environments, date strings include a time zone
-      // abbreviation, between 3 and 5 letters enclosed in parentheses,
-      // which can be interpreted by PHP.
-      var matches = dateString.match(/\(([A-Z]{3,5})\)/);
-      var abbreviation = matches ? matches[1] : 0;
+  /**
+   * Set the client's system time zone as default values of form fields.
+   */
+  Drupal.behaviors.setTimezone = {
+    attach: function (context, settings) {
+      var $timezone = $(context).find('.timezone-detect').once('timezone');
+      if ($timezone.length) {
+        var dateString = Date();
+        // In some client environments, date strings include a time zone
+        // abbreviation, between 3 and 5 letters enclosed in parentheses,
+        // which can be interpreted by PHP.
+        var matches = dateString.match(/\(([A-Z]{3,5})\)/);
+        var abbreviation = matches ? matches[1] : 0;
 
-      // For all other client environments, the abbreviation is set to "0"
-      // and the current offset from UTC and daylight saving time status are
-      // used to guess the time zone.
-      var dateNow = new Date();
-      var offsetNow = dateNow.getTimezoneOffset() * -60;
+        // For all other client environments, the abbreviation is set to "0"
+        // and the current offset from UTC and daylight saving time status are
+        // used to guess the time zone.
+        var dateNow = new Date();
+        var offsetNow = dateNow.getTimezoneOffset() * -60;
 
-      // Use January 1 and July 1 as test dates for determining daylight
-      // saving time status by comparing their offsets.
-      var dateJan = new Date(dateNow.getFullYear(), 0, 1, 12, 0, 0, 0);
-      var dateJul = new Date(dateNow.getFullYear(), 6, 1, 12, 0, 0, 0);
-      var offsetJan = dateJan.getTimezoneOffset() * -60;
-      var offsetJul = dateJul.getTimezoneOffset() * -60;
+        // Use January 1 and July 1 as test dates for determining daylight
+        // saving time status by comparing their offsets.
+        var dateJan = new Date(dateNow.getFullYear(), 0, 1, 12, 0, 0, 0);
+        var dateJul = new Date(dateNow.getFullYear(), 6, 1, 12, 0, 0, 0);
+        var offsetJan = dateJan.getTimezoneOffset() * -60;
+        var offsetJul = dateJul.getTimezoneOffset() * -60;
 
-      var isDaylightSavingTime;
-      // If the offset from UTC is identical on January 1 and July 1,
-      // assume daylight saving time is not used in this time zone.
-      if (offsetJan === offsetJul) {
-        isDaylightSavingTime = '';
-      }
-      // If the maximum annual offset is equivalent to the current offset,
-      // assume daylight saving time is in effect.
-      else if (Math.max(offsetJan, offsetJul) === offsetNow) {
-        isDaylightSavingTime = 1;
-      }
-      // Otherwise, assume daylight saving time is not in effect.
-      else {
-        isDaylightSavingTime = 0;
-      }
+        var isDaylightSavingTime;
+        // If the offset from UTC is identical on January 1 and July 1,
+        // assume daylight saving time is not used in this time zone.
+        if (offsetJan === offsetJul) {
+          isDaylightSavingTime = '';
+        }
+        // If the maximum annual offset is equivalent to the current offset,
+        // assume daylight saving time is in effect.
+        else if (Math.max(offsetJan, offsetJul) === offsetNow) {
+          isDaylightSavingTime = 1;
+        }
+        // Otherwise, assume daylight saving time is not in effect.
+        else {
+          isDaylightSavingTime = 0;
+        }
 
-      // Submit request to the system/timezone callback and set the form field
-      // to the response time zone. The client date is passed to the callback
-      // for debugging purposes. Submit a synchronous request to avoid database
-      // errors associated with concurrent requests during install.
-      var path = 'system/timezone/' + abbreviation + '/' + offsetNow + '/' + isDaylightSavingTime;
-      $.ajax({
-        async: false,
-        url: Drupal.url(path),
-        data: { date: dateString },
-        dataType: 'json',
-        success: function (data) {
-          if (data) {
-            $timezone.val(data);
+        // Submit request to the system/timezone callback and set the form field
+        // to the response time zone. The client date is passed to the callback
+        // for debugging purposes. Submit a synchronous request to avoid database
+        // errors associated with concurrent requests during install.
+        var path = 'system/timezone/' + abbreviation + '/' + offsetNow + '/' + isDaylightSavingTime;
+        $.ajax({
+          async: false,
+          url: Drupal.url(path),
+          data: { date: dateString },
+          dataType: 'json',
+          success: function (data) {
+            if (data) {
+              $timezone.val(data);
+            }
           }
-        }
-      });
+        });
+      }
     }
-  }
-};
+  };
 
 })(jQuery);
diff --git a/core/misc/vertical-tabs.js b/core/misc/vertical-tabs.js
index d45bcac..6efa4c3 100644
--- a/core/misc/vertical-tabs.js
+++ b/core/misc/vertical-tabs.js
@@ -1,215 +1,215 @@
 (function ($) {
 
-"use strict";
-
-/**
- * This script transforms a set of details into a stack of vertical
- * tabs. Another tab pane can be selected by clicking on the respective
- * tab.
- *
- * Each tab may have a summary which can be updated by another
- * script. For that to work, each details element has an associated
- * 'verticalTabCallback' (with jQuery.data() attached to the details),
- * which is called every time the user performs an update to a form
- * element inside the tab pane.
- */
-Drupal.behaviors.verticalTabs = {
-  attach: function (context) {
-
-    if (!Drupal.checkWidthBreakpoint()) {
-      return;
-    }
+  "use strict";
 
-    $(context).find('[data-vertical-tabs-panes]').once('vertical-tabs', function () {
-      var $this = $(this).addClass('vertical-tabs-panes');
-      var focusID = $this.find(':hidden.vertical-tabs-active-tab').val();
-      var tab_focus;
+  /**
+   * This script transforms a set of details into a stack of vertical
+   * tabs. Another tab pane can be selected by clicking on the respective
+   * tab.
+   *
+   * Each tab may have a summary which can be updated by another
+   * script. For that to work, each details element has an associated
+   * 'verticalTabCallback' (with jQuery.data() attached to the details),
+   * which is called every time the user performs an update to a form
+   * element inside the tab pane.
+   */
+  Drupal.behaviors.verticalTabs = {
+    attach: function (context) {
 
-      // Check if there are some details that can be converted to vertical-tabs
-      var $details = $this.find('> details');
-      if ($details.length === 0) {
+      if (!Drupal.checkWidthBreakpoint()) {
         return;
       }
 
-      // Create the tab column.
-      var tab_list = $('<ul class="vertical-tabs-list"></ul>');
-      $this.wrap('<div class="vertical-tabs clearfix"></div>').before(tab_list);
+      $(context).find('[data-vertical-tabs-panes]').once('vertical-tabs', function () {
+        var $this = $(this).addClass('vertical-tabs-panes');
+        var focusID = $this.find(':hidden.vertical-tabs-active-tab').val();
+        var tab_focus;
 
-      // Transform each details into a tab.
-      $details.each(function () {
-        var $this = $(this);
-        var vertical_tab = new Drupal.verticalTab({
-          title: $this.find('> summary').text(),
-          details: $this
-        });
-        tab_list.append(vertical_tab.item);
-        $this
-          .removeClass('collapsed')
-          // prop() can't be used on browsers not supporting details element,
-          // the style won't apply to them if prop() is used.
-          .attr('open', true)
-          .addClass('vertical-tabs-pane')
-          .data('verticalTab', vertical_tab);
-        if (this.id === focusID) {
-          tab_focus = $this;
+        // Check if there are some details that can be converted to vertical-tabs
+        var $details = $this.find('> details');
+        if ($details.length === 0) {
+          return;
         }
-      });
 
-      $(tab_list).find('> li:first').addClass('first');
-      $(tab_list).find('> li:last').addClass('last');
+        // Create the tab column.
+        var tab_list = $('<ul class="vertical-tabs-list"></ul>');
+        $this.wrap('<div class="vertical-tabs clearfix"></div>').before(tab_list);
+
+        // Transform each details into a tab.
+        $details.each(function () {
+          var $this = $(this);
+          var vertical_tab = new Drupal.verticalTab({
+            title: $this.find('> summary').text(),
+            details: $this
+          });
+          tab_list.append(vertical_tab.item);
+          $this
+            .removeClass('collapsed')
+            // prop() can't be used on browsers not supporting details element,
+            // the style won't apply to them if prop() is used.
+            .attr('open', true)
+            .addClass('vertical-tabs-pane')
+            .data('verticalTab', vertical_tab);
+          if (this.id === focusID) {
+            tab_focus = $this;
+          }
+        });
 
-      if (!tab_focus) {
-        // If the current URL has a fragment and one of the tabs contains an
-        // element that matches the URL fragment, activate that tab.
-        var $locationHash = $this.find(window.location.hash);
-        if (window.location.hash && $locationHash.length) {
-          tab_focus = $locationHash.closest('.vertical-tabs-pane');
+        $(tab_list).find('> li:first').addClass('first');
+        $(tab_list).find('> li:last').addClass('last');
+
+        if (!tab_focus) {
+          // If the current URL has a fragment and one of the tabs contains an
+          // element that matches the URL fragment, activate that tab.
+          var $locationHash = $this.find(window.location.hash);
+          if (window.location.hash && $locationHash.length) {
+            tab_focus = $locationHash.closest('.vertical-tabs-pane');
+          }
+          else {
+            tab_focus = $this.find('> .vertical-tabs-pane:first');
+          }
         }
-        else {
-          tab_focus = $this.find('> .vertical-tabs-pane:first');
+        if (tab_focus.length) {
+          tab_focus.data('verticalTab').focus();
         }
-      }
-      if (tab_focus.length) {
-        tab_focus.data('verticalTab').focus();
-      }
-    });
-  }
-};
-
-/**
- * The vertical tab object represents a single tab within a tab group.
- *
- * @param settings
- *   An object with the following keys:
- *   - title: The name of the tab.
- *   - details: The jQuery object of the details element that is the tab pane.
- */
-Drupal.verticalTab = function (settings) {
-  var self = this;
-  $.extend(this, settings, Drupal.theme('verticalTab', settings));
-
-  this.link.attr('href', '#' + settings.details.attr('id'));
-
-  this.link.on('click', function (e) {
-    e.preventDefault();
-    self.focus();
-  });
-
-  // Keyboard events added:
-  // Pressing the Enter key will open the tab pane.
-  this.link.on('keydown', function (event) {
-    event.preventDefault();
-    if (event.keyCode === 13) {
-      self.focus();
-      // Set focus on the first input field of the visible details/tab pane.
-      $(".vertical-tabs-pane :input:visible:enabled:first").trigger('focus');
+      });
     }
-  });
-
-  this.details
-    .on('summaryUpdated', function () {
-      self.updateSummary();
-    })
-    .trigger('summaryUpdated');
-};
+  };
 
-Drupal.verticalTab.prototype = {
   /**
-   * Displays the tab's content pane.
+   * The vertical tab object represents a single tab within a tab group.
+   *
+   * @param settings
+   *   An object with the following keys:
+   *   - title: The name of the tab.
+   *   - details: The jQuery object of the details element that is the tab pane.
    */
-  focus: function () {
+  Drupal.verticalTab = function (settings) {
+    var self = this;
+    $.extend(this, settings, Drupal.theme('verticalTab', settings));
+
+    this.link.attr('href', '#' + settings.details.attr('id'));
+
+    this.link.on('click', function (e) {
+      e.preventDefault();
+      self.focus();
+    });
+
+    // Keyboard events added:
+    // Pressing the Enter key will open the tab pane.
+    this.link.on('keydown', function (event) {
+      event.preventDefault();
+      if (event.keyCode === 13) {
+        self.focus();
+        // Set focus on the first input field of the visible details/tab pane.
+        $(".vertical-tabs-pane :input:visible:enabled:first").trigger('focus');
+      }
+    });
+
     this.details
-      .siblings('.vertical-tabs-pane')
+      .on('summaryUpdated', function () {
+        self.updateSummary();
+      })
+      .trigger('summaryUpdated');
+  };
+
+  Drupal.verticalTab.prototype = {
+    /**
+     * Displays the tab's content pane.
+     */
+    focus: function () {
+      this.details
+        .siblings('.vertical-tabs-pane')
         .each(function () {
           var tab = $(this).data('verticalTab');
           tab.details.hide();
           tab.item.removeClass('selected');
         })
         .end()
-      .show()
-      .siblings(':hidden.vertical-tabs-active-tab')
+        .show()
+        .siblings(':hidden.vertical-tabs-active-tab')
         .val(this.details.attr('id'));
-    this.item.addClass('selected');
-    // Mark the active tab for screen readers.
-    $('#active-vertical-tab').remove();
-    this.link.append('<span id="active-vertical-tab" class="visually-hidden">' + Drupal.t('(active tab)') + '</span>');
-  },
-
-  /**
-   * Updates the tab's summary.
-   */
-  updateSummary: function () {
-    this.summary.html(this.details.drupalGetSummary());
-  },
-
-  /**
-   * Shows a vertical tab pane.
-   */
-  tabShow: function () {
-    // Display the tab.
-    this.item.show();
-    // Show the vertical tabs.
-    this.item.closest('.form-type-vertical-tabs').show();
-    // Update .first marker for items. We need recurse from parent to retain the
-    // actual DOM element order as jQuery implements sortOrder, but not as public
-    // method.
-    this.item.parent().children('.vertical-tab-button').removeClass('first')
-      .filter(':visible:first').addClass('first');
-    // Display the details element.
-    this.details.removeClass('vertical-tab-hidden').show();
-    // Focus this tab.
-    this.focus();
-    return this;
-  },
+      this.item.addClass('selected');
+      // Mark the active tab for screen readers.
+      $('#active-vertical-tab').remove();
+      this.link.append('<span id="active-vertical-tab" class="visually-hidden">' + Drupal.t('(active tab)') + '</span>');
+    },
+
+    /**
+     * Updates the tab's summary.
+     */
+    updateSummary: function () {
+      this.summary.html(this.details.drupalGetSummary());
+    },
+
+    /**
+     * Shows a vertical tab pane.
+     */
+    tabShow: function () {
+      // Display the tab.
+      this.item.show();
+      // Show the vertical tabs.
+      this.item.closest('.form-type-vertical-tabs').show();
+      // Update .first marker for items. We need recurse from parent to retain the
+      // actual DOM element order as jQuery implements sortOrder, but not as public
+      // method.
+      this.item.parent().children('.vertical-tab-button').removeClass('first')
+        .filter(':visible:first').addClass('first');
+      // Display the details element.
+      this.details.removeClass('vertical-tab-hidden').show();
+      // Focus this tab.
+      this.focus();
+      return this;
+    },
+
+    /**
+     * Hides a vertical tab pane.
+     */
+    tabHide: function () {
+      // Hide this tab.
+      this.item.hide();
+      // Update .first marker for items. We need recurse from parent to retain the
+      // actual DOM element order as jQuery implements sortOrder, but not as public
+      // method.
+      this.item.parent().children('.vertical-tab-button').removeClass('first')
+        .filter(':visible:first').addClass('first');
+      // Hide the details element.
+      this.details.addClass('vertical-tab-hidden').hide();
+      // Focus the first visible tab (if there is one).
+      var $firstTab = this.details.siblings('.vertical-tabs-pane:not(.vertical-tab-hidden):first');
+      if ($firstTab.length) {
+        $firstTab.data('verticalTab').focus();
+      }
+      // Hide the vertical tabs (if no tabs remain).
+      else {
+        this.item.closest('.form-type-vertical-tabs').hide();
+      }
+      return this;
+    }
+  };
 
   /**
-   * Hides a vertical tab pane.
+   * Theme function for a vertical tab.
+   *
+   * @param settings
+   *   An object with the following keys:
+   *   - title: The name of the tab.
+   * @return
+   *   This function has to return an object with at least these keys:
+   *   - item: The root tab jQuery element
+   *   - link: The anchor tag that acts as the clickable area of the tab
+   *       (jQuery version)
+   *   - summary: The jQuery element that contains the tab summary
    */
-  tabHide: function () {
-    // Hide this tab.
-    this.item.hide();
-    // Update .first marker for items. We need recurse from parent to retain the
-    // actual DOM element order as jQuery implements sortOrder, but not as public
-    // method.
-    this.item.parent().children('.vertical-tab-button').removeClass('first')
-      .filter(':visible:first').addClass('first');
-    // Hide the details element.
-    this.details.addClass('vertical-tab-hidden').hide();
-    // Focus the first visible tab (if there is one).
-    var $firstTab = this.details.siblings('.vertical-tabs-pane:not(.vertical-tab-hidden):first');
-    if ($firstTab.length) {
-      $firstTab.data('verticalTab').focus();
-    }
-    // Hide the vertical tabs (if no tabs remain).
-    else {
-      this.item.closest('.form-type-vertical-tabs').hide();
-    }
-    return this;
-  }
-};
-
-/**
- * Theme function for a vertical tab.
- *
- * @param settings
- *   An object with the following keys:
- *   - title: The name of the tab.
- * @return
- *   This function has to return an object with at least these keys:
- *   - item: The root tab jQuery element
- *   - link: The anchor tag that acts as the clickable area of the tab
- *       (jQuery version)
- *   - summary: The jQuery element that contains the tab summary
- */
-Drupal.theme.verticalTab = function (settings) {
-  var tab = {};
-  tab.item = $('<li class="vertical-tab-button" tabindex="-1"></li>')
-    .append(tab.link = $('<a href="#"></a>')
-      .append(tab.title = $('<strong></strong>').text(settings.title))
-      .append(tab.summary = $('<span class="summary"></span>')
-    )
-  );
-  return tab;
-};
+  Drupal.theme.verticalTab = function (settings) {
+    var tab = {};
+    tab.item = $('<li class="vertical-tab-button" tabindex="-1"></li>')
+      .append(tab.link = $('<a href="#"></a>')
+        .append(tab.title = $('<strong></strong>').text(settings.title))
+        .append(tab.summary = $('<span class="summary"></span>')
+        )
+      );
+    return tab;
+  };
 
 })(jQuery);
diff --git a/core/modules/block/block.js b/core/modules/block/block.js
index 4ddcf4a..302c315 100644
--- a/core/modules/block/block.js
+++ b/core/modules/block/block.js
@@ -1,134 +1,134 @@
 (function ($, window) {
 
-"use strict";
+  "use strict";
 
-/**
- * Provide the summary information for the block settings vertical tabs.
- */
-Drupal.behaviors.blockSettingsSummary = {
-  attach: function () {
-    // The drupalSetSummary method required for this behavior is not available
-    // on the Blocks administration page, so we need to make sure this
-    // behavior is processed only if drupalSetSummary is defined.
-    if (typeof jQuery.fn.drupalSetSummary === 'undefined') {
-      return;
-    }
-
-    function checkboxesSummary (context) {
-      var vals = [];
-      var $checkboxes = $(context).find('input[type="checkbox"]:checked + label');
-      for (var i = 0, il = $checkboxes.length; i < il; i += 1) {
-        vals.push($($checkboxes[i]).text());
+  /**
+   * Provide the summary information for the block settings vertical tabs.
+   */
+  Drupal.behaviors.blockSettingsSummary = {
+    attach: function () {
+      // The drupalSetSummary method required for this behavior is not available
+      // on the Blocks administration page, so we need to make sure this
+      // behavior is processed only if drupalSetSummary is defined.
+      if (typeof jQuery.fn.drupalSetSummary === 'undefined') {
+        return;
       }
-      if (!vals.length) {
-        vals.push(Drupal.t('Not restricted'));
-      }
-      return vals.join(', ');
-    }
 
-    $('#edit-visibility-node-type, #edit-visibility-language, #edit-visibility-role').drupalSetSummary(checkboxesSummary);
-
-    $('#edit-visibility-path').drupalSetSummary(function (context) {
-      var $pages = $(context).find('textarea[name="visibility[path][pages]"]');
-      if (!$pages.val()) {
-        return Drupal.t('Not restricted');
-      }
-      else {
-        return Drupal.t('Restricted to certain pages');
+      function checkboxesSummary(context) {
+        var vals = [];
+        var $checkboxes = $(context).find('input[type="checkbox"]:checked + label');
+        for (var i = 0, il = $checkboxes.length; i < il; i += 1) {
+          vals.push($($checkboxes[i]).text());
+        }
+        if (!vals.length) {
+          vals.push(Drupal.t('Not restricted'));
+        }
+        return vals.join(', ');
       }
-    });
-  }
-};
 
-/**
- * Move a block in the blocks table from one region to another via select list.
- *
- * This behavior is dependent on the tableDrag behavior, since it uses the
- * objects initialized in that behavior to update the row.
- */
-Drupal.behaviors.blockDrag = {
-  attach: function (context, settings) {
-    // tableDrag is required and we should be on the blocks admin page.
-    if (typeof Drupal.tableDrag === 'undefined' || typeof Drupal.tableDrag.blocks === 'undefined') {
-      return;
+      $('#edit-visibility-node-type, #edit-visibility-language, #edit-visibility-role').drupalSetSummary(checkboxesSummary);
+
+      $('#edit-visibility-path').drupalSetSummary(function (context) {
+        var $pages = $(context).find('textarea[name="visibility[path][pages]"]');
+        if (!$pages.val()) {
+          return Drupal.t('Not restricted');
+        }
+        else {
+          return Drupal.t('Restricted to certain pages');
+        }
+      });
     }
+  };
 
-    var table = $('#blocks');
-    var tableDrag = Drupal.tableDrag.blocks; // Get the blocks tableDrag object.
+  /**
+   * Move a block in the blocks table from one region to another via select list.
+   *
+   * This behavior is dependent on the tableDrag behavior, since it uses the
+   * objects initialized in that behavior to update the row.
+   */
+  Drupal.behaviors.blockDrag = {
+    attach: function (context, settings) {
+      // tableDrag is required and we should be on the blocks admin page.
+      if (typeof Drupal.tableDrag === 'undefined' || typeof Drupal.tableDrag.blocks === 'undefined') {
+        return;
+      }
 
-    // Add a handler for when a row is swapped, update empty regions.
-    tableDrag.row.prototype.onSwap = function (swappedRow) {
-      checkEmptyRegions(table, this);
-    };
+      var table = $('#blocks');
+      var tableDrag = Drupal.tableDrag.blocks; // Get the blocks tableDrag object.
 
-    // Add a handler so when a row is dropped, update fields dropped into new regions.
-    tableDrag.onDrop = function () {
-      var dragObject = this;
-      var $rowElement = $(dragObject.rowObject.element);
-      // Use "region-message" row instead of "region" row because
-      // "region-{region_name}-message" is less prone to regexp match errors.
-      var regionRow = $rowElement.prevAll('tr.region-message').get(0);
-      var regionName = regionRow.className.replace(/([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
-      var regionField = $rowElement.find('select.block-region-select');
-      // Check whether the newly picked region is available for this block.
-      if (regionField.find('option[value=' + regionName + ']').length === 0) {
-        // If not, alert the user and keep the block in its old region setting.
-        window.alert(Drupal.t('The block cannot be placed in this region.'));
-        // Simulate that there was a selected element change, so the row is put
-        // back to from where the user tried to drag it.
-        regionField.trigger('change');
-      }
-      else if ($rowElement.prev('tr').is('.region-message')) {
-        var weightField = $rowElement.find('select.block-weight');
-        var oldRegionName = weightField[0].className.replace(/([^ ]+[ ]+)*block-weight-([^ ]+)([ ]+[^ ]+)*/, '$2');
+      // Add a handler for when a row is swapped, update empty regions.
+      tableDrag.row.prototype.onSwap = function (swappedRow) {
+        checkEmptyRegions(table, this);
+      };
 
-        if (!regionField.is('.block-region-' + regionName)) {
-          regionField.removeClass('block-region-' + oldRegionName).addClass('block-region-' + regionName);
-          weightField.removeClass('block-weight-' + oldRegionName).addClass('block-weight-' + regionName);
-          regionField.val(regionName);
+      // Add a handler so when a row is dropped, update fields dropped into new regions.
+      tableDrag.onDrop = function () {
+        var dragObject = this;
+        var $rowElement = $(dragObject.rowObject.element);
+        // Use "region-message" row instead of "region" row because
+        // "region-{region_name}-message" is less prone to regexp match errors.
+        var regionRow = $rowElement.prevAll('tr.region-message').get(0);
+        var regionName = regionRow.className.replace(/([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
+        var regionField = $rowElement.find('select.block-region-select');
+        // Check whether the newly picked region is available for this block.
+        if (regionField.find('option[value=' + regionName + ']').length === 0) {
+          // If not, alert the user and keep the block in its old region setting.
+          window.alert(Drupal.t('The block cannot be placed in this region.'));
+          // Simulate that there was a selected element change, so the row is put
+          // back to from where the user tried to drag it.
+          regionField.trigger('change');
         }
-      }
-    };
+        else if ($rowElement.prev('tr').is('.region-message')) {
+          var weightField = $rowElement.find('select.block-weight');
+          var oldRegionName = weightField[0].className.replace(/([^ ]+[ ]+)*block-weight-([^ ]+)([ ]+[^ ]+)*/, '$2');
+
+          if (!regionField.is('.block-region-' + regionName)) {
+            regionField.removeClass('block-region-' + oldRegionName).addClass('block-region-' + regionName);
+            weightField.removeClass('block-weight-' + oldRegionName).addClass('block-weight-' + regionName);
+            regionField.val(regionName);
+          }
+        }
+      };
 
-    // Add the behavior to each region select list.
-    $(context).find('select.block-region-select').once('block-region-select', function () {
-      $(this).on('change', function (event) {
-        // Make our new row and select field.
-        var row = $(this).closest('tr');
-        var select = $(this);
-        tableDrag.rowObject = new tableDrag.row(row);
+      // Add the behavior to each region select list.
+      $(context).find('select.block-region-select').once('block-region-select', function () {
+        $(this).on('change', function (event) {
+          // Make our new row and select field.
+          var row = $(this).closest('tr');
+          var select = $(this);
+          tableDrag.rowObject = new tableDrag.row(row);
 
-        // Find the correct region and insert the row as the last in the region.
-        table.find('.region-' + select[0].value + '-message').nextUntil('.region-message').last().before(row);
+          // Find the correct region and insert the row as the last in the region.
+          table.find('.region-' + select[0].value + '-message').nextUntil('.region-message').last().before(row);
 
-        // Modify empty regions with added or removed fields.
-        checkEmptyRegions(table, row);
-        // Remove focus from selectbox.
-        select.trigger('blur');
+          // Modify empty regions with added or removed fields.
+          checkEmptyRegions(table, row);
+          // Remove focus from selectbox.
+          select.trigger('blur');
+        });
       });
-    });
 
-    var checkEmptyRegions = function (table, rowObject) {
-      table.find('tr.region-message').each(function () {
-        var $this = $(this);
-        // If the dragged row is in this region, but above the message row, swap it down one space.
-        if ($this.prev('tr').get(0) === rowObject.element) {
-          // Prevent a recursion problem when using the keyboard to move rows up.
-          if ((rowObject.method !== 'keyboard' || rowObject.direction === 'down')) {
-            rowObject.swap('after', this);
+      var checkEmptyRegions = function (table, rowObject) {
+        table.find('tr.region-message').each(function () {
+          var $this = $(this);
+          // If the dragged row is in this region, but above the message row, swap it down one space.
+          if ($this.prev('tr').get(0) === rowObject.element) {
+            // Prevent a recursion problem when using the keyboard to move rows up.
+            if ((rowObject.method !== 'keyboard' || rowObject.direction === 'down')) {
+              rowObject.swap('after', this);
+            }
           }
-        }
-        // This region has become empty.
-        if ($this.next('tr').is(':not(.draggable)') || $this.next('tr').length === 0) {
-          $this.removeClass('region-populated').addClass('region-empty');
-        }
-        // This region has become populated.
-        else if ($this.is('.region-empty')) {
-          $this.removeClass('region-empty').addClass('region-populated');
-        }
-      });
-    };
-  }
-};
+          // This region has become empty.
+          if ($this.next('tr').is(':not(.draggable)') || $this.next('tr').length === 0) {
+            $this.removeClass('region-populated').addClass('region-empty');
+          }
+          // This region has become populated.
+          else if ($this.is('.region-empty')) {
+            $this.removeClass('region-empty').addClass('region-populated');
+          }
+        });
+      };
+    }
+  };
 
 })(jQuery, window);
diff --git a/core/modules/block/custom_block/custom_block.js b/core/modules/block/custom_block/custom_block.js
index fb0905a..be0ea7d 100644
--- a/core/modules/block/custom_block/custom_block.js
+++ b/core/modules/block/custom_block/custom_block.js
@@ -5,42 +5,42 @@
 
 (function ($) {
 
-"use strict";
+  "use strict";
 
-Drupal.behaviors.customBlockDetailsSummaries = {
-  attach: function (context) {
-    var $context = $(context);
-    $context.find('.custom-block-form-revision-information').drupalSetSummary(function (context) {
+  Drupal.behaviors.customBlockDetailsSummaries = {
+    attach: function (context) {
       var $context = $(context);
-      var revisionCheckbox = $context.find('.form-item-revision input');
-
-      // Return 'New revision' if the 'Create new revision' checkbox is checked,
-      // or if the checkbox doesn't exist, but the revision log does. For users
-      // without the "Administer content" permission the checkbox won't appear,
-      // but the revision log will if the content type is set to auto-revision.
-      if (revisionCheckbox.is(':checked') || (!revisionCheckbox.length && $context.find('.form-item-log textarea').length)) {
-        return Drupal.t('New revision');
-      }
-
-      return Drupal.t('No revision');
-    });
-
-    $context.find('fieldset.custom-block-translation-options').drupalSetSummary(function (context) {
-      var $context = $(context);
-      var translate;
-      var $checkbox = $context.find('.form-item-translation-translate input');
-
-      if ($checkbox.size()) {
-        translate = $checkbox.is(':checked') ? Drupal.t('Needs to be updated') : Drupal.t('Does not need to be updated');
-      }
-      else {
-        $checkbox = $context.find('.form-item-translation-retranslate input');
-        translate = $checkbox.is(':checked') ? Drupal.t('Flag other translations as outdated') : Drupal.t('Do not flag other translations as outdated');
-      }
-
-      return translate;
-    });
-  }
-};
+      $context.find('.custom-block-form-revision-information').drupalSetSummary(function (context) {
+        var $context = $(context);
+        var revisionCheckbox = $context.find('.form-item-revision input');
+
+        // Return 'New revision' if the 'Create new revision' checkbox is checked,
+        // or if the checkbox doesn't exist, but the revision log does. For users
+        // without the "Administer content" permission the checkbox won't appear,
+        // but the revision log will if the content type is set to auto-revision.
+        if (revisionCheckbox.is(':checked') || (!revisionCheckbox.length && $context.find('.form-item-log textarea').length)) {
+          return Drupal.t('New revision');
+        }
+
+        return Drupal.t('No revision');
+      });
+
+      $context.find('fieldset.custom-block-translation-options').drupalSetSummary(function (context) {
+        var $context = $(context);
+        var translate;
+        var $checkbox = $context.find('.form-item-translation-translate input');
+
+        if ($checkbox.size()) {
+          translate = $checkbox.is(':checked') ? Drupal.t('Needs to be updated') : Drupal.t('Does not need to be updated');
+        }
+        else {
+          $checkbox = $context.find('.form-item-translation-retranslate input');
+          translate = $checkbox.is(':checked') ? Drupal.t('Flag other translations as outdated') : Drupal.t('Do not flag other translations as outdated');
+        }
+
+        return translate;
+      });
+    }
+  };
 
 })(jQuery);
diff --git a/core/modules/block/js/block.admin.js b/core/modules/block/js/block.admin.js
index 5c657d4..44a4a74 100644
--- a/core/modules/block/js/block.admin.js
+++ b/core/modules/block/js/block.admin.js
@@ -1,89 +1,89 @@
 (function ($, Drupal) {
 
-"use strict";
+  "use strict";
 
-/**
- * Filters the block list by a text input search string.
- *
- * Text search input: input.block-filter-text
- * Target element:    input.block-filter-text[data-element]
- * Source text:       .block-filter-text-source
- */
-Drupal.behaviors.blockFilterByText = {
-  attach: function (context, settings) {
-    var $input = $('input.block-filter-text').once('block-filter-text');
-    var $element = $($input.attr('data-element'));
-    var $blocks, $details;
-
-    /**
-     * Hides the <details> element for a category if it has no visible blocks.
-     */
-    function hideCategoryDetails(index, element) {
-      var $details = $(element);
-      $details.toggle($details.find('li:visible').length > 0);
-    }
-
-    /**
-     * Filters the block list.
-     */
-    function filterBlockList (e) {
-      var query = $(e.target).val().toLowerCase();
+  /**
+   * Filters the block list by a text input search string.
+   *
+   * Text search input: input.block-filter-text
+   * Target element:    input.block-filter-text[data-element]
+   * Source text:       .block-filter-text-source
+   */
+  Drupal.behaviors.blockFilterByText = {
+    attach: function (context, settings) {
+      var $input = $('input.block-filter-text').once('block-filter-text');
+      var $element = $($input.attr('data-element'));
+      var $blocks, $details;
 
       /**
-       * Shows or hides the block entry based on the query.
+       * Hides the <details> element for a category if it has no visible blocks.
        */
-      function showBlockEntry (index, block) {
-        var $block = $(block);
-        var $sources = $block.find('.block-filter-text-source');
-        var textMatch = $sources.text().toLowerCase().indexOf(query) !== -1;
-        $block.toggle(textMatch);
+      function hideCategoryDetails(index, element) {
+        var $details = $(element);
+        $details.toggle($details.find('li:visible').length > 0);
       }
 
-      // Filter if the length of the query is at least 2 characters.
-      if (query.length >= 2) {
-        $blocks.each(showBlockEntry);
+      /**
+       * Filters the block list.
+       */
+      function filterBlockList(e) {
+        var query = $(e.target).val().toLowerCase();
 
-        // Note that we first open all <details> to be able to use ':visible'.
-        // Mark the <details> elements that were closed before filtering, so
-        // they can be reclosed when filtering is removed.
-        $details.not('[open]').attr('data-drupal-block-state', 'forced-open');
-        // Hide the category <details> if they don't have any visible rows.
-        $details.attr('open', 'open').each(hideCategoryDetails);
-      }
-      else {
-        $blocks.show();
-        $details.show();
-        // Return <details> elements that had been closed before filtering
-        // to a closed state.
-        $details.filter('[data-drupal-block-state="forced-open"]').removeAttr('open data-drupal-block-state');
+        /**
+         * Shows or hides the block entry based on the query.
+         */
+        function showBlockEntry(index, block) {
+          var $block = $(block);
+          var $sources = $block.find('.block-filter-text-source');
+          var textMatch = $sources.text().toLowerCase().indexOf(query) !== -1;
+          $block.toggle(textMatch);
+        }
+
+        // Filter if the length of the query is at least 2 characters.
+        if (query.length >= 2) {
+          $blocks.each(showBlockEntry);
+
+          // Note that we first open all <details> to be able to use ':visible'.
+          // Mark the <details> elements that were closed before filtering, so
+          // they can be reclosed when filtering is removed.
+          $details.not('[open]').attr('data-drupal-block-state', 'forced-open');
+          // Hide the category <details> if they don't have any visible rows.
+          $details.attr('open', 'open').each(hideCategoryDetails);
+        }
+        else {
+          $blocks.show();
+          $details.show();
+          // Return <details> elements that had been closed before filtering
+          // to a closed state.
+          $details.filter('[data-drupal-block-state="forced-open"]').removeAttr('open data-drupal-block-state');
+        }
       }
-    }
 
-    if ($element.length) {
-      $details = $element.find('details');
-      $blocks = $details.find('li');
+      if ($element.length) {
+        $details = $element.find('details');
+        $blocks = $details.find('li');
 
-      $input.on('keyup', filterBlockList);
+        $input.on('keyup', filterBlockList);
+      }
     }
-  }
-};
+  };
 
-/**
- * Highlights the block that was just placed into the block listing.
- */
-Drupal.behaviors.blockHighlightPlacement = {
-  attach: function (context, settings) {
-    if (settings.blockPlacement) {
-      $('#blocks').once('block-highlight', function () {
-        var $container = $(this);
-        // Just scrolling the document.body will not work in Firefox. The html
-        // element is needed as well.
-        $('html, body').animate({
-          scrollTop: $('#block-placed').offset().top - $container.offset().top + $container.scrollTop()
-        }, 500);
-      });
+  /**
+   * Highlights the block that was just placed into the block listing.
+   */
+  Drupal.behaviors.blockHighlightPlacement = {
+    attach: function (context, settings) {
+      if (settings.blockPlacement) {
+        $('#blocks').once('block-highlight', function () {
+          var $container = $(this);
+          // Just scrolling the document.body will not work in Firefox. The html
+          // element is needed as well.
+          $('html, body').animate({
+            scrollTop: $('#block-placed').offset().top - $container.offset().top + $container.scrollTop()
+          }, 500);
+        });
+      }
     }
-  }
-};
+  };
 
 }(jQuery, Drupal));
diff --git a/core/modules/book/book.js b/core/modules/book/book.js
index f2d36ad..df57547 100644
--- a/core/modules/book/book.js
+++ b/core/modules/book/book.js
@@ -5,25 +5,25 @@
 
 (function ($) {
 
-"use strict";
+  "use strict";
 
-Drupal.behaviors.bookDetailsSummaries = {
-  attach: function (context) {
-    $(context).find('.book-outline-form').drupalSetSummary(function (context) {
-      var $select = $(context).find('.book-title-select');
-      var val = $select.val();
+  Drupal.behaviors.bookDetailsSummaries = {
+    attach: function (context) {
+      $(context).find('.book-outline-form').drupalSetSummary(function (context) {
+        var $select = $(context).find('.book-title-select');
+        var val = $select.val();
 
-      if (val === '0') {
-        return Drupal.t('Not in book');
-      }
-      else if (val === 'new') {
-        return Drupal.t('New book');
-      }
-      else {
-        return Drupal.checkPlain($select.find(':selected').text());
-      }
-    });
-  }
-};
+        if (val === '0') {
+          return Drupal.t('Not in book');
+        }
+        else if (val === 'new') {
+          return Drupal.t('New book');
+        }
+        else {
+          return Drupal.checkPlain($select.find(':selected').text());
+        }
+      });
+    }
+  };
 
 })(jQuery);
diff --git a/core/modules/ckeditor/js/ckeditor.admin.js b/core/modules/ckeditor/js/ckeditor.admin.js
index 0e8f5b1..f5163b8 100644
--- a/core/modules/ckeditor/js/ckeditor.admin.js
+++ b/core/modules/ckeditor/js/ckeditor.admin.js
@@ -4,1535 +4,1535 @@
  */
 (function ($, Drupal, _, CKEDITOR) {
 
-"use strict";
-
-Drupal.ckeditor = Drupal.ckeditor || {};
+  "use strict";
+
+  Drupal.ckeditor = Drupal.ckeditor || {};
+
+  Drupal.behaviors.ckeditorAdmin = {
+    attach: function (context) {
+      // Process the CKEditor configuration fragment once.
+      var $configurationForm = $(context).find('.ckeditor-toolbar-configuration');
+      if ($configurationForm.once('ckeditor-configuration').length) {
+        var $textarea = $configurationForm
+          // Hide the textarea that contains the serialized representation of the
+          // CKEditor configuration.
+          .find('.form-item-editor-settings-toolbar-button-groups')
+          .hide()
+          // Return the textarea child node from this expression.
+          .find('textarea');
+
+        // The HTML for the CKEditor configuration is assembled on the server and
+        // and sent to the client as a serialized DOM fragment.
+        $configurationForm.append(drupalSettings.ckeditor.toolbarAdmin);
+
+        // Create a configuration model.
+        var model = Drupal.ckeditor.models.configurationModel = new Drupal.ckeditor.ConfigurationModel({
+          $textarea: $textarea,
+          activeEditorConfig: JSON.parse($textarea.val()),
+          hiddenEditorConfig: drupalSettings.ckeditor.hiddenCKEditorConfig
+        });
 
-Drupal.behaviors.ckeditorAdmin = {
-  attach: function (context) {
-    // Process the CKEditor configuration fragment once.
-    var $configurationForm = $(context).find('.ckeditor-toolbar-configuration');
-    if ($configurationForm.once('ckeditor-configuration').length) {
-      var $textarea = $configurationForm
-        // Hide the textarea that contains the serialized representation of the
-        // CKEditor configuration.
-        .find('.form-item-editor-settings-toolbar-button-groups')
-        .hide()
-        // Return the textarea child node from this expression.
-        .find('textarea');
-
-      // The HTML for the CKEditor configuration is assembled on the server and
-      // and sent to the client as a serialized DOM fragment.
-      $configurationForm.append(drupalSettings.ckeditor.toolbarAdmin);
-
-      // Create a configuration model.
-      var model = Drupal.ckeditor.models.configurationModel = new Drupal.ckeditor.ConfigurationModel({
-        $textarea: $textarea,
-        activeEditorConfig: JSON.parse($textarea.val()),
-        hiddenEditorConfig: drupalSettings.ckeditor.hiddenCKEditorConfig
-      });
-
-      // Create the configuration Views.
-      var viewDefaults = {
-        model: model,
-        el: $('.ckeditor-toolbar-configuration')
-      };
-      Drupal.ckeditor.views = {
-        controller: new Drupal.ckeditor.ConfigurationController(viewDefaults),
-        visualView: new Drupal.ckeditor.ConfigurationVisualView(viewDefaults),
-        keyboardView: new Drupal.ckeditor.ConfigurationKeyboardView(viewDefaults),
-        auralView: new Drupal.ckeditor.ConfigurationAuralView(viewDefaults)
-      };
-    }
-  },
-  detach: function (context, settings, trigger) {
-    // Early-return if the trigger for detachment is something else than unload.
-    if (trigger !== 'unload') {
-      return;
-    }
+        // Create the configuration Views.
+        var viewDefaults = {
+          model: model,
+          el: $('.ckeditor-toolbar-configuration')
+        };
+        Drupal.ckeditor.views = {
+          controller: new Drupal.ckeditor.ConfigurationController(viewDefaults),
+          visualView: new Drupal.ckeditor.ConfigurationVisualView(viewDefaults),
+          keyboardView: new Drupal.ckeditor.ConfigurationKeyboardView(viewDefaults),
+          auralView: new Drupal.ckeditor.ConfigurationAuralView(viewDefaults)
+        };
+      }
+    },
+    detach: function (context, settings, trigger) {
+      // Early-return if the trigger for detachment is something else than unload.
+      if (trigger !== 'unload') {
+        return;
+      }
 
-    // We're detaching because CKEditor as text editor has been disabled; this
-    // really means that all CKEditor toolbar buttons have been removed. Hence,
-    // all editor features will be removed, so any reactions from filters will
-    // be undone.
-    var $configurationForm = $(context).find('.ckeditor-toolbar-configuration.ckeditor-configuration-processed');
-    if ($configurationForm.length && Drupal.ckeditor.models && Drupal.ckeditor.models.configurationModel) {
-      var config = Drupal.ckeditor.models.configurationModel.toJSON().activeEditorConfig;
-      var buttons = Drupal.ckeditor.views.controller.getButtonList(config);
-      var $activeToolbar = $('.ckeditor-toolbar-configuration').find('.ckeditor-toolbar-active');
-      for (var i = 0; i < buttons.length; i++) {
-        $activeToolbar.trigger('CKEditorToolbarChanged', ['removed', buttons[i]]);
+      // We're detaching because CKEditor as text editor has been disabled; this
+      // really means that all CKEditor toolbar buttons have been removed. Hence,
+      // all editor features will be removed, so any reactions from filters will
+      // be undone.
+      var $configurationForm = $(context).find('.ckeditor-toolbar-configuration.ckeditor-configuration-processed');
+      if ($configurationForm.length && Drupal.ckeditor.models && Drupal.ckeditor.models.configurationModel) {
+        var config = Drupal.ckeditor.models.configurationModel.toJSON().activeEditorConfig;
+        var buttons = Drupal.ckeditor.views.controller.getButtonList(config);
+        var $activeToolbar = $('.ckeditor-toolbar-configuration').find('.ckeditor-toolbar-active');
+        for (var i = 0; i < buttons.length; i++) {
+          $activeToolbar.trigger('CKEditorToolbarChanged', ['removed', buttons[i]]);
+        }
       }
     }
-  }
-};
-
-/**
- * CKEditor configuration UI methods of Backbone objects.
- */
-Drupal.ckeditor = {
-
-  // A hash of View instances.
-  views: {},
-
-  // A hash of Model instances.
-  models: {},
+  };
 
   /**
-   * Backbone model for the CKEditor toolbar configuration state.
+   * CKEditor configuration UI methods of Backbone objects.
    */
-  ConfigurationModel: Backbone.Model.extend({
-    defaults: {
-      // The CKEditor configuration that is being manipulated through the UI.
-      activeEditorConfig: null,
-      // The textarea that contains the serialized representation of the active
-      // CKEditor configuration.
-      $textarea: null,
-      // Tracks whether the active toolbar DOM structure has been changed. When
-      // true, activeEditorConfig needs to be updated, and when that is updated,
-      // $textarea will also be updated.
-      isDirty: false,
-      // The configuration for the hidden CKEditor instance that is used to build
-      // the features metadata.
-      hiddenEditorConfig: null,
-      // A hash, keyed by a feature name, that details CKEditor plugin features.
-      featuresMetadata: null,
-      // Whether the button group names are currently visible.
-      groupNamesVisible: false
-    },
-    sync: function () {
-      // Push the settings into the textarea.
-      this.get('$textarea').val(JSON.stringify(this.get('activeEditorConfig')));
-    }
-  }),
+  Drupal.ckeditor = {
 
-  /**
-   * Backbone View acting as a controller for CKEditor toolbar configuration.
-   */
-  ConfigurationController: Backbone.View.extend({
+    // A hash of View instances.
+    views: {},
 
-    events: {},
+    // A hash of Model instances.
+    models: {},
 
     /**
-     * {@inheritdoc}
+     * Backbone model for the CKEditor toolbar configuration state.
      */
-    initialize: function () {
-      this.getCKEditorFeatures(this.model.get('hiddenEditorConfig'), this.disableFeaturesDisallowedByFilters.bind(this));
-
-      // Push the active editor configuration to the textarea.
-      this.model.listenTo(this.model, 'change:activeEditorConfig', this.model.sync);
-      this.listenTo(this.model, 'change:isDirty', this.parseEditorDOM);
-    },
+    ConfigurationModel: Backbone.Model.extend({
+      defaults: {
+        // The CKEditor configuration that is being manipulated through the UI.
+        activeEditorConfig: null,
+        // The textarea that contains the serialized representation of the active
+        // CKEditor configuration.
+        $textarea: null,
+        // Tracks whether the active toolbar DOM structure has been changed. When
+        // true, activeEditorConfig needs to be updated, and when that is updated,
+        // $textarea will also be updated.
+        isDirty: false,
+        // The configuration for the hidden CKEditor instance that is used to build
+        // the features metadata.
+        hiddenEditorConfig: null,
+        // A hash, keyed by a feature name, that details CKEditor plugin features.
+        featuresMetadata: null,
+        // Whether the button group names are currently visible.
+        groupNamesVisible: false
+      },
+      sync: function () {
+        // Push the settings into the textarea.
+        this.get('$textarea').val(JSON.stringify(this.get('activeEditorConfig')));
+      }
+    }),
 
     /**
-     * Converts the active toolbar DOM structure to an object representation.
-     *
-     * @param Drupal.ckeditor.ConfigurationModel model
-     *   The state model for the CKEditor configuration.
-     * @param Boolean isDirty
-     *   Tracks whether the active toolbar DOM structure has been changed.
-     *   isDirty is toggled back to false in this method.
-     * @param Object options
-     *   An object that includes:
-     *   - Boolean broadcast: (optional) A flag that controls whether a
-     *     CKEditorToolbarChanged event should be fired for configuration
-     *     changes.
+     * Backbone View acting as a controller for CKEditor toolbar configuration.
      */
-    parseEditorDOM: function (model, isDirty, options) {
-      if (isDirty) {
-        var currentConfig = this.model.get('activeEditorConfig');
-
-        // Process the rows.
-        var rows = [];
-        this.$el
-          .find('.ckeditor-active-toolbar-configuration')
-          .children('.ckeditor-row').each(function () {
-            var groups = [];
-            // Process the button groups.
-            $(this).find('.ckeditor-toolbar-group').each(function () {
-              var $group = $(this);
-              var $buttons = $group.find('.ckeditor-button');
-              if ($buttons.length) {
-                var group = {
-                  name: $group.attr('data-drupal-ckeditor-toolbar-group-name'),
-                  items: []
-                };
-                $group.find('.ckeditor-button, .ckeditor-multiple-button').each(function () {
-                  group.items.push($(this).attr('data-drupal-ckeditor-button-name'));
-                });
-                groups.push(group);
+    ConfigurationController: Backbone.View.extend({
+
+      events: {},
+
+      /**
+       * {@inheritdoc}
+       */
+      initialize: function () {
+        this.getCKEditorFeatures(this.model.get('hiddenEditorConfig'), this.disableFeaturesDisallowedByFilters.bind(this));
+
+        // Push the active editor configuration to the textarea.
+        this.model.listenTo(this.model, 'change:activeEditorConfig', this.model.sync);
+        this.listenTo(this.model, 'change:isDirty', this.parseEditorDOM);
+      },
+
+      /**
+       * Converts the active toolbar DOM structure to an object representation.
+       *
+       * @param Drupal.ckeditor.ConfigurationModel model
+       *   The state model for the CKEditor configuration.
+       * @param Boolean isDirty
+       *   Tracks whether the active toolbar DOM structure has been changed.
+       *   isDirty is toggled back to false in this method.
+       * @param Object options
+       *   An object that includes:
+       *   - Boolean broadcast: (optional) A flag that controls whether a
+       *     CKEditorToolbarChanged event should be fired for configuration
+       *     changes.
+       */
+      parseEditorDOM: function (model, isDirty, options) {
+        if (isDirty) {
+          var currentConfig = this.model.get('activeEditorConfig');
+
+          // Process the rows.
+          var rows = [];
+          this.$el
+            .find('.ckeditor-active-toolbar-configuration')
+            .children('.ckeditor-row').each(function () {
+              var groups = [];
+              // Process the button groups.
+              $(this).find('.ckeditor-toolbar-group').each(function () {
+                var $group = $(this);
+                var $buttons = $group.find('.ckeditor-button');
+                if ($buttons.length) {
+                  var group = {
+                    name: $group.attr('data-drupal-ckeditor-toolbar-group-name'),
+                    items: []
+                  };
+                  $group.find('.ckeditor-button, .ckeditor-multiple-button').each(function () {
+                    group.items.push($(this).attr('data-drupal-ckeditor-button-name'));
+                  });
+                  groups.push(group);
+                }
+              });
+              if (groups.length) {
+                rows.push(groups);
               }
             });
-            if (groups.length) {
-              rows.push(groups);
+          this.model.set('activeEditorConfig', rows);
+          // Mark the model as clean. Whether or not the sync to the textfield
+          // occurs depends on the activeEditorConfig attribute firing a change
+          // event. The DOM has at least been processed and posted, so as far as
+          // the model is concerned, it is clean.
+          this.model.set('isDirty', false);
+
+          // Determine whether we should trigger an event.
+          if (options.broadcast !== false) {
+            var prev = this.getButtonList(currentConfig);
+            var next = this.getButtonList(rows);
+            if (prev.length !== next.length) {
+              this.$el
+                .find('.ckeditor-toolbar-active')
+                .trigger('CKEditorToolbarChanged', [
+                  (prev.length < next.length) ? 'added' : 'removed',
+                  _.difference(_.union(prev, next), _.intersection(prev, next))[0]
+                ]);
             }
-          });
-        this.model.set('activeEditorConfig', rows);
-        // Mark the model as clean. Whether or not the sync to the textfield
-        // occurs depends on the activeEditorConfig attribute firing a change
-        // event. The DOM has at least been processed and posted, so as far as
-        // the model is concerned, it is clean.
-        this.model.set('isDirty', false);
-
-        // Determine whether we should trigger an event.
-        if (options.broadcast !== false) {
-          var prev = this.getButtonList(currentConfig);
-          var next = this.getButtonList(rows);
-          if (prev.length !== next.length) {
-            this.$el
-            .find('.ckeditor-toolbar-active')
-            .trigger('CKEditorToolbarChanged', [
-              (prev.length < next.length) ? 'added' : 'removed',
-              _.difference(_.union(prev, next), _.intersection(prev, next))[0]
-            ]);
           }
         }
-      }
-    },
-
-    /**
-     * Asynchronously retrieve the metadata for all available CKEditor features.
-     *
-     * In order to get a list of all features needed by CKEditor, we create a
-     * hidden CKEditor instance, then check the CKEditor's "allowedContent"
-     * filter settings. Because creating an instance is expensive, a callback
-     * must be provided that will receive a hash of Drupal.EditorFeature
-     * features keyed by feature (button) name.
-     *
-     * @param Object CKEditorConfig
-     *   An object that represents the configuration settings for a CKEditor
-     *   editor component.
-     * @param Function callback
-     *   A function to invoke when the instanceReady event is fired by the
-     *   CKEditor object.
-     */
-    getCKEditorFeatures: function (CKEditorConfig, callback) {
-      var getProperties = function (CKEPropertiesList) {
-        return (_.isObject(CKEPropertiesList)) ? _.keys(CKEPropertiesList) : [];
-      };
-
-      var convertCKERulesToEditorFeature = function (feature, CKEFeatureRules) {
-        for (var i = 0; i < CKEFeatureRules.length; i++) {
-          var CKERule = CKEFeatureRules[i];
-          var rule = new Drupal.EditorFeatureHTMLRule();
-
-          // Tags.
-          var tags = getProperties(CKERule.elements);
-          rule.required.tags = (CKERule.propertiesOnly) ? [] : tags;
-          rule.allowed.tags = tags;
-          // Attributes.
-          rule.required.attributes = getProperties(CKERule.requiredAttributes);
-          rule.allowed.attributes = getProperties(CKERule.attributes);
-          // Styles.
-          rule.required.styles = getProperties(CKERule.requiredStyles);
-          rule.allowed.styles = getProperties(CKERule.styles);
-          // Classes.
-          rule.required.classes = getProperties(CKERule.requiredClasses);
-          rule.allowed.classes = getProperties(CKERule.classes);
-          // Raw.
-          rule.raw = CKERule;
-
-          feature.addHTMLRule(rule);
-        }
-      };
+      },
 
-      // Create hidden CKEditor with all features enabled, retrieve metadata.
-      // @see \Drupal\ckeditor\Plugin\Editor\CKEditor::settingsForm.
-      var hiddenCKEditorID = 'ckeditor-hidden';
-      if (CKEDITOR.instances[hiddenCKEditorID]) {
-        CKEDITOR.instances[hiddenCKEditorID].destroy(true);
-      }
-      // Load external plugins, if any.
-      var hiddenEditorConfig = this.model.get('hiddenEditorConfig');
-      if (hiddenEditorConfig.drupalExternalPlugins) {
-        var externalPlugins = hiddenEditorConfig.drupalExternalPlugins;
-        for (var pluginName in externalPlugins) {
-          if (externalPlugins.hasOwnProperty(pluginName)) {
-            CKEDITOR.plugins.addExternal(pluginName, externalPlugins[pluginName], '');
+      /**
+       * Asynchronously retrieve the metadata for all available CKEditor features.
+       *
+       * In order to get a list of all features needed by CKEditor, we create a
+       * hidden CKEditor instance, then check the CKEditor's "allowedContent"
+       * filter settings. Because creating an instance is expensive, a callback
+       * must be provided that will receive a hash of Drupal.EditorFeature
+       * features keyed by feature (button) name.
+       *
+       * @param Object CKEditorConfig
+       *   An object that represents the configuration settings for a CKEditor
+       *   editor component.
+       * @param Function callback
+       *   A function to invoke when the instanceReady event is fired by the
+       *   CKEditor object.
+       */
+      getCKEditorFeatures: function (CKEditorConfig, callback) {
+        var getProperties = function (CKEPropertiesList) {
+          return (_.isObject(CKEPropertiesList)) ? _.keys(CKEPropertiesList) : [];
+        };
+
+        var convertCKERulesToEditorFeature = function (feature, CKEFeatureRules) {
+          for (var i = 0; i < CKEFeatureRules.length; i++) {
+            var CKERule = CKEFeatureRules[i];
+            var rule = new Drupal.EditorFeatureHTMLRule();
+
+            // Tags.
+            var tags = getProperties(CKERule.elements);
+            rule.required.tags = (CKERule.propertiesOnly) ? [] : tags;
+            rule.allowed.tags = tags;
+            // Attributes.
+            rule.required.attributes = getProperties(CKERule.requiredAttributes);
+            rule.allowed.attributes = getProperties(CKERule.attributes);
+            // Styles.
+            rule.required.styles = getProperties(CKERule.requiredStyles);
+            rule.allowed.styles = getProperties(CKERule.styles);
+            // Classes.
+            rule.required.classes = getProperties(CKERule.requiredClasses);
+            rule.allowed.classes = getProperties(CKERule.classes);
+            // Raw.
+            rule.raw = CKERule;
+
+            feature.addHTMLRule(rule);
           }
+        };
+
+        // Create hidden CKEditor with all features enabled, retrieve metadata.
+        // @see \Drupal\ckeditor\Plugin\Editor\CKEditor::settingsForm.
+        var hiddenCKEditorID = 'ckeditor-hidden';
+        if (CKEDITOR.instances[hiddenCKEditorID]) {
+          CKEDITOR.instances[hiddenCKEditorID].destroy(true);
         }
-      }
-      CKEDITOR.inline($('#' + hiddenCKEditorID).get(0), CKEditorConfig);
-
-      // Once the instance is ready, retrieve the allowedContent filter rules
-      // and convert them to Drupal.EditorFeature objects.
-      CKEDITOR.once('instanceReady', function (e) {
-        if (e.editor.name === hiddenCKEditorID) {
-          // First collect all CKEditor allowedContent rules.
-          var CKEFeatureRulesMap = {};
-          var rules = e.editor.filter.allowedContent;
-          var rule, name;
-          for (var i = 0; i < rules.length; i++) {
-            rule = rules[i];
-            name = rule.featureName || ':(';
-            if (!CKEFeatureRulesMap[name]) {
-              CKEFeatureRulesMap[name] = [];
+        // Load external plugins, if any.
+        var hiddenEditorConfig = this.model.get('hiddenEditorConfig');
+        if (hiddenEditorConfig.drupalExternalPlugins) {
+          var externalPlugins = hiddenEditorConfig.drupalExternalPlugins;
+          for (var pluginName in externalPlugins) {
+            if (externalPlugins.hasOwnProperty(pluginName)) {
+              CKEDITOR.plugins.addExternal(pluginName, externalPlugins[pluginName], '');
             }
-            CKEFeatureRulesMap[name].push(rule);
           }
+        }
+        CKEDITOR.inline($('#' + hiddenCKEditorID).get(0), CKEditorConfig);
+
+        // Once the instance is ready, retrieve the allowedContent filter rules
+        // and convert them to Drupal.EditorFeature objects.
+        CKEDITOR.once('instanceReady', function (e) {
+          if (e.editor.name === hiddenCKEditorID) {
+            // First collect all CKEditor allowedContent rules.
+            var CKEFeatureRulesMap = {};
+            var rules = e.editor.filter.allowedContent;
+            var rule, name;
+            for (var i = 0; i < rules.length; i++) {
+              rule = rules[i];
+              name = rule.featureName || ':(';
+              if (!CKEFeatureRulesMap[name]) {
+                CKEFeatureRulesMap[name] = [];
+              }
+              CKEFeatureRulesMap[name].push(rule);
+            }
 
-          // Now convert these to Drupal.EditorFeature objects.
-          var features = {};
-          for (var featureName in CKEFeatureRulesMap) {
-            if (CKEFeatureRulesMap.hasOwnProperty(featureName)) {
-              var feature = new Drupal.EditorFeature(featureName);
-              convertCKERulesToEditorFeature(feature, CKEFeatureRulesMap[featureName]);
-              features[featureName] = feature;
+            // Now convert these to Drupal.EditorFeature objects.
+            var features = {};
+            for (var featureName in CKEFeatureRulesMap) {
+              if (CKEFeatureRulesMap.hasOwnProperty(featureName)) {
+                var feature = new Drupal.EditorFeature(featureName);
+                convertCKERulesToEditorFeature(feature, CKEFeatureRulesMap[featureName]);
+                features[featureName] = feature;
+              }
             }
+
+            callback(features);
           }
+        });
+      },
 
-          callback(features);
+      /**
+       * Retrieves the feature for a given button from featuresMetadata. Returns
+       * false if the given button is in fact a divider.
+       *
+       * @param String button
+       *   The name of a CKEditor button.
+       * @return Object
+       *   The feature metadata object for a button.
+       */
+      getFeatureForButton: function (button) {
+        // Return false if the button being added is a divider.
+        if (button === '-') {
+          return false;
         }
-      });
-    },
 
-    /**
-     * Retrieves the feature for a given button from featuresMetadata. Returns
-     * false if the given button is in fact a divider.
-     *
-     * @param String button
-     *   The name of a CKEditor button.
-     * @return Object
-     *   The feature metadata object for a button.
-     */
-    getFeatureForButton: function (button) {
-      // Return false if the button being added is a divider.
-      if (button === '-') {
-        return false;
-      }
-
-      // Get a Drupal.editorFeature object that contains all metadata for
-      // the feature that was just added or removed. Not every feature has
-      // such metadata.
-      var featureName = button.toLowerCase();
-      var featuresMetadata = this.model.get('featuresMetadata');
-      if (!featuresMetadata[featureName]) {
-        featuresMetadata[featureName] = new Drupal.EditorFeature(featureName);
-        this.model.set('featuresMetadata', featuresMetadata);
-      }
-      return featuresMetadata[featureName];
-    },
-
-    /**
-     * Checks buttons against filter settings; disables disallowed buttons.
-     *
-     * @param Object features
-     *   A map of Drupal.EditorFeature objects.
-     */
-    disableFeaturesDisallowedByFilters: function (features) {
-      this.model.set('featuresMetadata', features);
-
-      // Ensure that toolbar configuration changes are broadcast.
-      this.broadcastConfigurationChanges(this.$el);
-
-      // Initialization: not all of the default toolbar buttons may be allowed
-      // by the current filter settings. Remove any of the default toolbar
-      // buttons that require more permissive filter settings. The remaining
-      // default toolbar buttons are marked as "added".
-      var existingButtons = [];
-      // Loop through each button group after flattening the groups from the
-      // toolbar row arrays.
-      for (var i = 0, buttonGroups = _.flatten(this.model.get('activeEditorConfig')); i < buttonGroups.length; i++) {
-        // Pull the button names from each toolbar button group.
-        for (var k = 0, buttons = buttonGroups[i].items; k < buttons.length; k++) {
-          existingButtons.push(buttons[k]);
-        }
-      }
-      // Remove duplicate buttons.
-      existingButtons = _.unique(existingButtons);
-      // Prepare the active toolbar and available-button toolbars.
-      for (i = 0; i < existingButtons.length; i++) {
-        var button = existingButtons[i];
-        var feature = this.getFeatureForButton(button);
-        // Skip dividers.
-        if (feature === false) {
-          continue;
+        // Get a Drupal.editorFeature object that contains all metadata for
+        // the feature that was just added or removed. Not every feature has
+        // such metadata.
+        var featureName = button.toLowerCase();
+        var featuresMetadata = this.model.get('featuresMetadata');
+        if (!featuresMetadata[featureName]) {
+          featuresMetadata[featureName] = new Drupal.EditorFeature(featureName);
+          this.model.set('featuresMetadata', featuresMetadata);
         }
+        return featuresMetadata[featureName];
+      },
 
-        if (Drupal.editorConfiguration.featureIsAllowedByFilters(feature)) {
-          // Existing toolbar buttons are in fact "added features".
-          this.$el.find('.ckeditor-toolbar-active').trigger('CKEditorToolbarChanged', ['added', existingButtons[i]]);
-        }
-        else {
-          // Move the button element from the active the active toolbar to the
-          // list of available buttons.
-          $('.ckeditor-toolbar-active li[data-drupal-ckeditor-button-name="' + button + '"]')
-            .detach()
-            .appendTo('.ckeditor-toolbar-disabled > .ckeditor-toolbar-available > ul');
-          // Update the toolbar value field.
-          this.model.set({'isDirty': true}, {broadcast: false});
+      /**
+       * Checks buttons against filter settings; disables disallowed buttons.
+       *
+       * @param Object features
+       *   A map of Drupal.EditorFeature objects.
+       */
+      disableFeaturesDisallowedByFilters: function (features) {
+        this.model.set('featuresMetadata', features);
+
+        // Ensure that toolbar configuration changes are broadcast.
+        this.broadcastConfigurationChanges(this.$el);
+
+        // Initialization: not all of the default toolbar buttons may be allowed
+        // by the current filter settings. Remove any of the default toolbar
+        // buttons that require more permissive filter settings. The remaining
+        // default toolbar buttons are marked as "added".
+        var existingButtons = [];
+        // Loop through each button group after flattening the groups from the
+        // toolbar row arrays.
+        for (var i = 0, buttonGroups = _.flatten(this.model.get('activeEditorConfig')); i < buttonGroups.length; i++) {
+          // Pull the button names from each toolbar button group.
+          for (var k = 0, buttons = buttonGroups[i].items; k < buttons.length; k++) {
+            existingButtons.push(buttons[k]);
+          }
         }
-      }
-    },
-
-    /**
-     * Sets up broadcasting of CKEditor toolbar configuration changes.
-     *
-     * @param jQuery $ckeditorToolbar
-     *   The active toolbar DOM element wrapped in jQuery.
-     */
-    broadcastConfigurationChanges: function ($ckeditorToolbar) {
-      var view = this;
-      var hiddenEditorConfig = this.model.get('hiddenEditorConfig');
-      var featuresMetadata = this.model.get('featuresMetadata');
-      var getFeatureForButton = this.getFeatureForButton.bind(this);
-      var getCKEditorFeatures = this.getCKEditorFeatures.bind(this);
-      $ckeditorToolbar
-        .find('.ckeditor-toolbar-active')
-        // Listen for CKEditor toolbar configuration changes. When a button is
-        // added/removed, call an appropriate Drupal.editorConfiguration method.
-        .on('CKEditorToolbarChanged.ckeditorAdmin', function (event, action, button) {
-          var feature = getFeatureForButton(button);
-
-          // Early-return if the button being added is a divider.
+        // Remove duplicate buttons.
+        existingButtons = _.unique(existingButtons);
+        // Prepare the active toolbar and available-button toolbars.
+        for (i = 0; i < existingButtons.length; i++) {
+          var button = existingButtons[i];
+          var feature = this.getFeatureForButton(button);
+          // Skip dividers.
           if (feature === false) {
-            return;
+            continue;
           }
 
-          // Trigger a standardized text editor configuration event to indicate
-          // whether a feature was added or removed, so that filters can react.
-          var configEvent = (action === 'added') ? 'addedFeature' : 'removedFeature';
-          Drupal.editorConfiguration[configEvent](feature);
-        })
-        // Listen for CKEditor plugin settings changes. When a plugin setting is
-        // changed, rebuild the CKEditor features metadata.
-        .on('CKEditorPluginSettingsChanged.ckeditorAdmin', function (event, settingsChanges) {
-          // Update hidden CKEditor configuration.
-          for (var key in settingsChanges) {
-            if (settingsChanges.hasOwnProperty(key)) {
-              hiddenEditorConfig[key] = settingsChanges[key];
-            }
+          if (Drupal.editorConfiguration.featureIsAllowedByFilters(feature)) {
+            // Existing toolbar buttons are in fact "added features".
+            this.$el.find('.ckeditor-toolbar-active').trigger('CKEditorToolbarChanged', ['added', existingButtons[i]]);
           }
+          else {
+            // Move the button element from the active the active toolbar to the
+            // list of available buttons.
+            $('.ckeditor-toolbar-active li[data-drupal-ckeditor-button-name="' + button + '"]')
+              .detach()
+              .appendTo('.ckeditor-toolbar-disabled > .ckeditor-toolbar-available > ul');
+            // Update the toolbar value field.
+            this.model.set({'isDirty': true}, {broadcast: false});
+          }
+        }
+      },
 
-          // Retrieve features for the updated hidden CKEditor configuration.
-          getCKEditorFeatures(hiddenEditorConfig, function (features) {
-            // Trigger a standardized text editor configuration event for each
-            // feature that was modified by the configuration changes.
-            for (var name in features) {
-              if (features.hasOwnProperty(name)) {
-                var feature = features[name];
-                if (featuresMetadata.hasOwnProperty(name) && !_.isEqual(featuresMetadata[name], feature)) {
-                  Drupal.editorConfiguration.modifiedFeature(feature);
-                }
+      /**
+       * Sets up broadcasting of CKEditor toolbar configuration changes.
+       *
+       * @param jQuery $ckeditorToolbar
+       *   The active toolbar DOM element wrapped in jQuery.
+       */
+      broadcastConfigurationChanges: function ($ckeditorToolbar) {
+        var view = this;
+        var hiddenEditorConfig = this.model.get('hiddenEditorConfig');
+        var featuresMetadata = this.model.get('featuresMetadata');
+        var getFeatureForButton = this.getFeatureForButton.bind(this);
+        var getCKEditorFeatures = this.getCKEditorFeatures.bind(this);
+        $ckeditorToolbar
+          .find('.ckeditor-toolbar-active')
+          // Listen for CKEditor toolbar configuration changes. When a button is
+          // added/removed, call an appropriate Drupal.editorConfiguration method.
+          .on('CKEditorToolbarChanged.ckeditorAdmin', function (event, action, button) {
+            var feature = getFeatureForButton(button);
+
+            // Early-return if the button being added is a divider.
+            if (feature === false) {
+              return;
+            }
+
+            // Trigger a standardized text editor configuration event to indicate
+            // whether a feature was added or removed, so that filters can react.
+            var configEvent = (action === 'added') ? 'addedFeature' : 'removedFeature';
+            Drupal.editorConfiguration[configEvent](feature);
+          })
+          // Listen for CKEditor plugin settings changes. When a plugin setting is
+          // changed, rebuild the CKEditor features metadata.
+          .on('CKEditorPluginSettingsChanged.ckeditorAdmin', function (event, settingsChanges) {
+            // Update hidden CKEditor configuration.
+            for (var key in settingsChanges) {
+              if (settingsChanges.hasOwnProperty(key)) {
+                hiddenEditorConfig[key] = settingsChanges[key];
               }
             }
-            // Update the CKEditor features metadata.
-            view.model.set('featuresMetadata', features);
+
+            // Retrieve features for the updated hidden CKEditor configuration.
+            getCKEditorFeatures(hiddenEditorConfig, function (features) {
+              // Trigger a standardized text editor configuration event for each
+              // feature that was modified by the configuration changes.
+              for (var name in features) {
+                if (features.hasOwnProperty(name)) {
+                  var feature = features[name];
+                  if (featuresMetadata.hasOwnProperty(name) && !_.isEqual(featuresMetadata[name], feature)) {
+                    Drupal.editorConfiguration.modifiedFeature(feature);
+                  }
+                }
+              }
+              // Update the CKEditor features metadata.
+              view.model.set('featuresMetadata', features);
+            });
           });
-        });
-    },
+      },
 
-    /**
-     * Returns the list of buttons from an editor configuration.
-     *
-     * @param Object config
-     *   A CKEditor configuration object.
-     * @return Array
-     *   A list of buttons in the CKEditor configuration.
-     */
-    getButtonList: function (config) {
-      var buttons = [];
-      // Remove the rows
-      config = _.flatten(config);
-
-      // Loop through the button groups and pull out the buttons.
-      config.forEach(function (group) {
-        group.items.forEach(function (button) {
-          buttons.push(button);
+      /**
+       * Returns the list of buttons from an editor configuration.
+       *
+       * @param Object config
+       *   A CKEditor configuration object.
+       * @return Array
+       *   A list of buttons in the CKEditor configuration.
+       */
+      getButtonList: function (config) {
+        var buttons = [];
+        // Remove the rows
+        config = _.flatten(config);
+
+        // Loop through the button groups and pull out the buttons.
+        config.forEach(function (group) {
+          group.items.forEach(function (button) {
+            buttons.push(button);
+          });
         });
-      });
-
-      // Remove the dividing elements if any.
-      return _.without(buttons, '-');
-    }
-  }),
-
-  /**
-   * Backbone View for CKEditor toolbar configuration; visual UX.
-   */
-  ConfigurationVisualView: Backbone.View.extend({
 
-    events: {
-      'click .ckeditor-toolbar-group-name': 'onGroupNameClick',
-      'click .ckeditor-groupnames-toggle': 'onGroupNamesToggleClick',
-      'click .ckeditor-add-new-group button': 'onAddGroupButtonClick'
-    },
+        // Remove the dividing elements if any.
+        return _.without(buttons, '-');
+      }
+    }),
 
     /**
-     * {@inheritdoc}
+     * Backbone View for CKEditor toolbar configuration; visual UX.
      */
-    initialize: function () {
-      this.listenTo(this.model, 'change:isDirty change:groupNamesVisible', this.render);
+    ConfigurationVisualView: Backbone.View.extend({
 
-      // Add a toggle for the button group names.
-      $(Drupal.theme('ckeditorButtonGroupNamesToggle'))
-        .prependTo(this.$el.find('#ckeditor-active-toolbar').parent());
+      events: {
+        'click .ckeditor-toolbar-group-name': 'onGroupNameClick',
+        'click .ckeditor-groupnames-toggle': 'onGroupNamesToggleClick',
+        'click .ckeditor-add-new-group button': 'onAddGroupButtonClick'
+      },
 
-      this.render();
-    },
+      /**
+       * {@inheritdoc}
+       */
+      initialize: function () {
+        this.listenTo(this.model, 'change:isDirty change:groupNamesVisible', this.render);
 
-    /**
-     * {@inheritdoc}
-     */
-    render: function (model, value, changedAttributes) {
-      this.insertPlaceholders();
-      this.applySorting();
-
-      // Toggle button group names.
-      var groupNamesVisible = this.model.get('groupNamesVisible');
-      // If a button was just placed in the active toolbar, ensure that the
-      // button group names are visible.
-      if (changedAttributes && changedAttributes.changes && changedAttributes.changes.isDirty) {
-        this.model.set({groupNamesVisible: true}, {silent: true});
-        groupNamesVisible = true;
-      }
-      this.$el.find('[data-toolbar="active"]').toggleClass('ckeditor-group-names-are-visible', groupNamesVisible);
-      this.$el.find('.ckeditor-groupnames-toggle')
-        .text((groupNamesVisible) ? Drupal.t('Hide group names') : Drupal.t('Show group names'))
-        .attr('aria-pressed', groupNamesVisible);
+        // Add a toggle for the button group names.
+        $(Drupal.theme('ckeditorButtonGroupNamesToggle'))
+          .prependTo(this.$el.find('#ckeditor-active-toolbar').parent());
 
-      return this;
-    },
+        this.render();
+      },
 
-    /**
-     * Handles clicks to a button group name.
-     *
-     * @param jQuery.Event event
-     */
-    onGroupNameClick: function (event) {
-      var $group = $(event.currentTarget).closest('.ckeditor-toolbar-group');
-      openGroupNameDialog(this, $group);
+      /**
+       * {@inheritdoc}
+       */
+      render: function (model, value, changedAttributes) {
+        this.insertPlaceholders();
+        this.applySorting();
+
+        // Toggle button group names.
+        var groupNamesVisible = this.model.get('groupNamesVisible');
+        // If a button was just placed in the active toolbar, ensure that the
+        // button group names are visible.
+        if (changedAttributes && changedAttributes.changes && changedAttributes.changes.isDirty) {
+          this.model.set({groupNamesVisible: true}, {silent: true});
+          groupNamesVisible = true;
+        }
+        this.$el.find('[data-toolbar="active"]').toggleClass('ckeditor-group-names-are-visible', groupNamesVisible);
+        this.$el.find('.ckeditor-groupnames-toggle')
+          .text((groupNamesVisible) ? Drupal.t('Hide group names') : Drupal.t('Show group names'))
+          .attr('aria-pressed', groupNamesVisible);
 
-      event.stopPropagation();
-      event.preventDefault();
-    },
+        return this;
+      },
 
-    /**
-     * Handles clicks on the button group names toggle button.
-     */
-    onGroupNamesToggleClick: function (event) {
-      this.model.set('groupNamesVisible', !this.model.get('groupNamesVisible'));
-      event.preventDefault();
-    },
+      /**
+       * Handles clicks to a button group name.
+       *
+       * @param jQuery.Event event
+       */
+      onGroupNameClick: function (event) {
+        var $group = $(event.currentTarget).closest('.ckeditor-toolbar-group');
+        openGroupNameDialog(this, $group);
 
-    /**
-     * Prompts the user to provide a name for a new button group; inserts it.
-     *
-     * @param jQuery.Event event
-     */
-    onAddGroupButtonClick: function (event) {
+        event.stopPropagation();
+        event.preventDefault();
+      },
 
       /**
-       * Inserts a new button if the openGroupNameDialog function returns true.
+       * Handles clicks on the button group names toggle button.
+       */
+      onGroupNamesToggleClick: function (event) {
+        this.model.set('groupNamesVisible', !this.model.get('groupNamesVisible'));
+        event.preventDefault();
+      },
+
+      /**
+       * Prompts the user to provide a name for a new button group; inserts it.
        *
-       * @param Boolean success
-       *   A flag that indicates if the user created a new group (true) or
-       *   canceled out of the dialog (false).
-       * @param jQuery $group
-       *   A jQuery DOM fragment that represents the new button group. It has
-       *   not been added to the DOM yet.
+       * @param jQuery.Event event
        */
-      function insertNewGroup (success, $group) {
-        if (success) {
-          $group.appendTo($(event.currentTarget).closest('.ckeditor-row').children('.ckeditor-toolbar-groups'));
-          // Focus on the new group.
-          $group.trigger('focus');
+      onAddGroupButtonClick: function (event) {
+
+        /**
+         * Inserts a new button if the openGroupNameDialog function returns true.
+         *
+         * @param Boolean success
+         *   A flag that indicates if the user created a new group (true) or
+         *   canceled out of the dialog (false).
+         * @param jQuery $group
+         *   A jQuery DOM fragment that represents the new button group. It has
+         *   not been added to the DOM yet.
+         */
+        function insertNewGroup(success, $group) {
+          if (success) {
+            $group.appendTo($(event.currentTarget).closest('.ckeditor-row').children('.ckeditor-toolbar-groups'));
+            // Focus on the new group.
+            $group.trigger('focus');
+          }
         }
-      }
 
-      // Pass in a DOM fragment of a placeholder group so that the new group
-      // name can be applied to it.
-      openGroupNameDialog(this, $(Drupal.theme('ckeditorToolbarGroup')), insertNewGroup);
+        // Pass in a DOM fragment of a placeholder group so that the new group
+        // name can be applied to it.
+        openGroupNameDialog(this, $(Drupal.theme('ckeditorToolbarGroup')), insertNewGroup);
 
-      event.preventDefault();
-    },
+        event.preventDefault();
+      },
 
-    /**
-     * Handles jQuery Sortable stop sort of a button group.
-     *
-     * @param jQuery.Event event
-     * @param Object ui
-     *   A jQuery.ui.sortable argument that contains information about the
-     *   elements involved in the sort action.
-     */
-    endGroupDrag: function (event, ui) {
-      var view = this;
-      registerGroupMove(this, ui.item, function (success) {
-        if (!success) {
-          // Cancel any sorting in the configuration area.
-          view.$el.find('.ckeditor-toolbar-configuration').find('.ui-sortable').sortable('cancel');
-        }
-      });
-    },
+      /**
+       * Handles jQuery Sortable stop sort of a button group.
+       *
+       * @param jQuery.Event event
+       * @param Object ui
+       *   A jQuery.ui.sortable argument that contains information about the
+       *   elements involved in the sort action.
+       */
+      endGroupDrag: function (event, ui) {
+        var view = this;
+        registerGroupMove(this, ui.item, function (success) {
+          if (!success) {
+            // Cancel any sorting in the configuration area.
+            view.$el.find('.ckeditor-toolbar-configuration').find('.ui-sortable').sortable('cancel');
+          }
+        });
+      },
 
-    /**
-     * Handles jQuery Sortable start sort of a button.
-     *
-     * @param jQuery.Event event
-     * @param Object ui
-     *   A jQuery.ui.sortable argument that contains information about the
-     *   elements involved in the sort action.
-     */
-    startButtonDrag: function (event, ui) {
-      this.$el.find('a:focus').trigger('blur');
+      /**
+       * Handles jQuery Sortable start sort of a button.
+       *
+       * @param jQuery.Event event
+       * @param Object ui
+       *   A jQuery.ui.sortable argument that contains information about the
+       *   elements involved in the sort action.
+       */
+      startButtonDrag: function (event, ui) {
+        this.$el.find('a:focus').trigger('blur');
 
-      // Show the button group names as soon as the user starts dragging.
-      this.model.set('groupNamesVisible', true);
-    },
+        // Show the button group names as soon as the user starts dragging.
+        this.model.set('groupNamesVisible', true);
+      },
 
-    /**
-     * Handles jQuery Sortable stop sort of a button.
-     *
-     * @param jQuery.Event event
-     * @param Object ui
-     *   A jQuery.ui.sortable argument that contains information about the
-     *   elements involved in the sort action.
-     */
-    endButtonDrag: function (event, ui) {
-      var view = this;
-      registerButtonMove(this, ui.item, function (success) {
-        if (!success) {
-          // Cancel any sorting in the configuration area.
-          view.$el.find('.ui-sortable').sortable('cancel');
-        }
-        // Refocus the target button so that the user can continue from a known
-        // place.
-        ui.item.find('a').trigger('focus');
-      });
-    },
+      /**
+       * Handles jQuery Sortable stop sort of a button.
+       *
+       * @param jQuery.Event event
+       * @param Object ui
+       *   A jQuery.ui.sortable argument that contains information about the
+       *   elements involved in the sort action.
+       */
+      endButtonDrag: function (event, ui) {
+        var view = this;
+        registerButtonMove(this, ui.item, function (success) {
+          if (!success) {
+            // Cancel any sorting in the configuration area.
+            view.$el.find('.ui-sortable').sortable('cancel');
+          }
+          // Refocus the target button so that the user can continue from a known
+          // place.
+          ui.item.find('a').trigger('focus');
+        });
+      },
 
-    /**
-     * Invokes jQuery.sortable() on new buttons and groups in a CKEditor config.
-     */
-    applySorting: function () {
-      // Make the buttons sortable.
-      this.$el.find('.ckeditor-buttons').not('.ui-sortable').sortable({
-        // Change this to .ckeditor-toolbar-group-buttons.
-        connectWith: '.ckeditor-buttons',
-        placeholder: 'ckeditor-button-placeholder',
-        forcePlaceholderSize: true,
-        tolerance: 'pointer',
-        cursor: 'move',
-        start: this.startButtonDrag.bind(this),
-        // Sorting within a sortable.
-        stop: this.endButtonDrag.bind(this)
-      }).disableSelection();
-
-      // Add the drag and drop functionality to button groups.
-      this.$el.find('.ckeditor-toolbar-groups').not('.ui-sortable').sortable({
-        connectWith: '.ckeditor-toolbar-groups',
-        cancel: '.ckeditor-add-new-group',
-        placeholder: 'ckeditor-toolbar-group-placeholder',
-        forcePlaceholderSize: true,
-        cursor: 'move',
-        stop: this.endGroupDrag.bind(this)
-      });
-
-      // Add the drag and drop functionality to buttons.
-      this.$el.find('.ckeditor-multiple-buttons li').draggable({
-        connectToSortable: '.ckeditor-toolbar-active .ckeditor-buttons',
-        helper: 'clone'
-      });
-    },
+      /**
+       * Invokes jQuery.sortable() on new buttons and groups in a CKEditor config.
+       */
+      applySorting: function () {
+        // Make the buttons sortable.
+        this.$el.find('.ckeditor-buttons').not('.ui-sortable').sortable({
+          // Change this to .ckeditor-toolbar-group-buttons.
+          connectWith: '.ckeditor-buttons',
+          placeholder: 'ckeditor-button-placeholder',
+          forcePlaceholderSize: true,
+          tolerance: 'pointer',
+          cursor: 'move',
+          start: this.startButtonDrag.bind(this),
+          // Sorting within a sortable.
+          stop: this.endButtonDrag.bind(this)
+        }).disableSelection();
+
+        // Add the drag and drop functionality to button groups.
+        this.$el.find('.ckeditor-toolbar-groups').not('.ui-sortable').sortable({
+          connectWith: '.ckeditor-toolbar-groups',
+          cancel: '.ckeditor-add-new-group',
+          placeholder: 'ckeditor-toolbar-group-placeholder',
+          forcePlaceholderSize: true,
+          cursor: 'move',
+          stop: this.endGroupDrag.bind(this)
+        });
 
-    /**
-     * Wraps the invocation of methods to insert blank groups and rows.
-     */
-    insertPlaceholders: function () {
-      this.insertPlaceholderRow();
-      this.insertNewGroupButtons();
-    },
+        // Add the drag and drop functionality to buttons.
+        this.$el.find('.ckeditor-multiple-buttons li').draggable({
+          connectToSortable: '.ckeditor-toolbar-active .ckeditor-buttons',
+          helper: 'clone'
+        });
+      },
 
-    /**
-     * Inserts a blank row at the bottom of the CKEditor configuration.
-     */
-    insertPlaceholderRow: function () {
-      var $rows = this.$el.find('.ckeditor-row');
-      // Add a placeholder row. to the end of the list if one does not exist.
-      if (!$rows.eq(-1).hasClass('placeholder')) {
-         this.$el
-          .find('.ckeditor-toolbar-active')
-          .children('.ckeditor-active-toolbar-configuration')
-          .append(Drupal.theme('ckeditorRow'));
-      }
-      // Update the $rows variable to include the new row.
-      $rows = this.$el.find('.ckeditor-row');
-      // Remove blank rows except the last one.
-      var len = $rows.length;
-      $rows.filter(function (index, row) {
+      /**
+       * Wraps the invocation of methods to insert blank groups and rows.
+       */
+      insertPlaceholders: function () {
+        this.insertPlaceholderRow();
+        this.insertNewGroupButtons();
+      },
+
+      /**
+       * Inserts a blank row at the bottom of the CKEditor configuration.
+       */
+      insertPlaceholderRow: function () {
+        var $rows = this.$el.find('.ckeditor-row');
+        // Add a placeholder row. to the end of the list if one does not exist.
+        if (!$rows.eq(-1).hasClass('placeholder')) {
+          this.$el
+            .find('.ckeditor-toolbar-active')
+            .children('.ckeditor-active-toolbar-configuration')
+            .append(Drupal.theme('ckeditorRow'));
+        }
+        // Update the $rows variable to include the new row.
+        $rows = this.$el.find('.ckeditor-row');
+        // Remove blank rows except the last one.
+        var len = $rows.length;
+        $rows.filter(function (index, row) {
           // Do not remove the last row.
           if (index + 1 === len) {
             return false;
           }
           return $(row).find('.ckeditor-toolbar-group').not('.placeholder').length === 0;
         })
-      // Then get all rows that are placeholders and remove them.
-      .remove();
-    },
-
-    /**
-     * Inserts a button in each row that will add a new CKEditor button group.
-     */
-    insertNewGroupButtons: function () {
-      // Insert an add group button to each row.
-      this.$el.find('.ckeditor-row').each(function () {
-        var $row = $(this);
-        var $groups = $row.find('.ckeditor-toolbar-group');
-        var $button = $row.find('.ckeditor-add-new-group');
-        if ($button.length === 0) {
-          $row.children('.ckeditor-toolbar-groups').append(Drupal.theme('ckeditorNewButtonGroup'));
-        }
-        // If a placeholder group exists, make sure it's at the end of the row.
-        else if (!$groups.eq(-1).hasClass('ckeditor-add-new-group')) {
-          $button.appendTo($row.children('.ckeditor-toolbar-groups'));
-        }
-      });
-    }
-  }),
+          // Then get all rows that are placeholders and remove them.
+          .remove();
+      },
 
-  /**
-   * Backbone View for CKEditor toolbar configuration; keyboard UX.
-   */
-  ConfigurationKeyboardView: Backbone.View.extend({
+      /**
+       * Inserts a button in each row that will add a new CKEditor button group.
+       */
+      insertNewGroupButtons: function () {
+        // Insert an add group button to each row.
+        this.$el.find('.ckeditor-row').each(function () {
+          var $row = $(this);
+          var $groups = $row.find('.ckeditor-toolbar-group');
+          var $button = $row.find('.ckeditor-add-new-group');
+          if ($button.length === 0) {
+            $row.children('.ckeditor-toolbar-groups').append(Drupal.theme('ckeditorNewButtonGroup'));
+          }
+          // If a placeholder group exists, make sure it's at the end of the row.
+          else if (!$groups.eq(-1).hasClass('ckeditor-add-new-group')) {
+            $button.appendTo($row.children('.ckeditor-toolbar-groups'));
+          }
+        });
+      }
+    }),
 
     /**
-     * {@inheritdoc}
+     * Backbone View for CKEditor toolbar configuration; keyboard UX.
      */
-    initialize: function () {
-      // Add keyboard arrow support.
-      this.$el.on('keydown.ckeditor', '.ckeditor-buttons a, .ckeditor-multiple-buttons a', this.onPressButton.bind(this));
-      this.$el.on('keydown.ckeditor', '[data-drupal-ckeditor-type="group"]', this.onPressGroup.bind(this));
-    },
+    ConfigurationKeyboardView: Backbone.View.extend({
 
-    /**
-     * {@inheritdoc}
-     */
-    render: function () {},
+      /**
+       * {@inheritdoc}
+       */
+      initialize: function () {
+        // Add keyboard arrow support.
+        this.$el.on('keydown.ckeditor', '.ckeditor-buttons a, .ckeditor-multiple-buttons a', this.onPressButton.bind(this));
+        this.$el.on('keydown.ckeditor', '[data-drupal-ckeditor-type="group"]', this.onPressGroup.bind(this));
+      },
 
-    /**
-     * Handles keypresses on a CKEditor configuration button.
-     *
-     * @param jQuery.Event event
-     */
-    onPressButton: function (event) {
-      var upDownKeys = [
-        38, // Up arrow.
-        63232, // Safari up arrow.
-        40, // Down arrow.
-        63233 // Safari down arrow.
-      ];
-      var leftRightKeys = [
-        37, // Left arrow.
-        63234, // Safari left arrow.
-        39, // Right arrow.
-        63235 // Safari right arrow.
-      ];
-
-      // Respond to an enter key press. Prevent the bubbling of the enter key
-      // press to the button group parent element.
-      if (event.keyCode === 13) {
-        event.stopPropagation();
-      }
+      /**
+       * {@inheritdoc}
+       */
+      render: function () {},
 
-      // Only take action when a direction key is pressed.
-      if (_.indexOf(_.union(upDownKeys, leftRightKeys), event.keyCode) > -1) {
-        var view = this;
-        var $target = $(event.currentTarget);
-        var $button = $target.parent();
-        var $container = $button.parent();
-        var $group = $button.closest('.ckeditor-toolbar-group');
-        var $row = $button.closest('.ckeditor-row');
-        var containerType = $container.data('drupal-ckeditor-button-sorting');
-        var $availableButtons = this.$el.find('[data-drupal-ckeditor-button-sorting="source"]');
-        var $activeButtons = this.$el.find('.ckeditor-toolbar-active');
-        // The current location of the button, just in case it needs to be put
-        // back.
-        var $originalGroup = $group;
-        var dir;
-
-        // Move available buttons between their container and the active toolbar.
-        if (containerType === 'source') {
-          // Move the button to the active toolbar configuration when the down or
-          // up keys are pressed.
-          if (_.indexOf([40, 63233], event.keyCode) > -1) {
-            // Move the button to the first row, first button group index
-            // position.
-            $activeButtons.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button);
-          }
+      /**
+       * Handles keypresses on a CKEditor configuration button.
+       *
+       * @param jQuery.Event event
+       */
+      onPressButton: function (event) {
+        var upDownKeys = [
+          38, // Up arrow.
+          63232, // Safari up arrow.
+          40, // Down arrow.
+          63233 // Safari down arrow.
+        ];
+        var leftRightKeys = [
+          37, // Left arrow.
+          63234, // Safari left arrow.
+          39, // Right arrow.
+          63235 // Safari right arrow.
+        ];
+
+        // Respond to an enter key press. Prevent the bubbling of the enter key
+        // press to the button group parent element.
+        if (event.keyCode === 13) {
+          event.stopPropagation();
         }
-        else if (containerType === 'target') {
-          // Move buttons between sibling buttons in a group and between groups.
-          if (_.indexOf(leftRightKeys, event.keyCode) > -1) {
-            // Move left.
-            var $siblings = $container.children();
-            var index = $siblings.index($button);
-            if (_.indexOf([37, 63234], event.keyCode) > -1) {
-              // Move between sibling buttons.
-              if (index > 0) {
-                $button.insertBefore($container.children().eq(index - 1));
+
+        // Only take action when a direction key is pressed.
+        if (_.indexOf(_.union(upDownKeys, leftRightKeys), event.keyCode) > -1) {
+          var view = this;
+          var $target = $(event.currentTarget);
+          var $button = $target.parent();
+          var $container = $button.parent();
+          var $group = $button.closest('.ckeditor-toolbar-group');
+          var $row = $button.closest('.ckeditor-row');
+          var containerType = $container.data('drupal-ckeditor-button-sorting');
+          var $availableButtons = this.$el.find('[data-drupal-ckeditor-button-sorting="source"]');
+          var $activeButtons = this.$el.find('.ckeditor-toolbar-active');
+          // The current location of the button, just in case it needs to be put
+          // back.
+          var $originalGroup = $group;
+          var dir;
+
+          // Move available buttons between their container and the active toolbar.
+          if (containerType === 'source') {
+            // Move the button to the active toolbar configuration when the down or
+            // up keys are pressed.
+            if (_.indexOf([40, 63233], event.keyCode) > -1) {
+              // Move the button to the first row, first button group index
+              // position.
+              $activeButtons.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button);
+            }
+          }
+          else if (containerType === 'target') {
+            // Move buttons between sibling buttons in a group and between groups.
+            if (_.indexOf(leftRightKeys, event.keyCode) > -1) {
+              // Move left.
+              var $siblings = $container.children();
+              var index = $siblings.index($button);
+              if (_.indexOf([37, 63234], event.keyCode) > -1) {
+                // Move between sibling buttons.
+                if (index > 0) {
+                  $button.insertBefore($container.children().eq(index - 1));
+                }
+                // Move between button groups and rows.
+                else {
+                  // Move between button groups.
+                  $group = $container.parent().prev();
+                  if ($group.length > 0) {
+                    $group.find('.ckeditor-toolbar-group-buttons').append($button);
+                  }
+                  // Wrap between rows.
+                  else {
+                    $container.closest('.ckeditor-row').prev().find('.ckeditor-toolbar-group').not('.placeholder').find('.ckeditor-toolbar-group-buttons').eq(-1).append($button);
+                  }
+                }
               }
-              // Move between button groups and rows.
-              else {
-                // Move between button groups.
-                $group = $container.parent().prev();
-                if ($group.length > 0) {
-                  $group.find('.ckeditor-toolbar-group-buttons').append($button);
+              // Move right.
+              else if (_.indexOf([39, 63235], event.keyCode) > -1) {
+                // Move between sibling buttons.
+                if (index < ($siblings.length - 1)) {
+                  $button.insertAfter($container.children().eq(index + 1));
                 }
-                // Wrap between rows.
+                // Move between button groups. Moving right at the end of a row
+                // will create a new group.
                 else {
-                  $container.closest('.ckeditor-row').prev().find('.ckeditor-toolbar-group').not('.placeholder').find('.ckeditor-toolbar-group-buttons').eq(-1).append($button);
+                  $container.parent().next().find('.ckeditor-toolbar-group-buttons').prepend($button);
                 }
               }
             }
-            // Move right.
-            else if (_.indexOf([39, 63235], event.keyCode) > -1) {
-              // Move between sibling buttons.
-              if (index < ($siblings.length - 1)) {
-                $button.insertAfter($container.children().eq(index + 1));
+            // Move buttons between rows and the available button set.
+            else if (_.indexOf(upDownKeys, event.keyCode) > -1) {
+              dir = (_.indexOf([38, 63232], event.keyCode) > -1) ? 'prev' : 'next';
+              $row = $container.closest('.ckeditor-row')[dir]();
+              // Move the button back into the available button set.
+              if (dir === 'prev' && $row.length === 0) {
+                // If this is a divider, just destroy it.
+                if ($button.data('drupal-ckeditor-type') === 'separator') {
+                  $button
+                    .off()
+                    .remove();
+                  // Focus on the first button in the active toolbar.
+                  $activeButtons.find('.ckeditor-toolbar-group-buttons').eq(0).children().eq(0).children().trigger('focus');
+                }
+                // Otherwise, move it.
+                else {
+                  $availableButtons.prepend($button);
+                }
               }
-              // Move between button groups. Moving right at the end of a row
-              // will create a new group.
               else {
-                $container.parent().next().find('.ckeditor-toolbar-group-buttons').prepend($button);
+                $row.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button);
               }
             }
           }
-          // Move buttons between rows and the available button set.
-          else if (_.indexOf(upDownKeys, event.keyCode) > -1) {
-            dir = (_.indexOf([38, 63232], event.keyCode) > -1) ? 'prev' : 'next';
-            $row = $container.closest('.ckeditor-row')[dir]();
-            // Move the button back into the available button set.
-            if (dir === 'prev' && $row.length === 0) {
-              // If this is a divider, just destroy it.
-              if ($button.data('drupal-ckeditor-type') === 'separator') {
-                $button
-                  .off()
-                  .remove();
-                // Focus on the first button in the active toolbar.
-                $activeButtons.find('.ckeditor-toolbar-group-buttons').eq(0).children().eq(0).children().trigger('focus');
-              }
-              // Otherwise, move it.
-              else {
-                $availableButtons.prepend($button);
-              }
+          // Move dividers between their container and the active toolbar.
+          else if (containerType === 'dividers') {
+            // Move the button to the active toolbar configuration when the down or
+            // up keys are pressed.
+            if (_.indexOf([40, 63233], event.keyCode) > -1) {
+              // Move the button to the first row, first button group index
+              // position.
+              $button = $button.clone(true);
+              $activeButtons.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button);
+              $target = $button.children();
             }
+          }
+
+          view = this;
+          // Attempt to move the button to the new toolbar position.
+          registerButtonMove(this, $button, function (result) {
+
+            // Put the button back if the registration failed.
+            // If the button was in a row, then it was in the active toolbar
+            // configuration. The button was probably placed in a new group, but
+            // that action was canceled.
+            if (!result && $originalGroup) {
+              $originalGroup.find('.ckeditor-buttons').append($button);
+            }
+            // Otherwise refresh the sortables to acknowledge the new button
+            // positions.
             else {
-              $row.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button);
+              view.$el.find('.ui-sortable').sortable('refresh');
             }
-          }
+            // Refocus the target button so that the user can continue from a known
+            // place.
+            $target.trigger('focus');
+          });
+
+          event.preventDefault();
+          event.stopPropagation();
         }
-        // Move dividers between their container and the active toolbar.
-        else if (containerType === 'dividers') {
-          // Move the button to the active toolbar configuration when the down or
-          // up keys are pressed.
-          if (_.indexOf([40, 63233], event.keyCode) > -1) {
-            // Move the button to the first row, first button group index
-            // position.
-            $button = $button.clone(true);
-            $activeButtons.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button);
-            $target = $button.children();
-          }
+      },
+
+      /**
+       * Handles keypresses on a CKEditor configuration group.
+       *
+       * @param jQuery.Event event
+       */
+      onPressGroup: function (event) {
+        var upDownKeys = [
+          38, // Up arrow.
+          63232, // Safari up arrow.
+          40, // Down arrow.
+          63233 // Safari down arrow.
+        ];
+        var leftRightKeys = [
+          37, // Left arrow.
+          63234, // Safari left arrow.
+          39, // Right arrow.
+          63235 // Safari right arrow.
+        ];
+
+        // Respond to an enter key press.
+        if (event.keyCode === 13) {
+          var view = this;
+          // Open the group renaming dialog in the next evaluation cycle so that
+          // this event can be cancelled and the bubbling wiped out. Otherwise,
+          // Firefox has issues because the page focus is shifted to the dialog
+          // along with the keydown event.
+          window.setTimeout(function () {
+            openGroupNameDialog(view, $(event.currentTarget));
+          }, 0);
+          event.preventDefault();
+          event.stopPropagation();
         }
 
-        view = this;
-        // Attempt to move the button to the new toolbar position.
-        registerButtonMove(this, $button, function (result) {
+        // Respond to direction key presses.
+        if (_.indexOf(_.union(upDownKeys, leftRightKeys), event.keyCode) > -1) {
+          var $group = $(event.currentTarget);
+          var $container = $group.parent();
+          var $siblings = $container.children();
+          var index, dir;
+          // Move groups between sibling groups.
+          if (_.indexOf(leftRightKeys, event.keyCode) > -1) {
+            index = $siblings.index($group);
+            // Move left between sibling groups.
+            if ((_.indexOf([37, 63234], event.keyCode) > -1)) {
+              if (index > 0) {
+                $group.insertBefore($siblings.eq(index - 1));
+              }
+              // Wrap between rows. Insert the group before the placeholder group
+              // at the end of the previous row.
+              else {
+                $group.insertBefore($container.closest('.ckeditor-row').prev().find('.ckeditor-toolbar-groups').children().eq(-1));
+              }
+            }
+            // Move right between sibling groups.
+            else if (_.indexOf([39, 63235], event.keyCode) > -1) {
+              // Move to the right if the next group is not a placeholder.
+              if (!$siblings.eq(index + 1).hasClass('placeholder')) {
+                $group.insertAfter($container.children().eq(index + 1));
+              }
+              // Wrap group between rows.
+              else {
+                $container.closest('.ckeditor-row').next().find('.ckeditor-toolbar-groups').prepend($group);
+              }
+            }
 
-          // Put the button back if the registration failed.
-          // If the button was in a row, then it was in the active toolbar
-          // configuration. The button was probably placed in a new group, but
-          // that action was canceled.
-          if (!result && $originalGroup) {
-            $originalGroup.find('.ckeditor-buttons').append($button);
           }
-          // Otherwise refresh the sortables to acknowledge the new button
-          // positions.
-          else {
-            view.$el.find('.ui-sortable').sortable('refresh');
+          // Move groups between rows.
+          else if (_.indexOf(upDownKeys, event.keyCode) > -1) {
+            dir = (_.indexOf([38, 63232], event.keyCode) > -1) ? 'prev' : 'next';
+            $group.closest('.ckeditor-row')[dir]().find('.ckeditor-toolbar-groups').eq(0).prepend($group);
           }
-          // Refocus the target button so that the user can continue from a known
-          // place.
-          $target.trigger('focus');
-        });
 
-        event.preventDefault();
-        event.stopPropagation();
+          registerGroupMove(this, $group);
+          $group.trigger('focus');
+          event.preventDefault();
+          event.stopPropagation();
+        }
       }
-    },
+    }),
 
     /**
-     * Handles keypresses on a CKEditor configuration group.
-     *
-     * @param jQuery.Event event
+     * Backbone View for CKEditor toolbar configuration; aural UX (output only).
      */
-    onPressGroup: function (event) {
-      var upDownKeys = [
-        38, // Up arrow.
-        63232, // Safari up arrow.
-        40, // Down arrow.
-        63233 // Safari down arrow.
-      ];
-      var leftRightKeys = [
-        37, // Left arrow.
-        63234, // Safari left arrow.
-        39, // Right arrow.
-        63235 // Safari right arrow.
-      ];
-
-      // Respond to an enter key press.
-      if (event.keyCode === 13) {
-        var view = this;
-        // Open the group renaming dialog in the next evaluation cycle so that
-        // this event can be cancelled and the bubbling wiped out. Otherwise,
-        // Firefox has issues because the page focus is shifted to the dialog
-        // along with the keydown event.
-        window.setTimeout(function () {
-          openGroupNameDialog(view, $(event.currentTarget));
-        }, 0);
-        event.preventDefault();
-        event.stopPropagation();
-      }
+    ConfigurationAuralView: Backbone.View.extend({
+
+      events: {
+        'click .ckeditor-buttons a': 'announceButtonHelp',
+        'click .ckeditor-multiple-buttons a': 'announceSeparatorHelp',
+        'focus .ckeditor-button a': 'onFocus',
+        'focus .ckeditor-button-separator a': 'onFocus',
+        'focus .ckeditor-toolbar-group': 'onFocus'
+      },
 
-      // Respond to direction key presses.
-      if (_.indexOf(_.union(upDownKeys, leftRightKeys), event.keyCode) > -1) {
-        var $group = $(event.currentTarget);
-        var $container = $group.parent();
-        var $siblings = $container.children();
-        var index, dir;
-        // Move groups between sibling groups.
-        if (_.indexOf(leftRightKeys, event.keyCode) > -1) {
-          index = $siblings.index($group);
-          // Move left between sibling groups.
-          if ((_.indexOf([37, 63234], event.keyCode) > -1)) {
-            if (index > 0) {
-              $group.insertBefore($siblings.eq(index - 1));
-            }
-            // Wrap between rows. Insert the group before the placeholder group
-            // at the end of the previous row.
-            else {
-              $group.insertBefore($container.closest('.ckeditor-row').prev().find('.ckeditor-toolbar-groups').children().eq(-1));
-            }
-          }
-          // Move right between sibling groups.
-          else if (_.indexOf([39, 63235], event.keyCode) > -1) {
-            // Move to the right if the next group is not a placeholder.
-            if (!$siblings.eq(index + 1).hasClass('placeholder')) {
-              $group.insertAfter($container.children().eq(index + 1));
+      /**
+       * {@inheritdoc}
+       */
+      initialize: function () {
+        // Announce the button and group positions when the model is no longer
+        // dirty.
+        this.listenTo(this.model, 'change:isDirty', this.announceMove);
+      },
+
+      /**
+       * Calls announce on buttons and groups when their position is changed.
+       *
+       * @param Drupal.ckeditor.ConfigurationModel model
+       * @param Boolean isDirty
+       *   A model attribute that indicates if the changed toolbar configuration
+       *   has been stored or not.
+       */
+      announceMove: function (model, isDirty) {
+        // Announce the position of a button or group after the model has been
+        // updated.
+        if (!isDirty) {
+          var item = document.activeElement || null;
+          if (item) {
+            var $item = $(item);
+            if ($item.hasClass('ckeditor-toolbar-group')) {
+              this.announceButtonGroupPosition($item);
             }
-            // Wrap group between rows.
-            else {
-              $container.closest('.ckeditor-row').next().find('.ckeditor-toolbar-groups').prepend($group);
+            else if ($item.parent().hasClass('ckeditor-button')) {
+              this.announceButtonPosition($item.parent());
             }
           }
-
-        }
-        // Move groups between rows.
-        else if (_.indexOf(upDownKeys, event.keyCode) > -1) {
-          dir = (_.indexOf([38, 63232], event.keyCode) > -1) ? 'prev' : 'next';
-          $group.closest('.ckeditor-row')[dir]().find('.ckeditor-toolbar-groups').eq(0).prepend($group);
         }
+      },
 
-        registerGroupMove(this, $group);
-        $group.trigger('focus');
-        event.preventDefault();
+      /**
+       * Handles the focus event of elements in the active and available toolbars.
+       *
+       * @param jQuery.Event event
+       */
+      onFocus: function (event) {
         event.stopPropagation();
-      }
-    }
-  }),
-
-  /**
-   * Backbone View for CKEditor toolbar configuration; aural UX (output only).
-   */
-  ConfigurationAuralView: Backbone.View.extend({
-
-    events: {
-      'click .ckeditor-buttons a': 'announceButtonHelp',
-      'click .ckeditor-multiple-buttons a': 'announceSeparatorHelp',
-      'focus .ckeditor-button a': 'onFocus',
-      'focus .ckeditor-button-separator a': 'onFocus',
-      'focus .ckeditor-toolbar-group': 'onFocus'
-    },
-
-    /**
-     * {@inheritdoc}
-     */
-    initialize: function () {
-      // Announce the button and group positions when the model is no longer
-      // dirty.
-      this.listenTo(this.model, 'change:isDirty', this.announceMove);
-    },
 
-    /**
-     * Calls announce on buttons and groups when their position is changed.
-     *
-     * @param Drupal.ckeditor.ConfigurationModel model
-     * @param Boolean isDirty
-     *   A model attribute that indicates if the changed toolbar configuration
-     *   has been stored or not.
-     */
-    announceMove: function (model, isDirty) {
-      // Announce the position of a button or group after the model has been
-      // updated.
-      if (!isDirty) {
-        var item = document.activeElement || null;
-        if (item) {
-          var $item = $(item);
-          if ($item.hasClass('ckeditor-toolbar-group')) {
-            this.announceButtonGroupPosition($item);
-          }
-          else if ($item.parent().hasClass('ckeditor-button')) {
-            this.announceButtonPosition($item.parent());
-          }
+        var $originalTarget = $(event.target);
+        var $currentTarget = $(event.currentTarget);
+        var $parent = $currentTarget.parent();
+        if ($parent.hasClass('ckeditor-button') || $parent.hasClass('ckeditor-button-separator')) {
+          this.announceButtonPosition($currentTarget.parent());
         }
-      }
-    },
-
-    /**
-     * Handles the focus event of elements in the active and available toolbars.
-     *
-     * @param jQuery.Event event
-     */
-    onFocus: function (event) {
-      event.stopPropagation();
-
-      var $originalTarget = $(event.target);
-      var $currentTarget = $(event.currentTarget);
-      var $parent = $currentTarget.parent();
-      if ($parent.hasClass('ckeditor-button') || $parent.hasClass('ckeditor-button-separator')) {
-        this.announceButtonPosition($currentTarget.parent());
-      }
-      else if ($originalTarget.attr('role') !== 'button' && $currentTarget.hasClass('ckeditor-toolbar-group')) {
-        this.announceButtonGroupPosition($currentTarget);
-      }
-    },
-
-    /**
-     * Announces the current position of a button group.
-     *
-     * @param jQuery $group
-     *   A jQuery set that contains an li element that wraps a group of buttons.
-     */
-    announceButtonGroupPosition: function ($group) {
-      var $groups = $group.parent().children();
-      var $row = $group.closest('.ckeditor-row');
-      var $rows = $row.parent().children();
-      var position = $groups.index($group) + 1;
-      var positionCount = $groups.not('.placeholder').length;
-      var row = $rows.index($row) + 1;
-      var rowCount = $rows.not('.placeholder').length;
-      var text = Drupal.t('@groupName button group in position @position of @positionCount in row @row of @rowCount.', {
-        '@groupName': $group.attr('data-drupal-ckeditor-toolbar-group-name'),
-        '@position': position,
-        '@positionCount': positionCount,
-        '@row': row,
-        '@rowCount': rowCount
-      });
-      // If this position is the first in the last row then tell the user that
-      // pressing the down arrow key will create a new row.
-      if (position === 1 && row === rowCount) {
-        text += "\n";
-        text += Drupal.t("Press the down arrow key to create a new row.");
-      }
-      Drupal.announce(text, 'assertive');
-    },
-
-    /**
-     * Announces current button position.
-     *
-     * @param jQuery $button
-     *   A jQuery set that contains an li element that wraps a button.
-     */
-    announceButtonPosition: function ($button) {
-      var $row = $button.closest('.ckeditor-row');
-      var $rows = $row.parent().children();
-      var $buttons = $button.closest('.ckeditor-buttons').children();
-      var $group = $button.closest('.ckeditor-toolbar-group');
-      var $groups = $group.parent().children();
-      var groupPosition = $groups.index($group) + 1;
-      var groupPositionCount = $groups.not('.placeholder').length;
-      var position = $buttons.index($button) + 1;
-      var positionCount = $buttons.length;
-      var row = $rows.index($row) + 1;
-      var rowCount = $rows.not('.placeholder').length;
-      // The name of the button separator is 'button separator' and its type
-      // is 'separator', so we do not want to print the type of this item,
-      // otherwise the UA will speak 'button separator separator'.
-      var type = ($button.attr('data-drupal-ckeditor-type') === 'separator') ? '' : Drupal.t('button');
-      var text;
-      // The button is located in the available button set.
-      if ($button.closest('.ckeditor-toolbar-disabled').length > 0) {
-        text = Drupal.t('@name @type.', {
-          '@name': $button.children().attr('aria-label'),
-          '@type': type
-        });
-        text += "\n" + Drupal.t('Press the down arrow key to activate.');
+        else if ($originalTarget.attr('role') !== 'button' && $currentTarget.hasClass('ckeditor-toolbar-group')) {
+          this.announceButtonGroupPosition($currentTarget);
+        }
+      },
 
-        Drupal.announce(text, 'assertive');
-      }
-      // The button is in the active toolbar.
-      else if ($group.not('.placeholder').length === 1) {
-        text = Drupal.t('@name @type in position @position of @positionCount in @groupName button group in row @row of @rowCount.', {
-          '@name': $button.children().attr('aria-label'),
-          '@type': type,
+      /**
+       * Announces the current position of a button group.
+       *
+       * @param jQuery $group
+       *   A jQuery set that contains an li element that wraps a group of buttons.
+       */
+      announceButtonGroupPosition: function ($group) {
+        var $groups = $group.parent().children();
+        var $row = $group.closest('.ckeditor-row');
+        var $rows = $row.parent().children();
+        var position = $groups.index($group) + 1;
+        var positionCount = $groups.not('.placeholder').length;
+        var row = $rows.index($row) + 1;
+        var rowCount = $rows.not('.placeholder').length;
+        var text = Drupal.t('@groupName button group in position @position of @positionCount in row @row of @rowCount.', {
+          '@groupName': $group.attr('data-drupal-ckeditor-toolbar-group-name'),
           '@position': position,
           '@positionCount': positionCount,
-          '@groupName': $group.attr('data-drupal-ckeditor-toolbar-group-name'),
           '@row': row,
           '@rowCount': rowCount
         });
         // If this position is the first in the last row then tell the user that
         // pressing the down arrow key will create a new row.
-        if (groupPosition === 1 && position === 1 && row === rowCount) {
+        if (position === 1 && row === rowCount) {
           text += "\n";
-          text += Drupal.t("Press the down arrow key to create a new button group in a new row.");
-        }
-        // If this position is the last one in this row then tell the user that
-        // moving the button to the next group will create a new group.
-        if (groupPosition === groupPositionCount && position === positionCount) {
-          text += "\n";
-          text += Drupal.t("This is the last group. Move the button forward to create a new group.");
+          text += Drupal.t("Press the down arrow key to create a new row.");
         }
         Drupal.announce(text, 'assertive');
-      }
-    },
+      },
 
-    /**
-     * Provides help information when a button is clicked.
-     *
-     * @param jQuery.Event event
-     */
-    announceButtonHelp: function (event) {
-      var $link = $(event.currentTarget);
-      var $button = $link.parent();
-      var enabled = $button.closest('.ckeditor-toolbar-active').length > 0;
-      var message;
-
-      if (enabled) {
-        message = Drupal.t('The "@name" button is currently enabled.', {
-          '@name': $link.attr('aria-label')
-        });
-        message += "\n" + Drupal.t('Use the keyboard arrow keys to change the position of this button.');
-        message += "\n" + Drupal.t('Press the up arrow key on the top row to disable the button.');
-      }
-      else {
-        message = Drupal.t('The "@name" button is currently disabled.', {
-          '@name': $link.attr('aria-label')
-        });
-        message += "\n" + Drupal.t('Use the down arrow key to move this button into the active toolbar.');
-      }
-      Drupal.announce(message);
-      event.preventDefault();
-    },
+      /**
+       * Announces current button position.
+       *
+       * @param jQuery $button
+       *   A jQuery set that contains an li element that wraps a button.
+       */
+      announceButtonPosition: function ($button) {
+        var $row = $button.closest('.ckeditor-row');
+        var $rows = $row.parent().children();
+        var $buttons = $button.closest('.ckeditor-buttons').children();
+        var $group = $button.closest('.ckeditor-toolbar-group');
+        var $groups = $group.parent().children();
+        var groupPosition = $groups.index($group) + 1;
+        var groupPositionCount = $groups.not('.placeholder').length;
+        var position = $buttons.index($button) + 1;
+        var positionCount = $buttons.length;
+        var row = $rows.index($row) + 1;
+        var rowCount = $rows.not('.placeholder').length;
+        // The name of the button separator is 'button separator' and its type
+        // is 'separator', so we do not want to print the type of this item,
+        // otherwise the UA will speak 'button separator separator'.
+        var type = ($button.attr('data-drupal-ckeditor-type') === 'separator') ? '' : Drupal.t('button');
+        var text;
+        // The button is located in the available button set.
+        if ($button.closest('.ckeditor-toolbar-disabled').length > 0) {
+          text = Drupal.t('@name @type.', {
+            '@name': $button.children().attr('aria-label'),
+            '@type': type
+          });
+          text += "\n" + Drupal.t('Press the down arrow key to activate.');
 
-    /**
-     * Provides help information when a separator is clicked.
-     *
-     * @param jQuery.Event event
-     */
-    announceSeparatorHelp: function (event) {
-      var $link = $(event.currentTarget);
-      var $button = $link.parent();
-      var enabled = $button.closest('.ckeditor-toolbar-active').length > 0;
-      var message;
-
-      if (enabled) {
-        message = Drupal.t('This @name is currently enabled.', {
-          '@name': $link.attr('aria-label')
-        });
-        message += "\n" + Drupal.t('Use the keyboard arrow keys to change the position of this separator.');
-      }
-      else {
-        message = Drupal.t('Separators are used to visually split individual buttons.');
-        message += "\n" + Drupal.t('This @name is currently disabled.', {
-          '@name': $link.attr('aria-label')
-        });
-        message += "\n" + Drupal.t('Use the down arrow key to move this separator into the active toolbar.');
-        message += "\n" + Drupal.t('You may add multiple separators to each button group.');
-      }
-      Drupal.announce(message);
-      event.preventDefault();
-    }
-  })
-};
+          Drupal.announce(text, 'assertive');
+        }
+        // The button is in the active toolbar.
+        else if ($group.not('.placeholder').length === 1) {
+          text = Drupal.t('@name @type in position @position of @positionCount in @groupName button group in row @row of @rowCount.', {
+            '@name': $button.children().attr('aria-label'),
+            '@type': type,
+            '@position': position,
+            '@positionCount': positionCount,
+            '@groupName': $group.attr('data-drupal-ckeditor-toolbar-group-name'),
+            '@row': row,
+            '@rowCount': rowCount
+          });
+          // If this position is the first in the last row then tell the user that
+          // pressing the down arrow key will create a new row.
+          if (groupPosition === 1 && position === 1 && row === rowCount) {
+            text += "\n";
+            text += Drupal.t("Press the down arrow key to create a new button group in a new row.");
+          }
+          // If this position is the last one in this row then tell the user that
+          // moving the button to the next group will create a new group.
+          if (groupPosition === groupPositionCount && position === positionCount) {
+            text += "\n";
+            text += Drupal.t("This is the last group. Move the button forward to create a new group.");
+          }
+          Drupal.announce(text, 'assertive');
+        }
+      },
 
-/**
- * Translates a change in CKEditor config DOM structure into the config model.
- *
- * If the button is moved within an existing group, the DOM structure is simply
- * translated to a configuration model. If the button is moved into a new group
- * placeholder, then a process is launched to name that group before the button
- * move is translated into configuration.
- *
- * @param Backbone.View view
- *   The Backbone View that invoked this function.
- * @param jQuery $button
- *   A jQuery set that contains an li element that wraps a button element.
- * @param function callback
- *   A callback to invoke after the button group naming modal dialog has been
- *   closed.
- */
-function registerButtonMove (view, $button, callback) {
-  var $group = $button.closest('.ckeditor-toolbar-group');
+      /**
+       * Provides help information when a button is clicked.
+       *
+       * @param jQuery.Event event
+       */
+      announceButtonHelp: function (event) {
+        var $link = $(event.currentTarget);
+        var $button = $link.parent();
+        var enabled = $button.closest('.ckeditor-toolbar-active').length > 0;
+        var message;
+
+        if (enabled) {
+          message = Drupal.t('The "@name" button is currently enabled.', {
+            '@name': $link.attr('aria-label')
+          });
+          message += "\n" + Drupal.t('Use the keyboard arrow keys to change the position of this button.');
+          message += "\n" + Drupal.t('Press the up arrow key on the top row to disable the button.');
+        }
+        else {
+          message = Drupal.t('The "@name" button is currently disabled.', {
+            '@name': $link.attr('aria-label')
+          });
+          message += "\n" + Drupal.t('Use the down arrow key to move this button into the active toolbar.');
+        }
+        Drupal.announce(message);
+        event.preventDefault();
+      },
 
-  // If dropped in a placeholder button group, the user must name it.
-  if ($group.hasClass('placeholder')) {
+      /**
+       * Provides help information when a separator is clicked.
+       *
+       * @param jQuery.Event event
+       */
+      announceSeparatorHelp: function (event) {
+        var $link = $(event.currentTarget);
+        var $button = $link.parent();
+        var enabled = $button.closest('.ckeditor-toolbar-active').length > 0;
+        var message;
+
+        if (enabled) {
+          message = Drupal.t('This @name is currently enabled.', {
+            '@name': $link.attr('aria-label')
+          });
+          message += "\n" + Drupal.t('Use the keyboard arrow keys to change the position of this separator.');
+        }
+        else {
+          message = Drupal.t('Separators are used to visually split individual buttons.');
+          message += "\n" + Drupal.t('This @name is currently disabled.', {
+            '@name': $link.attr('aria-label')
+          });
+          message += "\n" + Drupal.t('Use the down arrow key to move this separator into the active toolbar.');
+          message += "\n" + Drupal.t('You may add multiple separators to each button group.');
+        }
+        Drupal.announce(message);
+        event.preventDefault();
+      }
+    })
+  };
 
-    if (view.isProcessing) {
-      event.stopPropagation();
-      return;
-    }
-    view.isProcessing = true;
+  /**
+   * Translates a change in CKEditor config DOM structure into the config model.
+   *
+   * If the button is moved within an existing group, the DOM structure is simply
+   * translated to a configuration model. If the button is moved into a new group
+   * placeholder, then a process is launched to name that group before the button
+   * move is translated into configuration.
+   *
+   * @param Backbone.View view
+   *   The Backbone View that invoked this function.
+   * @param jQuery $button
+   *   A jQuery set that contains an li element that wraps a button element.
+   * @param function callback
+   *   A callback to invoke after the button group naming modal dialog has been
+   *   closed.
+   */
+  function registerButtonMove(view, $button, callback) {
+    var $group = $button.closest('.ckeditor-toolbar-group');
 
-    openGroupNameDialog(view, $group, callback);
-  }
-  else {
-    view.model.set('isDirty', true);
-    callback(true);
-  }
-}
+    // If dropped in a placeholder button group, the user must name it.
+    if ($group.hasClass('placeholder')) {
 
-/**
- * Translates a change in CKEditor config DOM structure into the config model.
- *
- * Each row has a placeholder group at the end of the row. A user may not move
- * an existing button group past the placeholder group at the end of a row.
- *
- * @param Backbone.View view
- *   The Backbone View that invoked this function.
- * @param jQuery $group
- *   A jQuery set that contains an li element that wraps a group of buttons.
- */
-function registerGroupMove (view, $group) {
-  // Remove placeholder classes if necessary.
-  var $row = $group.closest('.ckeditor-row');
-  if ($row.hasClass('placeholder')) {
-    $row.removeClass('placeholder');
-  }
-  // If there are any rows with just a placeholder group, mark the row as a
-  // placeholder.
-  $row.parent().children().each(function () {
-    var $row = $(this);
-    if ($row.find('.ckeditor-toolbar-group').not('.placeholder').length === 0) {
-      $row.addClass('placeholder');
-    }
-  });
-  view.model.set('isDirty', true);
-}
+      if (view.isProcessing) {
+        event.stopPropagation();
+        return;
+      }
+      view.isProcessing = true;
 
-/**
- * Opens a Drupal dialog with a form for changing the title of a button group.
- *
- * @param Backbone.View view
- *   The Backbone View that invoked this function.
- * @param jQuery $group
- *   A jQuery set that contains an li element that wraps a group of buttons.
- * @param function callback
- *   A callback to invoke after the button group naming modal dialog has been
- *   closed.
- */
-function openGroupNameDialog (view, $group, callback) {
-  callback = callback || function () {};
+      openGroupNameDialog(view, $group, callback);
+    }
+    else {
+      view.model.set('isDirty', true);
+      callback(true);
+    }
+  }
 
   /**
-   * Validates the string provided as a button group title.
+   * Translates a change in CKEditor config DOM structure into the config model.
+   *
+   * Each row has a placeholder group at the end of the row. A user may not move
+   * an existing button group past the placeholder group at the end of a row.
    *
-   * @param DOM form
-   *   The form DOM element that contains the input with the new button group
-   *   title string.
-   * @return Boolean
-   *   Returns true when an error exists, otherwise returns false.
+   * @param Backbone.View view
+   *   The Backbone View that invoked this function.
+   * @param jQuery $group
+   *   A jQuery set that contains an li element that wraps a group of buttons.
    */
-  function validateForm (form) {
-    if (form.elements[0].value.length === 0) {
-      var $form = $(form);
-      if (!$form.hasClass('errors')) {
-        $form
-          .addClass('errors')
-          .find('input')
-          .addClass('error')
-          .attr('aria-invalid', 'true');
-        $('<div class=\"description\" >' + Drupal.t('Please provide a name for the button group.') + '</div>').insertAfter(form.elements[0]);
-      }
-      return true;
+  function registerGroupMove(view, $group) {
+    // Remove placeholder classes if necessary.
+    var $row = $group.closest('.ckeditor-row');
+    if ($row.hasClass('placeholder')) {
+      $row.removeClass('placeholder');
     }
-    return false;
+    // If there are any rows with just a placeholder group, mark the row as a
+    // placeholder.
+    $row.parent().children().each(function () {
+      var $row = $(this);
+      if ($row.find('.ckeditor-toolbar-group').not('.placeholder').length === 0) {
+        $row.addClass('placeholder');
+      }
+    });
+    view.model.set('isDirty', true);
   }
 
   /**
-   * Attempts to close the dialog; Validates user input.
+   * Opens a Drupal dialog with a form for changing the title of a button group.
    *
-   * @param String action
-   *   The dialog action chosen by the user: 'apply' or 'cancel'.
-   * @param DOM form
-   *   The form DOM element that contains the input with the new button group
-   *   title string.
+   * @param Backbone.View view
+   *   The Backbone View that invoked this function.
+   * @param jQuery $group
+   *   A jQuery set that contains an li element that wraps a group of buttons.
+   * @param function callback
+   *   A callback to invoke after the button group naming modal dialog has been
+   *   closed.
    */
-  function closeDialog (action, form) {
+  function openGroupNameDialog(view, $group, callback) {
+    callback = callback || function () {};
 
     /**
-     * Closes the dialog when the user cancels or supplies valid data.
+     * Validates the string provided as a button group title.
+     *
+     * @param DOM form
+     *   The form DOM element that contains the input with the new button group
+     *   title string.
+     * @return Boolean
+     *   Returns true when an error exists, otherwise returns false.
      */
-    function shutdown () {
-      dialog.close(action);
-
-      // The processing marker can be deleted since the dialog has been closed.
-      delete view.isProcessing;
+    function validateForm(form) {
+      if (form.elements[0].value.length === 0) {
+        var $form = $(form);
+        if (!$form.hasClass('errors')) {
+          $form
+            .addClass('errors')
+            .find('input')
+            .addClass('error')
+            .attr('aria-invalid', 'true');
+          $('<div class=\"description\" >' + Drupal.t('Please provide a name for the button group.') + '</div>').insertAfter(form.elements[0]);
+        }
+        return true;
+      }
+      return false;
     }
 
     /**
-     * Applies a string as the name of a CKEditor button group.
+     * Attempts to close the dialog; Validates user input.
      *
-     * @param jQuery $group
-     *   A jQuery set that contains an li element that wraps a group of buttons.
-     * @param String name
-     *   The new name of the CKEditor button group.
+     * @param String action
+     *   The dialog action chosen by the user: 'apply' or 'cancel'.
+     * @param DOM form
+     *   The form DOM element that contains the input with the new button group
+     *   title string.
      */
-    function namePlaceholderGroup ($group, name) {
-      // If it's currently still a placeholder, then that means we're creating
-      // a new group, and we must do some extra work.
-      if ($group.hasClass('placeholder')) {
-        // Remove all whitespace from the name, lowercase it and ensure
-        // HTML-safe encoding, then use this as the group ID for CKEditor
-        // configuration UI accessibility purposes only.
-        var groupID = 'ckeditor-toolbar-group-aria-label-for-' + Drupal.checkPlain(name.toLowerCase().replace(/ /g,'-'));
+    function closeDialog(action, form) {
+
+      /**
+       * Closes the dialog when the user cancels or supplies valid data.
+       */
+      function shutdown() {
+        dialog.close(action);
+
+        // The processing marker can be deleted since the dialog has been closed.
+        delete view.isProcessing;
+      }
+
+      /**
+       * Applies a string as the name of a CKEditor button group.
+       *
+       * @param jQuery $group
+       *   A jQuery set that contains an li element that wraps a group of buttons.
+       * @param String name
+       *   The new name of the CKEditor button group.
+       */
+      function namePlaceholderGroup($group, name) {
+        // If it's currently still a placeholder, then that means we're creating
+        // a new group, and we must do some extra work.
+        if ($group.hasClass('placeholder')) {
+          // Remove all whitespace from the name, lowercase it and ensure
+          // HTML-safe encoding, then use this as the group ID for CKEditor
+          // configuration UI accessibility purposes only.
+          var groupID = 'ckeditor-toolbar-group-aria-label-for-' + Drupal.checkPlain(name.toLowerCase().replace(/ /g, '-'));
+          $group
+            // Update the group container.
+            .removeAttr('aria-label')
+            .attr('data-drupal-ckeditor-type', 'group')
+            .attr('tabindex', 0)
+            // Update the group heading.
+            .children('.ckeditor-toolbar-group-name')
+            .attr('id', groupID)
+            .end()
+            // Update the group items.
+            .children('.ckeditor-toolbar-group-buttons')
+            .attr('aria-labelledby', groupID);
+        }
+
         $group
-          // Update the group container.
-          .removeAttr('aria-label')
-          .attr('data-drupal-ckeditor-type', 'group')
-          .attr('tabindex', 0)
-          // Update the group heading.
+          .attr('data-drupal-ckeditor-toolbar-group-name', name)
           .children('.ckeditor-toolbar-group-name')
-          .attr('id', groupID)
-          .end()
-          // Update the group items.
-          .children('.ckeditor-toolbar-group-buttons')
-          .attr('aria-labelledby', groupID);
+          .text(name);
       }
 
-      $group
-        .attr('data-drupal-ckeditor-toolbar-group-name', name)
-        .children('.ckeditor-toolbar-group-name')
-        .text(name);
-    }
-
-    // Invoke a user-provided callback and indicate failure.
-    if (action === 'cancel') {
-      shutdown();
-      callback(false, $group);
-      return;
-    }
+      // Invoke a user-provided callback and indicate failure.
+      if (action === 'cancel') {
+        shutdown();
+        callback(false, $group);
+        return;
+      }
 
-    // Validate that a group name was provided.
-    if (form && validateForm(form)) {
-      return;
-    }
+      // Validate that a group name was provided.
+      if (form && validateForm(form)) {
+        return;
+      }
 
-    // React to application of a valid group name.
-    if (action === 'apply') {
-      shutdown();
-      // Apply the provided name to the button group label.
-      namePlaceholderGroup($group, Drupal.checkPlain(form.elements[0].value));
-      // Remove placeholder classes so that new placeholders will be
-      // inserted.
-      $group.closest('.ckeditor-row.placeholder').addBack().removeClass('placeholder');
+      // React to application of a valid group name.
+      if (action === 'apply') {
+        shutdown();
+        // Apply the provided name to the button group label.
+        namePlaceholderGroup($group, Drupal.checkPlain(form.elements[0].value));
+        // Remove placeholder classes so that new placeholders will be
+        // inserted.
+        $group.closest('.ckeditor-row.placeholder').addBack().removeClass('placeholder');
 
-      // Invoke a user-provided callback and indicate success.
-      callback(true, $group);
+        // Invoke a user-provided callback and indicate success.
+        callback(true, $group);
 
-      // Signal that the active toolbar DOM structure has changed.
-      view.model.set('isDirty', true);
+        // Signal that the active toolbar DOM structure has changed.
+        view.model.set('isDirty', true);
+      }
     }
-  }
 
-  // Create a Drupal dialog that will get a button group name from the user.
-  var $ckeditorButtonGroupNameForm = $(Drupal.theme('ckeditorButtonGroupNameForm'));
-  var dialog = Drupal.dialog($ckeditorButtonGroupNameForm.get(0), {
-    title: Drupal.t('Button group name'),
-    dialogClass: 'ckeditor-name-toolbar-group',
-    resizable: false,
-    buttons: [
-      {
-        text: Drupal.t('Apply'),
-        click: function () {
-          closeDialog('apply', this);
+    // Create a Drupal dialog that will get a button group name from the user.
+    var $ckeditorButtonGroupNameForm = $(Drupal.theme('ckeditorButtonGroupNameForm'));
+    var dialog = Drupal.dialog($ckeditorButtonGroupNameForm.get(0), {
+      title: Drupal.t('Button group name'),
+      dialogClass: 'ckeditor-name-toolbar-group',
+      resizable: false,
+      buttons: [
+        {
+          text: Drupal.t('Apply'),
+          click: function () {
+            closeDialog('apply', this);
+          },
+          'class': 'button-primary button'
         },
-        'class': 'button-primary button'
-      },
-      {
-        text: Drupal.t('Cancel'),
-        click: function () {
-          closeDialog('cancel');
-        },
-        'class': 'button'
-      }
-    ],
-    open: function () {
-      var form = this;
-      var $form = $(this);
-      var $widget = $form.parent();
-      $widget.find('.ui-dialog-titlebar-close').remove();
-      // Set a click handler on the input and button in the form.
-      $widget.on('keypress.ckeditor', 'input, button', function (event) {
-        // React to enter key press.
-        if (event.keyCode === 13) {
-          var $target = $(event.currentTarget);
-          var data = $target.data('ui-button');
-          var action = 'apply';
-          // Assume 'apply', but take into account that the user might have
-          // pressed the enter key on the dialog buttons.
-          if (data && data.options && data.options.label) {
-            action = data.options.label.toLowerCase();
-          }
-          closeDialog(action, form);
-          event.stopPropagation();
-          event.stopImmediatePropagation();
-          event.preventDefault();
+        {
+          text: Drupal.t('Cancel'),
+          click: function () {
+            closeDialog('cancel');
+          },
+          'class': 'button'
         }
-      });
-      // Announce to the user that a modal dialog is open.
-      var text = Drupal.t('Editing the name of the new button group in a dialog.');
-      if ($group.attr('data-drupal-ckeditor-toolbar-group-name') !== undefined) {
-        text = Drupal.t('Editing the name of the "@groupName" button group in a dialog.', {
-          '@groupName': $group.attr('data-drupal-ckeditor-toolbar-group-name')
+      ],
+      open: function () {
+        var form = this;
+        var $form = $(this);
+        var $widget = $form.parent();
+        $widget.find('.ui-dialog-titlebar-close').remove();
+        // Set a click handler on the input and button in the form.
+        $widget.on('keypress.ckeditor', 'input, button', function (event) {
+          // React to enter key press.
+          if (event.keyCode === 13) {
+            var $target = $(event.currentTarget);
+            var data = $target.data('ui-button');
+            var action = 'apply';
+            // Assume 'apply', but take into account that the user might have
+            // pressed the enter key on the dialog buttons.
+            if (data && data.options && data.options.label) {
+              action = data.options.label.toLowerCase();
+            }
+            closeDialog(action, form);
+            event.stopPropagation();
+            event.stopImmediatePropagation();
+            event.preventDefault();
+          }
         });
+        // Announce to the user that a modal dialog is open.
+        var text = Drupal.t('Editing the name of the new button group in a dialog.');
+        if ($group.attr('data-drupal-ckeditor-toolbar-group-name') !== undefined) {
+          text = Drupal.t('Editing the name of the "@groupName" button group in a dialog.', {
+            '@groupName': $group.attr('data-drupal-ckeditor-toolbar-group-name')
+          });
+        }
+        Drupal.announce(text);
+      },
+      close: function (event) {
+        // Automatically destroy the DOM element that was used for the dialog.
+        $(event.target).remove();
       }
-      Drupal.announce(text);
-    },
-    close: function (event) {
-      // Automatically destroy the DOM element that was used for the dialog.
-      $(event.target).remove();
-    }
-  });
-  // A modal dialog is used because the user must provide a button group name
-  // or cancel the button placement before taking any other action.
-  dialog.showModal();
-
-  $(document.querySelector('.ckeditor-name-toolbar-group').querySelector('input'))
-    // When editing, set the "group name" input in the form to the current value.
-    .attr('value', $group.attr('data-drupal-ckeditor-toolbar-group-name'))
-    // Focus on the "group name" input in the form.
-    .trigger('focus');
-}
+    });
+    // A modal dialog is used because the user must provide a button group name
+    // or cancel the button placement before taking any other action.
+    dialog.showModal();
+
+    $(document.querySelector('.ckeditor-name-toolbar-group').querySelector('input'))
+      // When editing, set the "group name" input in the form to the current value.
+      .attr('value', $group.attr('data-drupal-ckeditor-toolbar-group-name'))
+      // Focus on the "group name" input in the form.
+      .trigger('focus');
+  }
 
-/**
- * Automatically shows/hides settings of buttons-only CKEditor plugins.
- */
-Drupal.behaviors.ckeditorAdminButtonPluginSettings = {
-  attach: function (context) {
-    var $context = $(context);
-    var $ckeditorPluginSettings = $context.find('#ckeditor-plugin-settings').once('ckeditor-plugin-settings');
-    if ($ckeditorPluginSettings.length) {
-      // Hide all button-dependent plugin settings initially.
-      $ckeditorPluginSettings.find('[data-ckeditor-buttons]').each(function () {
-        var $this = $(this);
-        if ($this.data('verticalTab')) {
-          $this.data('verticalTab').tabHide();
-        }
-        else {
-          // On very narrow viewports, Vertical Tabs are disabled.
-          $this.hide();
-        }
-        $this.data('ckeditorButtonPluginSettingsActiveButtons', []);
-      });
-
-      // Whenever a button is added or removed, check if we should show or hide
-      // the corresponding plugin settings. (Note that upon initialization, each
-      // button that already is part of the toolbar still is considered "added",
-      // hence it also works correctly for buttons that were added previously.)
-      $context
-        .find('.ckeditor-toolbar-active')
-        .off('CKEditorToolbarChanged.ckeditorAdminPluginSettings')
-        .on('CKEditorToolbarChanged.ckeditorAdminPluginSettings', function (event, action, button) {
-          var $pluginSettings = $ckeditorPluginSettings
-            .find('[data-ckeditor-buttons~=' + button + ']');
-
-          // No settings for this button.
-          if ($pluginSettings.length === 0) {
-            return;
+  /**
+   * Automatically shows/hides settings of buttons-only CKEditor plugins.
+   */
+  Drupal.behaviors.ckeditorAdminButtonPluginSettings = {
+    attach: function (context) {
+      var $context = $(context);
+      var $ckeditorPluginSettings = $context.find('#ckeditor-plugin-settings').once('ckeditor-plugin-settings');
+      if ($ckeditorPluginSettings.length) {
+        // Hide all button-dependent plugin settings initially.
+        $ckeditorPluginSettings.find('[data-ckeditor-buttons]').each(function () {
+          var $this = $(this);
+          if ($this.data('verticalTab')) {
+            $this.data('verticalTab').tabHide();
+          }
+          else {
+            // On very narrow viewports, Vertical Tabs are disabled.
+            $this.hide();
           }
+          $this.data('ckeditorButtonPluginSettingsActiveButtons', []);
+        });
 
-          var verticalTab = $pluginSettings.data('verticalTab');
-          var activeButtons = $pluginSettings.data('ckeditorButtonPluginSettingsActiveButtons');
-          if (action === 'added') {
-            activeButtons.push(button);
-            // Show this plugin's settings if >=1 of its buttons are active.
-            if (verticalTab) {
-              verticalTab.tabShow();
-            }
-            else {
-              // On very narrow viewports, Vertical Tabs remain fieldsets.
-              $pluginSettings.show();
+        // Whenever a button is added or removed, check if we should show or hide
+        // the corresponding plugin settings. (Note that upon initialization, each
+        // button that already is part of the toolbar still is considered "added",
+        // hence it also works correctly for buttons that were added previously.)
+        $context
+          .find('.ckeditor-toolbar-active')
+          .off('CKEditorToolbarChanged.ckeditorAdminPluginSettings')
+          .on('CKEditorToolbarChanged.ckeditorAdminPluginSettings', function (event, action, button) {
+            var $pluginSettings = $ckeditorPluginSettings
+              .find('[data-ckeditor-buttons~=' + button + ']');
+
+            // No settings for this button.
+            if ($pluginSettings.length === 0) {
+              return;
             }
 
-          }
-          else {
-            // Remove this button from the list of active buttons.
-            activeButtons.splice(activeButtons.indexOf(button), 1);
-            // Show this plugin's settings 0 of its buttons are active.
-            if (activeButtons.length === 0) {
+            var verticalTab = $pluginSettings.data('verticalTab');
+            var activeButtons = $pluginSettings.data('ckeditorButtonPluginSettingsActiveButtons');
+            if (action === 'added') {
+              activeButtons.push(button);
+              // Show this plugin's settings if >=1 of its buttons are active.
               if (verticalTab) {
-                verticalTab.tabHide();
+                verticalTab.tabShow();
               }
               else {
-                // On very narrow viewports, Vertical Tabs are disabled.
-                $pluginSettings.hide();
+                // On very narrow viewports, Vertical Tabs remain fieldsets.
+                $pluginSettings.show();
               }
+
             }
-          }
-          $pluginSettings.data('ckeditorButtonPluginSettingsActiveButtons', activeButtons);
-        });
+            else {
+              // Remove this button from the list of active buttons.
+              activeButtons.splice(activeButtons.indexOf(button), 1);
+              // Show this plugin's settings 0 of its buttons are active.
+              if (activeButtons.length === 0) {
+                if (verticalTab) {
+                  verticalTab.tabHide();
+                }
+                else {
+                  // On very narrow viewports, Vertical Tabs are disabled.
+                  $pluginSettings.hide();
+                }
+              }
+            }
+            $pluginSettings.data('ckeditorButtonPluginSettingsActiveButtons', activeButtons);
+          });
+      }
     }
-  }
-};
+  };
 
-/**
- * Themes a blank CKEditor row.
- *
- * @return String
- */
-Drupal.theme.ckeditorRow = function () {
-  return '<li class="ckeditor-row placeholder" role="group"><ul class="ckeditor-toolbar-groups clearfix"></ul></li>';
-};
+  /**
+   * Themes a blank CKEditor row.
+   *
+   * @return String
+   */
+  Drupal.theme.ckeditorRow = function () {
+    return '<li class="ckeditor-row placeholder" role="group"><ul class="ckeditor-toolbar-groups clearfix"></ul></li>';
+  };
 
-/**
- * Themes a blank CKEditor button group.
- *
- * @return String
- */
-Drupal.theme.ckeditorToolbarGroup = function () {
-  var group = '';
-  group += '<li class="ckeditor-toolbar-group placeholder" role="presentation" aria-label="' + Drupal.t('Place a button to create a new button group.') + '">';
-  group += '<h3 class="ckeditor-toolbar-group-name">' + Drupal.t('New group') + '</h3>';
-  group += '<ul class="ckeditor-buttons ckeditor-toolbar-group-buttons" role="toolbar" data-drupal-ckeditor-button-sorting="target"></ul>';
-  group += '</li>';
-  return group;
-};
+  /**
+   * Themes a blank CKEditor button group.
+   *
+   * @return String
+   */
+  Drupal.theme.ckeditorToolbarGroup = function () {
+    var group = '';
+    group += '<li class="ckeditor-toolbar-group placeholder" role="presentation" aria-label="' + Drupal.t('Place a button to create a new button group.') + '">';
+    group += '<h3 class="ckeditor-toolbar-group-name">' + Drupal.t('New group') + '</h3>';
+    group += '<ul class="ckeditor-buttons ckeditor-toolbar-group-buttons" role="toolbar" data-drupal-ckeditor-button-sorting="target"></ul>';
+    group += '</li>';
+    return group;
+  };
 
-/**
- * Themes a form for changing the title of a CKEditor button group.
- *
- * @return String
- */
-Drupal.theme.ckeditorButtonGroupNameForm = function () {
-  return '<form><input name="group-name" required="required"></form>';
-};
+  /**
+   * Themes a form for changing the title of a CKEditor button group.
+   *
+   * @return String
+   */
+  Drupal.theme.ckeditorButtonGroupNameForm = function () {
+    return '<form><input name="group-name" required="required"></form>';
+  };
 
-/**
- * Themes a button that will toggle the button group names in active config.
- *
- * @return String
- */
-Drupal.theme.ckeditorButtonGroupNamesToggle = function () {
-  return '<a class="ckeditor-groupnames-toggle" role="button" aria-pressed="false"></a>';
-};
+  /**
+   * Themes a button that will toggle the button group names in active config.
+   *
+   * @return String
+   */
+  Drupal.theme.ckeditorButtonGroupNamesToggle = function () {
+    return '<a class="ckeditor-groupnames-toggle" role="button" aria-pressed="false"></a>';
+  };
 
-/**
- * Themes a button that will prompt the user to name a new button group.
- *
- * @return String
- */
-Drupal.theme.ckeditorNewButtonGroup = function () {
-  return '<li class="ckeditor-add-new-group"><button role="button" aria-label="' + Drupal.t('Add a CKEditor button group to the end of this row.') + '">' + Drupal.t('Add group') + '</button></li>';
-};
+  /**
+   * Themes a button that will prompt the user to name a new button group.
+   *
+   * @return String
+   */
+  Drupal.theme.ckeditorNewButtonGroup = function () {
+    return '<li class="ckeditor-add-new-group"><button role="button" aria-label="' + Drupal.t('Add a CKEditor button group to the end of this row.') + '">' + Drupal.t('Add group') + '</button></li>';
+  };
 
 })(jQuery, Drupal, _, CKEDITOR);
diff --git a/core/modules/ckeditor/js/ckeditor.drupalimage.admin.js b/core/modules/ckeditor/js/ckeditor.drupalimage.admin.js
index 91ead76..1315b24 100644
--- a/core/modules/ckeditor/js/ckeditor.drupalimage.admin.js
+++ b/core/modules/ckeditor/js/ckeditor.drupalimage.admin.js
@@ -1,35 +1,35 @@
 (function ($, Drupal, drupalSettings) {
 
-"use strict";
+  "use strict";
 
-/**
- * Provides the summary for the "drupalimage" plugin settings vertical tab.
- */
-Drupal.behaviors.ckeditorDrupalImageSettingsSummary = {
-  attach: function () {
-    $('#edit-editor-settings-plugins-drupalimage').drupalSetSummary(function (context) {
-      var root = 'input[name="editor[settings][plugins][drupalimage][image_upload]';
-      var $status = $(root + '[status]"]');
-      var $maxFileSize = $(root + '[max_size]"]');
-      var $maxWidth = $(root + '[max_dimensions][width]"]');
-      var $maxHeight = $(root + '[max_dimensions][height]"]');
-      var $scheme = $(root + '[scheme]"]:checked');
+  /**
+   * Provides the summary for the "drupalimage" plugin settings vertical tab.
+   */
+  Drupal.behaviors.ckeditorDrupalImageSettingsSummary = {
+    attach: function () {
+      $('#edit-editor-settings-plugins-drupalimage').drupalSetSummary(function (context) {
+        var root = 'input[name="editor[settings][plugins][drupalimage][image_upload]';
+        var $status = $(root + '[status]"]');
+        var $maxFileSize = $(root + '[max_size]"]');
+        var $maxWidth = $(root + '[max_dimensions][width]"]');
+        var $maxHeight = $(root + '[max_dimensions][height]"]');
+        var $scheme = $(root + '[scheme]"]:checked');
 
-      var maxFileSize = $maxFileSize.val() ? $maxFileSize.val() : $maxFileSize.attr('placeholder');
-      var maxDimensions = ($maxWidth.val() && $maxHeight.val()) ? '(' + $maxWidth.val() + 'x' + $maxHeight.val() + ')' : '';
+        var maxFileSize = $maxFileSize.val() ? $maxFileSize.val() : $maxFileSize.attr('placeholder');
+        var maxDimensions = ($maxWidth.val() && $maxHeight.val()) ? '(' + $maxWidth.val() + 'x' + $maxHeight.val() + ')' : '';
 
-      if (!$status.is(':checked')) {
-        return Drupal.t('Uploads disabled');
-      }
+        if (!$status.is(':checked')) {
+          return Drupal.t('Uploads disabled');
+        }
 
-      var output = '';
-      output += Drupal.t('Uploads enabled, max size: @size @dimensions', { '@size': maxFileSize, '@dimensions': maxDimensions });
-      if ($scheme.length) {
-        output += '<br />' + $scheme.attr('data-label');
-      }
-      return output;
-    });
-  }
-};
+        var output = '';
+        output += Drupal.t('Uploads enabled, max size: @size @dimensions', { '@size': maxFileSize, '@dimensions': maxDimensions });
+        if ($scheme.length) {
+          output += '<br />' + $scheme.attr('data-label');
+        }
+        return output;
+      });
+    }
+  };
 
 })(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/ckeditor/js/ckeditor.js b/core/modules/ckeditor/js/ckeditor.js
index d6baf17..2831992 100644
--- a/core/modules/ckeditor/js/ckeditor.js
+++ b/core/modules/ckeditor/js/ckeditor.js
@@ -1,272 +1,272 @@
 (function (Drupal, debounce, CKEDITOR, $) {
 
-"use strict";
+  "use strict";
 
-Drupal.editors.ckeditor = {
+  Drupal.editors.ckeditor = {
 
-  attach: function (element, format) {
-    this._loadExternalPlugins(format);
-    this._ACF_HACK_to_support_blacklisted_attributes(element, format);
-    // Also pass settings that are Drupal-specific.
-    format.editorSettings.drupal = {
-      format: format.format
-    };
-    return !!CKEDITOR.replace(element, format.editorSettings);
-  },
+    attach: function (element, format) {
+      this._loadExternalPlugins(format);
+      this._ACF_HACK_to_support_blacklisted_attributes(element, format);
+      // Also pass settings that are Drupal-specific.
+      format.editorSettings.drupal = {
+        format: format.format
+      };
+      return !!CKEDITOR.replace(element, format.editorSettings);
+    },
 
-  detach: function (element, format, trigger) {
-    var editor = CKEDITOR.dom.element.get(element).getEditor();
-    if (editor) {
-      if (trigger === 'serialize') {
-        editor.updateElement();
-      }
-      else {
-        editor.destroy();
-        element.removeAttribute('contentEditable');
+    detach: function (element, format, trigger) {
+      var editor = CKEDITOR.dom.element.get(element).getEditor();
+      if (editor) {
+        if (trigger === 'serialize') {
+          editor.updateElement();
+        }
+        else {
+          editor.destroy();
+          element.removeAttribute('contentEditable');
+        }
       }
-    }
-    return !!editor;
-  },
+      return !!editor;
+    },
 
-  onChange: function (element, callback) {
-    var editor = CKEDITOR.dom.element.get(element).getEditor();
-    if (editor) {
-      editor.on('change', debounce(function () {
-        callback(editor.getData());
-      }, 400));
-    }
-    return !!editor;
-  },
+    onChange: function (element, callback) {
+      var editor = CKEDITOR.dom.element.get(element).getEditor();
+      if (editor) {
+        editor.on('change', debounce(function () {
+          callback(editor.getData());
+        }, 400));
+      }
+      return !!editor;
+    },
 
-  attachInlineEditor: function (element, format, mainToolbarId, floatedToolbarId) {
-    this._loadExternalPlugins(format);
-    this._ACF_HACK_to_support_blacklisted_attributes(element, format);
-    // Also pass settings that are Drupal-specific.
-    format.editorSettings.drupal = {
-      format: format.format
-    };
+    attachInlineEditor: function (element, format, mainToolbarId, floatedToolbarId) {
+      this._loadExternalPlugins(format);
+      this._ACF_HACK_to_support_blacklisted_attributes(element, format);
+      // Also pass settings that are Drupal-specific.
+      format.editorSettings.drupal = {
+        format: format.format
+      };
 
-    var settings = $.extend(true, {}, format.editorSettings);
+      var settings = $.extend(true, {}, format.editorSettings);
 
-    // If a toolbar is already provided for "true WYSIWYG" (in-place editing),
-    // then use that toolbar instead: override the default settings to render
-    // CKEditor UI's top toolbar into mainToolbar, and don't render the bottom
-    // toolbar at all. (CKEditor doesn't need a floated toolbar.)
-    if (mainToolbarId) {
-      var settingsOverride = {
-        extraPlugins: 'sharedspace',
-        removePlugins: 'floatingspace,elementspath',
-        sharedSpaces: {
-          top: mainToolbarId
-        }
-      };
+      // If a toolbar is already provided for "true WYSIWYG" (in-place editing),
+      // then use that toolbar instead: override the default settings to render
+      // CKEditor UI's top toolbar into mainToolbar, and don't render the bottom
+      // toolbar at all. (CKEditor doesn't need a floated toolbar.)
+      if (mainToolbarId) {
+        var settingsOverride = {
+          extraPlugins: 'sharedspace',
+          removePlugins: 'floatingspace,elementspath',
+          sharedSpaces: {
+            top: mainToolbarId
+          }
+        };
 
-      // Find the "Source" button, if any, and replace it with "Sourcedialog".
-      // (The 'sourcearea' plugin only works in CKEditor's iframe mode.)
-      var sourceButtonFound = false;
-      for (var i = 0; !sourceButtonFound && i < settings.toolbar.length; i++) {
-        if (settings.toolbar[i] !== '/') {
-          for (var j = 0; !sourceButtonFound && j < settings.toolbar[i].items.length; j++) {
-            if (settings.toolbar[i].items[j] === 'Source') {
-              sourceButtonFound = true;
-              // Swap sourcearea's "Source" button for sourcedialog's.
-              settings.toolbar[i].items[j] = 'Sourcedialog';
-              settingsOverride.extraPlugins += ',sourcedialog';
-              settingsOverride.removePlugins += ',sourcearea';
+        // Find the "Source" button, if any, and replace it with "Sourcedialog".
+        // (The 'sourcearea' plugin only works in CKEditor's iframe mode.)
+        var sourceButtonFound = false;
+        for (var i = 0; !sourceButtonFound && i < settings.toolbar.length; i++) {
+          if (settings.toolbar[i] !== '/') {
+            for (var j = 0; !sourceButtonFound && j < settings.toolbar[i].items.length; j++) {
+              if (settings.toolbar[i].items[j] === 'Source') {
+                sourceButtonFound = true;
+                // Swap sourcearea's "Source" button for sourcedialog's.
+                settings.toolbar[i].items[j] = 'Sourcedialog';
+                settingsOverride.extraPlugins += ',sourcedialog';
+                settingsOverride.removePlugins += ',sourcearea';
+              }
             }
           }
         }
-      }
 
-      settings.extraPlugins += ',' + settingsOverride.extraPlugins;
-      settings.removePlugins += ',' + settingsOverride.removePlugins;
-      settings.sharedSpaces = settingsOverride.sharedSpaces;
-    }
+        settings.extraPlugins += ',' + settingsOverride.extraPlugins;
+        settings.removePlugins += ',' + settingsOverride.removePlugins;
+        settings.sharedSpaces = settingsOverride.sharedSpaces;
+      }
 
-    // CKEditor requires an element to already have the contentEditable
-    // attribute set to "true", otherwise it won't attach an inline editor.
-    element.setAttribute('contentEditable', 'true');
+      // CKEditor requires an element to already have the contentEditable
+      // attribute set to "true", otherwise it won't attach an inline editor.
+      element.setAttribute('contentEditable', 'true');
 
-    return !!CKEDITOR.inline(element, settings);
-  },
+      return !!CKEDITOR.inline(element, settings);
+    },
 
-  _loadExternalPlugins: function (format) {
-    var externalPlugins = format.editorSettings.drupalExternalPlugins;
-    // Register and load additional CKEditor plugins as necessary.
-    if (externalPlugins) {
-      for (var pluginName in externalPlugins) {
-        if (externalPlugins.hasOwnProperty(pluginName)) {
-          CKEDITOR.plugins.addExternal(pluginName, externalPlugins[pluginName], '');
+    _loadExternalPlugins: function (format) {
+      var externalPlugins = format.editorSettings.drupalExternalPlugins;
+      // Register and load additional CKEditor plugins as necessary.
+      if (externalPlugins) {
+        for (var pluginName in externalPlugins) {
+          if (externalPlugins.hasOwnProperty(pluginName)) {
+            CKEDITOR.plugins.addExternal(pluginName, externalPlugins[pluginName], '');
+          }
         }
+        delete format.editorSettings.drupalExternalPlugins;
       }
-      delete format.editorSettings.drupalExternalPlugins;
-    }
-  },
+    },
 
-  /**
-   * This is a huge hack to do ONE thing: to allow Drupal to fully mandate what
-   * CKEditor should allow by setting CKEditor's allowedContent setting. The
-   * problem is that allowedContent only allows for whitelisting, whereas
-   * Drupal's default HTML filtering (the filter_html filter) also blacklists
-   * the "style" and "on*" ("onClick" etc.) attributes.
-   *
-   * So this function hacks in explicit support for Drupal's filter_html's need
-   * to blacklist specifically those attributes, until ACF supports blacklisting
-   * of properties: http://dev.ckeditor.com/ticket/10276.
-   *
-   * Limitations:
-   *   - This does not support blacklisting of other attributes, it's only
-   *     intended to implement filter_html's blacklisted attributes.
-   *   - This is only a temporary work-around; it assumes the filter_html
-   *     filter is being used whenever *any* restriction exists. This is a valid
-   *     assumption for the default text formats in Drupal 8 core, but obviously
-   *     won't work for release.
-   *
-   * This is the only way we could get https://drupal.org/node/1936392 committed
-   * before Drupal 8 code freeze on July 1, 2013. CKEditor has committed to
-   * explicitly supporting this in some way.
-   *
-   * @todo D8 remove this once http://dev.ckeditor.com/ticket/10276 is done.
-   */
-  _ACF_HACK_to_support_blacklisted_attributes: function (element, format) {
-    function override(rule) {
-      var oldValue = rule.attributes;
-      function filter_html_override_attributes (attribute) {
-        // Disallow the "style" and "on*" attributes on any tag.
-        if (attribute === 'style' || attribute.substr(0, 2) === 'on') {
-          return false;
-        }
+    /**
+     * This is a huge hack to do ONE thing: to allow Drupal to fully mandate what
+     * CKEditor should allow by setting CKEditor's allowedContent setting. The
+     * problem is that allowedContent only allows for whitelisting, whereas
+     * Drupal's default HTML filtering (the filter_html filter) also blacklists
+     * the "style" and "on*" ("onClick" etc.) attributes.
+     *
+     * So this function hacks in explicit support for Drupal's filter_html's need
+     * to blacklist specifically those attributes, until ACF supports blacklisting
+     * of properties: http://dev.ckeditor.com/ticket/10276.
+     *
+     * Limitations:
+     *   - This does not support blacklisting of other attributes, it's only
+     *     intended to implement filter_html's blacklisted attributes.
+     *   - This is only a temporary work-around; it assumes the filter_html
+     *     filter is being used whenever *any* restriction exists. This is a valid
+     *     assumption for the default text formats in Drupal 8 core, but obviously
+     *     won't work for release.
+     *
+     * This is the only way we could get https://drupal.org/node/1936392 committed
+     * before Drupal 8 code freeze on July 1, 2013. CKEditor has committed to
+     * explicitly supporting this in some way.
+     *
+     * @todo D8 remove this once http://dev.ckeditor.com/ticket/10276 is done.
+     */
+    _ACF_HACK_to_support_blacklisted_attributes: function (element, format) {
+      function override(rule) {
+        var oldValue = rule.attributes;
+        function filter_html_override_attributes(attribute) {
+          // Disallow the "style" and "on*" attributes on any tag.
+          if (attribute === 'style' || attribute.substr(0, 2) === 'on') {
+            return false;
+          }
 
-        // Ensure the original logic still runs, if any.
-        if (typeof oldValue === 'function') {
-          return oldValue(attribute);
-        }
-        else if (typeof oldValue === 'boolean') {
-          return oldValue;
-        }
+          // Ensure the original logic still runs, if any.
+          if (typeof oldValue === 'function') {
+            return oldValue(attribute);
+          }
+          else if (typeof oldValue === 'boolean') {
+            return oldValue;
+          }
 
-        // Otherwise, accept this attribute.
-        return true;
+          // Otherwise, accept this attribute.
+          return true;
+        }
+        rule.attributes = filter_html_override_attributes;
       }
-      rule.attributes = filter_html_override_attributes;
-    }
 
-    CKEDITOR.once('instanceLoaded', function(e) {
-      if (e.editor.name === element.id) {
-        // If everything is allowed, everything is allowed.
-        if (format.editorSettings.allowedContent === true) {
-          return;
-        }
-        // Otherwise, assume Drupal's filter_html filter is being used.
-        else {
-          // Get the filter object (ACF).
-          var filter = e.editor.filter;
-          // Find the "config" rule (the one caused by the allowedContent
-          // setting) for each HTML tag, and override its "attributes" value.
-          for (var el in filter._.rules.elements) {
-            if (filter._.rules.elements.hasOwnProperty(el)) {
-              for (var i = 0; i < filter._.rules.elements[el].length; i++) {
-                if (filter._.rules.elements[el][i].featureName === 'config') {
-                  override(filter._.rules.elements[el][i]);
+      CKEDITOR.once('instanceLoaded', function (e) {
+        if (e.editor.name === element.id) {
+          // If everything is allowed, everything is allowed.
+          if (format.editorSettings.allowedContent === true) {
+            return;
+          }
+          // Otherwise, assume Drupal's filter_html filter is being used.
+          else {
+            // Get the filter object (ACF).
+            var filter = e.editor.filter;
+            // Find the "config" rule (the one caused by the allowedContent
+            // setting) for each HTML tag, and override its "attributes" value.
+            for (var el in filter._.rules.elements) {
+              if (filter._.rules.elements.hasOwnProperty(el)) {
+                for (var i = 0; i < filter._.rules.elements[el].length; i++) {
+                  if (filter._.rules.elements[el][i].featureName === 'config') {
+                    override(filter._.rules.elements[el][i]);
+                  }
                 }
               }
             }
           }
         }
-      }
-    });
-  }
+      });
+    }
 
-};
+  };
 
-Drupal.ckeditor = {
-  /**
-   * Variable storing the current dialog's save callback.
-   */
-  saveCallack: null,
+  Drupal.ckeditor = {
+    /**
+     * Variable storing the current dialog's save callback.
+     */
+    saveCallack: null,
 
-  /**
-   * Open a dialog for a Drupal-based plugin.
-   *
-   * This dynamically loads jQuery UI (if necessary) using the Drupal AJAX
-   * framework, then opens a dialog at the specified Drupal path.
-   *
-   * @param editor
-   *   The CKEditor instance that is opening the dialog.
-   * @param string url
-   *   The URL that contains the contents of the dialog.
-   * @param Object existingValues
-   *   Existing values that will be sent via POST to the url for the dialog
-   *   contents.
-   * @param Function saveCallback
-   *   A function to be called upon saving the dialog.
-   * @param Object dialogSettings
-   *   An object containing settings to be passed to the jQuery UI.
-   */
-  openDialog: function (editor, url, existingValues, saveCallback, dialogSettings) {
-    // Locate a suitable place to display our loading indicator.
-    var $target = $(editor.container.$);
-    if (editor.elementMode === CKEDITOR.ELEMENT_MODE_REPLACE) {
-      $target = $target.find('.cke_contents');
-    }
+    /**
+     * Open a dialog for a Drupal-based plugin.
+     *
+     * This dynamically loads jQuery UI (if necessary) using the Drupal AJAX
+     * framework, then opens a dialog at the specified Drupal path.
+     *
+     * @param editor
+     *   The CKEditor instance that is opening the dialog.
+     * @param string url
+     *   The URL that contains the contents of the dialog.
+     * @param Object existingValues
+     *   Existing values that will be sent via POST to the url for the dialog
+     *   contents.
+     * @param Function saveCallback
+     *   A function to be called upon saving the dialog.
+     * @param Object dialogSettings
+     *   An object containing settings to be passed to the jQuery UI.
+     */
+    openDialog: function (editor, url, existingValues, saveCallback, dialogSettings) {
+      // Locate a suitable place to display our loading indicator.
+      var $target = $(editor.container.$);
+      if (editor.elementMode === CKEDITOR.ELEMENT_MODE_REPLACE) {
+        $target = $target.find('.cke_contents');
+      }
 
-    // Remove any previous loading indicator.
-    $target.css('position', 'relative').find('.ckeditor-dialog-loading').remove();
+      // Remove any previous loading indicator.
+      $target.css('position', 'relative').find('.ckeditor-dialog-loading').remove();
 
-    // Add a consistent dialog class.
-    var classes = dialogSettings.dialogClass ? dialogSettings.dialogClass.split(' ') : [];
-    classes.push('editor-dialog');
-    dialogSettings.dialogClass = classes.join(' ');
-    dialogSettings.autoResize = Drupal.checkWidthBreakpoint(600);
+      // Add a consistent dialog class.
+      var classes = dialogSettings.dialogClass ? dialogSettings.dialogClass.split(' ') : [];
+      classes.push('editor-dialog');
+      dialogSettings.dialogClass = classes.join(' ');
+      dialogSettings.autoResize = Drupal.checkWidthBreakpoint(600);
 
-    // Add a "Loading…" message, hide it underneath the CKEditor toolbar, create
-    // 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',
-      dialog: dialogSettings,
-      selector: '.ckeditor-dialog-loading-link',
-      url: url,
-      event: 'ckeditor-internal.ckeditor',
-      progress: { 'type': 'throbber' },
-      submit: {
-        editor_object: existingValues
-      }
-    });
-    $content.find('a')
-      .on('click', function () { return false; })
-      .trigger('ckeditor-internal.ckeditor');
+      // Add a "Loading…" message, hide it underneath the CKEditor toolbar, create
+      // 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',
+        dialog: dialogSettings,
+        selector: '.ckeditor-dialog-loading-link',
+        url: url,
+        event: 'ckeditor-internal.ckeditor',
+        progress: { 'type': 'throbber' },
+        submit: {
+          editor_object: existingValues
+        }
+      });
+      $content.find('a')
+        .on('click', function () { return false; })
+        .trigger('ckeditor-internal.ckeditor');
 
-    // After a short delay, show "Loading…" message.
-    window.setTimeout(function () {
-      $content.find('span').animate({ top: '0px' });
-    }, 1000);
+      // After a short delay, show "Loading…" message.
+      window.setTimeout(function () {
+        $content.find('span').animate({ top: '0px' });
+      }, 1000);
 
-    // Store the save callback to be executed when this dialog is closed.
-    Drupal.ckeditor.saveCallback = saveCallback;
-  }
-};
+      // Store the save callback to be executed when this dialog is closed.
+      Drupal.ckeditor.saveCallback = saveCallback;
+    }
+  };
 
-// Respond to new dialogs that are opened by CKEditor, closing the AJAX loader.
-$(window).on('dialog:beforecreate', function (e, dialog, $element, settings) {
-  $('.ckeditor-dialog-loading').animate({ top: '-40px' }, function () {
-    $(this).remove();
+  // Respond to new dialogs that are opened by CKEditor, closing the AJAX loader.
+  $(window).on('dialog:beforecreate', function (e, dialog, $element, settings) {
+    $('.ckeditor-dialog-loading').animate({ top: '-40px' }, function () {
+      $(this).remove();
+    });
   });
-});
 
-// Respond to dialogs that are saved, sending data back to CKEditor.
-$(window).on('editor:dialogsave', function (e, values) {
-  if (Drupal.ckeditor.saveCallback) {
-    Drupal.ckeditor.saveCallback(values);
-  }
-});
+  // Respond to dialogs that are saved, sending data back to CKEditor.
+  $(window).on('editor:dialogsave', function (e, values) {
+    if (Drupal.ckeditor.saveCallback) {
+      Drupal.ckeditor.saveCallback(values);
+    }
+  });
 
-// Respond to dialogs that are closed, removing the current save handler.
-$(window).on('dialog:afterclose', function (e, dialog, $element) {
-  if (Drupal.ckeditor.saveCallback) {
-    Drupal.ckeditor.saveCallback = null;
-  }
-});
+  // Respond to dialogs that are closed, removing the current save handler.
+  $(window).on('dialog:afterclose', function (e, dialog, $element) {
+    if (Drupal.ckeditor.saveCallback) {
+      Drupal.ckeditor.saveCallback = null;
+    }
+  });
 
 })(Drupal, Drupal.debounce, CKEDITOR, jQuery);
diff --git a/core/modules/ckeditor/js/ckeditor.stylescombo.admin.js b/core/modules/ckeditor/js/ckeditor.stylescombo.admin.js
index c10b046..4e369ac 100644
--- a/core/modules/ckeditor/js/ckeditor.stylescombo.admin.js
+++ b/core/modules/ckeditor/js/ckeditor.stylescombo.admin.js
@@ -1,111 +1,111 @@
 (function ($, Drupal, drupalSettings) {
 
-"use strict";
-
-/**
- * Ensures that the "stylescombo" button's metadata remains up-to-date.
- *
- * Triggers the CKEditorPluginSettingsChanged event whenever the "stylescombo"
- * plugin settings change, to ensure that the corresponding feature metadata is
- * immediately updated — i.e. ensure that HTML tags and classes entered here are
- * known to be "required", which may affect filter settings.
- */
-Drupal.behaviors.ckeditorStylesComboSettings = {
-  attach: function (context) {
-    var $context = $(context);
-
-    // React to changes in the list of user-defined styles: calculate the new
-    // stylesSet setting up to 2 times per second, and if it is different, fire
-    // the CKEditorPluginSettingsChanged event with the updated parts of the
-    // CKEditor configuration. (This will, in turn, cause the hidden CKEditor
-    // instance to be updated and a drupalEditorFeatureModified event to fire.)
-    var $ckeditorActiveToolbar = $context
-      .find('.ckeditor-toolbar-configuration')
-      .find('.ckeditor-toolbar-active');
-    var previousStylesSet = drupalSettings.ckeditor.hiddenCKEditorConfig.stylesSet;
-    var that = this;
-    $context.find('[name="editor[settings][plugins][stylescombo][styles]"]')
-      .on('blur.ckeditorStylesComboSettings', function () {
-        var styles = $.trim($('#edit-editor-settings-plugins-stylescombo-styles').val());
-        var stylesSet = that._generateStylesSetSetting(styles);
-        if (!_.isEqual(previousStylesSet, stylesSet)) {
-          previousStylesSet = stylesSet;
-          $ckeditorActiveToolbar.trigger('CKEditorPluginSettingsChanged', [{ stylesSet: stylesSet } ]);
-        }
-      });
-  },
-
+  "use strict";
 
   /**
-   * Builds the "stylesSet" configuration part of the CKEditor JS settings.
-   *
-   * @see \Drupal\ckeditor\Plugin\ckeditor\plugin\StylesCombo::generateStylesSetSetting()
+   * Ensures that the "stylescombo" button's metadata remains up-to-date.
    *
-   * Note that this is a more forgiving implementation than the PHP version: the
-   * parsing works identically, but instead of failing on invalid styles, we
-   * just ignore those.
-   *
-   * @param String sstyles
-   *   The "styles" setting.
-   *
-   * @return array
-   *   An array containing the "stylesSet" configuration.
+   * Triggers the CKEditorPluginSettingsChanged event whenever the "stylescombo"
+   * plugin settings change, to ensure that the corresponding feature metadata is
+   * immediately updated — i.e. ensure that HTML tags and classes entered here are
+   * known to be "required", which may affect filter settings.
    */
-  _generateStylesSetSetting: function (styles) {
-    var stylesSet = [];
+  Drupal.behaviors.ckeditorStylesComboSettings = {
+    attach: function (context) {
+      var $context = $(context);
 
-    styles = styles.replace(/\r/g, "\n");
-    var lines = styles.split("\n");
-    for (var i = 0; i < lines.length; i++) {
-      var style = $.trim(lines[i]);
+      // React to changes in the list of user-defined styles: calculate the new
+      // stylesSet setting up to 2 times per second, and if it is different, fire
+      // the CKEditorPluginSettingsChanged event with the updated parts of the
+      // CKEditor configuration. (This will, in turn, cause the hidden CKEditor
+      // instance to be updated and a drupalEditorFeatureModified event to fire.)
+      var $ckeditorActiveToolbar = $context
+        .find('.ckeditor-toolbar-configuration')
+        .find('.ckeditor-toolbar-active');
+      var previousStylesSet = drupalSettings.ckeditor.hiddenCKEditorConfig.stylesSet;
+      var that = this;
+      $context.find('[name="editor[settings][plugins][stylescombo][styles]"]')
+        .on('blur.ckeditorStylesComboSettings', function () {
+          var styles = $.trim($('#edit-editor-settings-plugins-stylescombo-styles').val());
+          var stylesSet = that._generateStylesSetSetting(styles);
+          if (!_.isEqual(previousStylesSet, stylesSet)) {
+            previousStylesSet = stylesSet;
+            $ckeditorActiveToolbar.trigger('CKEditorPluginSettingsChanged', [{ stylesSet: stylesSet }]);
+          }
+        });
+    },
 
-      // Ignore empty lines in between non-empty lines.
-      if (style.length === 0) {
-        continue;
-      }
 
-      // Validate syntax: element[.class...]|label pattern expected.
-      if (style.match(/^ *[a-zA-Z0-9]+ *(\.[a-zA-Z0-9_-]+ *)*\| *.+ *$/) === null) {
-        // Instead of failing, we just ignore any invalid styles.
-        continue;
-      }
+    /**
+     * Builds the "stylesSet" configuration part of the CKEditor JS settings.
+     *
+     * @see \Drupal\ckeditor\Plugin\ckeditor\plugin\StylesCombo::generateStylesSetSetting()
+     *
+     * Note that this is a more forgiving implementation than the PHP version: the
+     * parsing works identically, but instead of failing on invalid styles, we
+     * just ignore those.
+     *
+     * @param String sstyles
+     *   The "styles" setting.
+     *
+     * @return array
+     *   An array containing the "stylesSet" configuration.
+     */
+    _generateStylesSetSetting: function (styles) {
+      var stylesSet = [];
 
-      // Parse.
-      var parts = style.split('|');
-      var selector = parts[0];
-      var label = parts[1];
-      var classes = selector.split('.');
-      var element = classes.shift();
+      styles = styles.replace(/\r/g, "\n");
+      var lines = styles.split("\n");
+      for (var i = 0; i < lines.length; i++) {
+        var style = $.trim(lines[i]);
 
-      // Build the data structure CKEditor's stylescombo plugin expects.
-      // @see http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Styles
-      stylesSet.push({
-        attributes: { class: classes.join(' ') },
-        element: element,
-        name: label
-      });
-    }
+        // Ignore empty lines in between non-empty lines.
+        if (style.length === 0) {
+          continue;
+        }
 
-    return stylesSet;
-  }
-};
+        // Validate syntax: element[.class...]|label pattern expected.
+        if (style.match(/^ *[a-zA-Z0-9]+ *(\.[a-zA-Z0-9_-]+ *)*\| *.+ *$/) === null) {
+          // Instead of failing, we just ignore any invalid styles.
+          continue;
+        }
 
-/**
- * Provides the summary for the "stylescombo" plugin settings vertical tab.
- */
-Drupal.behaviors.ckeditorStylesComboSettingsSummary = {
-  attach: function () {
-    $('#edit-editor-settings-plugins-stylescombo').drupalSetSummary(function (context) {
-      var styles = $.trim($('#edit-editor-settings-plugins-stylescombo-styles').val());
-      if (styles.length === 0) {
-        return Drupal.t('No styles configured');
-      }
-      else {
-        var count = $.trim(styles).split("\n").length;
-        return Drupal.t('@count styles configured', { '@count': count});
+        // Parse.
+        var parts = style.split('|');
+        var selector = parts[0];
+        var label = parts[1];
+        var classes = selector.split('.');
+        var element = classes.shift();
+
+        // Build the data structure CKEditor's stylescombo plugin expects.
+        // @see http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Styles
+        stylesSet.push({
+          attributes: { class: classes.join(' ') },
+          element: element,
+          name: label
+        });
       }
-    });
-  }
-};
+
+      return stylesSet;
+    }
+  };
+
+  /**
+   * Provides the summary for the "stylescombo" plugin settings vertical tab.
+   */
+  Drupal.behaviors.ckeditorStylesComboSettingsSummary = {
+    attach: function () {
+      $('#edit-editor-settings-plugins-stylescombo').drupalSetSummary(function (context) {
+        var styles = $.trim($('#edit-editor-settings-plugins-stylescombo-styles').val());
+        if (styles.length === 0) {
+          return Drupal.t('No styles configured');
+        }
+        else {
+          var count = $.trim(styles).split("\n").length;
+          return Drupal.t('@count styles configured', { '@count': count});
+        }
+      });
+    }
+  };
 
 })(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/ckeditor/js/plugins/drupalimage/plugin.js b/core/modules/ckeditor/js/plugins/drupalimage/plugin.js
index 4b579d5..d58060f 100644
--- a/core/modules/ckeditor/js/plugins/drupalimage/plugin.js
+++ b/core/modules/ckeditor/js/plugins/drupalimage/plugin.js
@@ -5,175 +5,175 @@
 
 (function ($, Drupal, drupalSettings, CKEDITOR) {
 
-"use strict";
-
-CKEDITOR.plugins.add('drupalimage', {
-  init: function (editor) {
-    // Register the image command.
-    editor.addCommand('drupalimage', {
-      allowedContent: 'img[alt,!src,width,height]',
-      requiredContent: 'img[alt,src,width,height]',
-      modes: { wysiwyg : 1 },
-      canUndo: true,
-      exec: function (editor, override) {
-        var imageDOMElement = null;
-        var existingValues = {};
-        var dialogTitle;
-        var saveCallback = function (returnValues) {
-          var selection = editor.getSelection();
-          var imageElement = selection.getSelectedElement();
-
-          editor.fire('saveSnapshot');
-
-          // Create a new image element if needed.
-          if (!imageElement && returnValues.attributes.src) {
-            imageElement = editor.document.createElement('img');
-            imageElement.setAttribute('alt', '');
-            editor.insertElement(imageElement);
-          }
-          // Delete the image if the src was removed.
-          if (imageElement && !returnValues.attributes.src) {
-            imageElement.remove();
-          }
-          // Update the image properties.
-          else {
-            for (var key in returnValues.attributes) {
-              if (returnValues.attributes.hasOwnProperty(key)) {
-                // Update the property if a value is specified.
-                if (returnValues.attributes[key].length > 0) {
-                  var value = returnValues.attributes[key];
-                  imageElement.data('cke-saved-' + key, value);
-                  imageElement.setAttribute(key, value);
-                }
-                // Delete the property if set to an empty string.
-                else {
-                  imageElement.removeAttribute(key);
+  "use strict";
+
+  CKEDITOR.plugins.add('drupalimage', {
+    init: function (editor) {
+      // Register the image command.
+      editor.addCommand('drupalimage', {
+        allowedContent: 'img[alt,!src,width,height]',
+        requiredContent: 'img[alt,src,width,height]',
+        modes: { wysiwyg: 1 },
+        canUndo: true,
+        exec: function (editor, override) {
+          var imageDOMElement = null;
+          var existingValues = {};
+          var dialogTitle;
+          var saveCallback = function (returnValues) {
+            var selection = editor.getSelection();
+            var imageElement = selection.getSelectedElement();
+
+            editor.fire('saveSnapshot');
+
+            // Create a new image element if needed.
+            if (!imageElement && returnValues.attributes.src) {
+              imageElement = editor.document.createElement('img');
+              imageElement.setAttribute('alt', '');
+              editor.insertElement(imageElement);
+            }
+            // Delete the image if the src was removed.
+            if (imageElement && !returnValues.attributes.src) {
+              imageElement.remove();
+            }
+            // Update the image properties.
+            else {
+              for (var key in returnValues.attributes) {
+                if (returnValues.attributes.hasOwnProperty(key)) {
+                  // Update the property if a value is specified.
+                  if (returnValues.attributes[key].length > 0) {
+                    var value = returnValues.attributes[key];
+                    imageElement.data('cke-saved-' + key, value);
+                    imageElement.setAttribute(key, value);
+                  }
+                  // Delete the property if set to an empty string.
+                  else {
+                    imageElement.removeAttribute(key);
+                  }
                 }
               }
             }
-          }
 
-          // Save snapshot for undo support.
-          editor.fire('saveSnapshot');
-        };
-
-        // Allow CKEditor Widget plugins to execute DrupalImage's 'drupalimage'
-        // command. In this case, they need to provide the DOM element for the
-        // image (because this plugin wouldn't know where to find it), its
-        // existing values (because they're stored within the Widget in whatever
-        // way it sees fit) and a save callback (again because the Widget may
-        // store the returned values in whatever way it sees fit).
-        if (override) {
-          imageDOMElement = override.imageDOMElement;
-          existingValues = override.existingValues;
-          dialogTitle = override.dialogTitle;
-          if (override.saveCallback) {
-            saveCallback = override.saveCallback;
-          }
-        }
-        // Otherwise, retrieve the selected image and allow it to be edited, or
-        // if no image is selected: insert a new one.
-        else {
-          var selection = editor.getSelection();
-          var imageElement = selection.getSelectedElement();
-
-          // If the 'drupalimage' command is being applied to a CKEditor widget,
-          // then edit that Widget instead.
-          if (imageElement && imageElement.type === CKEDITOR.NODE_ELEMENT && imageElement.hasAttribute('data-widget-wrapper')) {
-            editor.widgets.focused.edit();
-            return;
-          }
-          // Otherwise, check if the 'drupalimage' command is being applied to
-          // an existing image tag, and then open a dialog to edit it.
-          else if (isImage(imageElement) && imageElement.$) {
-            imageDOMElement = imageElement.$;
-
-            // Width and height are populated by actual dimensions.
-            existingValues.width = imageDOMElement ? imageDOMElement.naturalWidth : '';
-            existingValues.height = imageDOMElement ? imageDOMElement.naturalHeight : '';
-            // Populate all other attributes by their specified attribute values.
-            var attribute = null, attributeName;
-            for (var key = 0; key < imageDOMElement.attributes.length; key++) {
-              attribute = imageDOMElement.attributes.item(key);
-              attributeName = attribute.nodeName.toLowerCase();
-              // Don't consider data-cke-saved- attributes; they're just there to
-              // work around browser quirks.
-              if (attributeName.substring(0, 15) === 'data-cke-saved-') {
-                continue;
-              }
-              // Store the value for this attribute, unless there's a
-              // data-cke-saved- alternative for it, which will contain the quirk-
-              // free, original value.
-              existingValues[attributeName] = imageElement.data('cke-saved-' + attributeName) || attribute.nodeValue;
+            // Save snapshot for undo support.
+            editor.fire('saveSnapshot');
+          };
+
+          // Allow CKEditor Widget plugins to execute DrupalImage's 'drupalimage'
+          // command. In this case, they need to provide the DOM element for the
+          // image (because this plugin wouldn't know where to find it), its
+          // existing values (because they're stored within the Widget in whatever
+          // way it sees fit) and a save callback (again because the Widget may
+          // store the returned values in whatever way it sees fit).
+          if (override) {
+            imageDOMElement = override.imageDOMElement;
+            existingValues = override.existingValues;
+            dialogTitle = override.dialogTitle;
+            if (override.saveCallback) {
+              saveCallback = override.saveCallback;
             }
-
-            dialogTitle = editor.config.drupalImage_dialogTitleEdit;
           }
-          // The 'drupalimage' command is being executed to add a new image.
+          // Otherwise, retrieve the selected image and allow it to be edited, or
+          // if no image is selected: insert a new one.
           else {
-            dialogTitle = editor.config.drupalImage_dialogTitleAdd;
-            // Allow other plugins to override the image insertion: they must
-            // listen to this event and cancel the event to do so.
-            if (!editor.fire('drupalimageinsert')) {
+            var selection = editor.getSelection();
+            var imageElement = selection.getSelectedElement();
+
+            // If the 'drupalimage' command is being applied to a CKEditor widget,
+            // then edit that Widget instead.
+            if (imageElement && imageElement.type === CKEDITOR.NODE_ELEMENT && imageElement.hasAttribute('data-widget-wrapper')) {
+              editor.widgets.focused.edit();
               return;
             }
+            // Otherwise, check if the 'drupalimage' command is being applied to
+            // an existing image tag, and then open a dialog to edit it.
+            else if (isImage(imageElement) && imageElement.$) {
+              imageDOMElement = imageElement.$;
+
+              // Width and height are populated by actual dimensions.
+              existingValues.width = imageDOMElement ? imageDOMElement.naturalWidth : '';
+              existingValues.height = imageDOMElement ? imageDOMElement.naturalHeight : '';
+              // Populate all other attributes by their specified attribute values.
+              var attribute = null, attributeName;
+              for (var key = 0; key < imageDOMElement.attributes.length; key++) {
+                attribute = imageDOMElement.attributes.item(key);
+                attributeName = attribute.nodeName.toLowerCase();
+                // Don't consider data-cke-saved- attributes; they're just there to
+                // work around browser quirks.
+                if (attributeName.substring(0, 15) === 'data-cke-saved-') {
+                  continue;
+                }
+                // Store the value for this attribute, unless there's a
+                // data-cke-saved- alternative for it, which will contain the quirk-
+                // free, original value.
+                existingValues[attributeName] = imageElement.data('cke-saved-' + attributeName) || attribute.nodeValue;
+              }
+
+              dialogTitle = editor.config.drupalImage_dialogTitleEdit;
+            }
+            // The 'drupalimage' command is being executed to add a new image.
+            else {
+              dialogTitle = editor.config.drupalImage_dialogTitleAdd;
+              // Allow other plugins to override the image insertion: they must
+              // listen to this event and cancel the event to do so.
+              if (!editor.fire('drupalimageinsert')) {
+                return;
+              }
+            }
           }
-        }
 
-        // Drupal.t() will not work inside CKEditor plugins because CKEditor
-        // loads the JavaScript file instead of Drupal. Pull translated strings
-        // from the plugin settings that are translated server-side.
-        var dialogSettings = {
-          title: dialogTitle,
-          dialogClass: 'editor-image-dialog'
-        };
+          // Drupal.t() will not work inside CKEditor plugins because CKEditor
+          // loads the JavaScript file instead of Drupal. Pull translated strings
+          // from the plugin settings that are translated server-side.
+          var dialogSettings = {
+            title: dialogTitle,
+            dialogClass: 'editor-image-dialog'
+          };
 
-        // Open the dialog for the edit form.
-        Drupal.ckeditor.openDialog(editor, Drupal.url('editor/dialog/image/' + editor.config.drupal.format), existingValues, saveCallback, dialogSettings);
-      }
-    });
-
-    // Register the toolbar button.
-    if (editor.ui.addButton) {
-      editor.ui.addButton('DrupalImage', {
-        label: Drupal.t('Image'),
-        command: 'drupalimage',
-        icon: this.path.replace(/plugin\.js.*/, 'image.png')
+          // Open the dialog for the edit form.
+          Drupal.ckeditor.openDialog(editor, Drupal.url('editor/dialog/image/' + editor.config.drupal.format), existingValues, saveCallback, dialogSettings);
+        }
       });
-    }
 
-    // Double clicking an image opens its properties.
-    editor.on('doubleclick', function (event) {
-      var element = event.data.element;
-      if (element.is('img') && !element.data('cke-realelement') && !element.isReadOnly()) {
-        editor.getCommand('drupalimage').exec();
+      // Register the toolbar button.
+      if (editor.ui.addButton) {
+        editor.ui.addButton('DrupalImage', {
+          label: Drupal.t('Image'),
+          command: 'drupalimage',
+          icon: this.path.replace(/plugin\.js.*/, 'image.png')
+        });
       }
-    });
-
-    // If the "menu" plugin is loaded, register the menu items.
-    if (editor.addMenuItems) {
-      editor.addMenuItems({
-        image: {
-          label: Drupal.t('Image Properties'),
-          command : 'drupalimage',
-          group: 'image'
-        }
-      });
-    }
 
-    // If the "contextmenu" plugin is loaded, register the listeners.
-    if (editor.contextMenu) {
-      editor.contextMenu.addListener(function (element, selection) {
-        if (isImage(element)) {
-          return { image: CKEDITOR.TRISTATE_OFF };
+      // Double clicking an image opens its properties.
+      editor.on('doubleclick', function (event) {
+        var element = event.data.element;
+        if (element.is('img') && !element.data('cke-realelement') && !element.isReadOnly()) {
+          editor.getCommand('drupalimage').exec();
         }
       });
+
+      // If the "menu" plugin is loaded, register the menu items.
+      if (editor.addMenuItems) {
+        editor.addMenuItems({
+          image: {
+            label: Drupal.t('Image Properties'),
+            command: 'drupalimage',
+            group: 'image'
+          }
+        });
+      }
+
+      // If the "contextmenu" plugin is loaded, register the listeners.
+      if (editor.contextMenu) {
+        editor.contextMenu.addListener(function (element, selection) {
+          if (isImage(element)) {
+            return { image: CKEDITOR.TRISTATE_OFF };
+          }
+        });
+      }
     }
-  }
-});
+  });
 
-function isImage (element) {
-  return element && element.is('img') && !element.data('cke-realelement') && !element.isReadOnly();
-}
+  function isImage(element) {
+    return element && element.is('img') && !element.data('cke-realelement') && !element.isReadOnly();
+  }
 
 })(jQuery, Drupal, drupalSettings, CKEDITOR);
diff --git a/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js b/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js
index c37b99d..42d9e93 100644
--- a/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js
+++ b/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js
@@ -7,263 +7,263 @@
 
 (function (CKEDITOR) {
 
-"use strict";
-
-CKEDITOR.plugins.add('drupalimagecaption', {
-  requires: 'widget',
-  init: function (editor) {
-
-    /**
-     * Override drupalimage plugin's image insertion mechanism with our own, to
-     * ensure a widget is inserted, rather than a simple image (Widget's auto-
-     * discovery only runs upon init).
-     */
-    editor.on('drupalimageinsert', function (event) {
-      editor.execCommand('widgetDrupalimagecaption');
-      event.cancel();
-    });
-
-    // Register the widget with a unique name "drupalimagecaption".
-    editor.widgets.add('drupalimagecaption', {
-      allowedContent: 'img[!src,alt,width,height,!data-caption,!data-align]',
-      template: '<img src="" />',
-      parts: {
-        image: 'img'
-      },
-
-      // Initialization method called for every widget instance being
-      // upcasted.
-      init: function () {
-        var image = this.parts.image;
-
-        // Save the initial widget data.
-        this.setData({
-          'data-editor-file-uuid': image.getAttribute('data-editor-file-uuid'),
-          src: image.getAttribute('src'),
-          width: image.getAttribute('width') || '',
-          height: image.getAttribute('height') || '',
-          alt: image.getAttribute('alt') || '',
-          data_caption: image.getAttribute('data-caption'),
-          data_align: image.getAttribute('data-align'),
-          hasCaption: image.hasAttribute('data-caption')
-        });
-
-        image.removeStyle('float');
-      },
-
-      // Called after initialization and on "data" changes.
-      data: function () {
-        if (this.data['data-editor-file-uuid'] !== null) {
-          this.parts.image.setAttribute('data-editor-file-uuid', this.data['data-editor-file-uuid']);
-          this.parts.image.setAttribute('data-cke-saved-data-editor-file-uuid', this.data['data-editor-file-uuid']);
-        }
-        this.parts.image.setAttribute('src', this.data.src);
-        this.parts.image.setAttribute('data-cke-saved-src', this.data.src);
-        this.parts.image.setAttribute('alt', this.data.alt);
-        this.parts.image.setAttribute('data-cke-saved-alt', this.data.alt);
-        this.parts.image.setAttribute('width', this.data.width);
-        this.parts.image.setAttribute('data-cke-saved-width', this.data.width);
-        this.parts.image.setAttribute('height', this.data.height);
-        this.parts.image.setAttribute('data-cke-saved-height', this.data.height);
-        if (this.data.hasCaption) {
-          this.parts.image.setAttribute('data-caption', this.data.data_caption);
-          this.parts.image.setAttribute('data-cke-saved-data-caption', this.data.data_caption);
-        }
-        else {
-          this.parts.image.removeAttributes(['data-caption', 'data-cke-saved-data-caption']);
-        }
-        if (this.data.data_align !== null) {
-          this.parts.image.setAttribute('data-align', this.data.data_align);
-          this.parts.image.setAttribute('data-cke-saved-data-align', this.data.data_align);
-        }
-        else {
-          this.parts.image.removeAttributes(['data-align', 'data-cke-saved-data-align']);
-        }
+  "use strict";
+
+  CKEDITOR.plugins.add('drupalimagecaption', {
+    requires: 'widget',
+    init: function (editor) {
+
+      /**
+       * Override drupalimage plugin's image insertion mechanism with our own, to
+       * ensure a widget is inserted, rather than a simple image (Widget's auto-
+       * discovery only runs upon init).
+       */
+      editor.on('drupalimageinsert', function (event) {
+        editor.execCommand('widgetDrupalimagecaption');
+        event.cancel();
+      });
+
+      // Register the widget with a unique name "drupalimagecaption".
+      editor.widgets.add('drupalimagecaption', {
+        allowedContent: 'img[!src,alt,width,height,!data-caption,!data-align]',
+        template: '<img src="" />',
+        parts: {
+          image: 'img'
+        },
+
+        // Initialization method called for every widget instance being
+        // upcasted.
+        init: function () {
+          var image = this.parts.image;
+
+          // Save the initial widget data.
+          this.setData({
+            'data-editor-file-uuid': image.getAttribute('data-editor-file-uuid'),
+            src: image.getAttribute('src'),
+            width: image.getAttribute('width') || '',
+            height: image.getAttribute('height') || '',
+            alt: image.getAttribute('alt') || '',
+            data_caption: image.getAttribute('data-caption'),
+            data_align: image.getAttribute('data-align'),
+            hasCaption: image.hasAttribute('data-caption')
+          });
 
-        // Float the wrapper too.
-        if (this.data.data_align === null) {
-          this.wrapper.removeStyle('float');
-          this.wrapper.removeStyle('text-align');
-        }
-        else if (this.data.data_align === 'center') {
-          this.wrapper.setStyle('float', 'none');
-          this.wrapper.setStyle('text-align', 'center');
-        }
-        else {
-          this.wrapper.setStyle('float', this.data.data_align);
-          this.wrapper.removeStyle('text-align');
-        }
-      },
-
-      // Check the elements that need to be converted to widgets.
-      upcast: function (el) {
-        // Upcast all <img> elements that are alone inside a block element.
-        if (el.name === 'img') {
-          if (CKEDITOR.dtd.$block[el.parent.name] && el.parent.children.length === 1) {
-            return true;
-          }
-        }
-      },
+          image.removeStyle('float');
+        },
 
-      // Convert the element back to its desired output representation.
-      downcast: function (el) {
-        if (this.data.hasCaption) {
-          el.attributes['data-caption'] = this.data.data_caption;
-        }
+        // Called after initialization and on "data" changes.
+        data: function () {
+          if (this.data['data-editor-file-uuid'] !== null) {
+            this.parts.image.setAttribute('data-editor-file-uuid', this.data['data-editor-file-uuid']);
+            this.parts.image.setAttribute('data-cke-saved-data-editor-file-uuid', this.data['data-editor-file-uuid']);
+          }
+          this.parts.image.setAttribute('src', this.data.src);
+          this.parts.image.setAttribute('data-cke-saved-src', this.data.src);
+          this.parts.image.setAttribute('alt', this.data.alt);
+          this.parts.image.setAttribute('data-cke-saved-alt', this.data.alt);
+          this.parts.image.setAttribute('width', this.data.width);
+          this.parts.image.setAttribute('data-cke-saved-width', this.data.width);
+          this.parts.image.setAttribute('height', this.data.height);
+          this.parts.image.setAttribute('data-cke-saved-height', this.data.height);
+          if (this.data.hasCaption) {
+            this.parts.image.setAttribute('data-caption', this.data.data_caption);
+            this.parts.image.setAttribute('data-cke-saved-data-caption', this.data.data_caption);
+          }
+          else {
+            this.parts.image.removeAttributes(['data-caption', 'data-cke-saved-data-caption']);
+          }
+          if (this.data.data_align !== null) {
+            this.parts.image.setAttribute('data-align', this.data.data_align);
+            this.parts.image.setAttribute('data-cke-saved-data-align', this.data.data_align);
+          }
+          else {
+            this.parts.image.removeAttributes(['data-align', 'data-cke-saved-data-align']);
+          }
 
-        if (this.data.data_align) {
-          el.attributes['data-align'] = this.data.data_align;
-        }
+          // Float the wrapper too.
+          if (this.data.data_align === null) {
+            this.wrapper.removeStyle('float');
+            this.wrapper.removeStyle('text-align');
+          }
+          else if (this.data.data_align === 'center') {
+            this.wrapper.setStyle('float', 'none');
+            this.wrapper.setStyle('text-align', 'center');
+          }
+          else {
+            this.wrapper.setStyle('float', this.data.data_align);
+            this.wrapper.removeStyle('text-align');
+          }
+        },
+
+        // Check the elements that need to be converted to widgets.
+        upcast: function (el) {
+          // Upcast all <img> elements that are alone inside a block element.
+          if (el.name === 'img') {
+            if (CKEDITOR.dtd.$block[el.parent.name] && el.parent.children.length === 1) {
+              return true;
+            }
+          }
+        },
 
-        if (!this.data.width) {
-          el.attributes['data-cke-saved-width'] = this.parts.image.$.naturalWidth;
-        }
-        if (!this.data.height) {
-          el.attributes['data-cke-saved-height'] = this.parts.image.$.naturalHeight;
-        }
-      },
-
-      _selectionWillCreateInlineImage: function () {
-        // Returns node or first of its ancestors
-        // which is a block or block limit.
-        function getBlockParent( node, root ) {
-          var path = new CKEDITOR.dom.elementPath( node, root );
-          return path.block || path.blockLimit;
-        }
+        // Convert the element back to its desired output representation.
+        downcast: function (el) {
+          if (this.data.hasCaption) {
+            el.attributes['data-caption'] = this.data.data_caption;
+          }
 
-        var range = editor.getSelection().getRanges()[ 0 ],
-          startEl = getBlockParent( range.startContainer, range.root ),
-          endEl = getBlockParent( range.endContainer, range.root );
+          if (this.data.data_align) {
+            el.attributes['data-align'] = this.data.data_align;
+          }
 
-        var insideStartEl = range.checkBoundaryOfElement( startEl, CKEDITOR.START );
-        var insideEndEl = range.checkBoundaryOfElement( endEl, CKEDITOR.END );
+          if (!this.data.width) {
+            el.attributes['data-cke-saved-width'] = this.parts.image.$.naturalWidth;
+          }
+          if (!this.data.height) {
+            el.attributes['data-cke-saved-height'] = this.parts.image.$.naturalHeight;
+          }
+        },
+
+        _selectionWillCreateInlineImage: function () {
+          // Returns node or first of its ancestors
+          // which is a block or block limit.
+          function getBlockParent(node, root) {
+            var path = new CKEDITOR.dom.elementPath(node, root);
+            return path.block || path.blockLimit;
+          }
 
-        return !(insideStartEl && insideEndEl);
-      },
+          var range = editor.getSelection().getRanges()[ 0 ],
+            startEl = getBlockParent(range.startContainer, range.root),
+            endEl = getBlockParent(range.endContainer, range.root);
 
-      _insertSaveCallback: function (returnValues) {
-        // We can't create an image with an empty "src" attribute.
-        if (returnValues.attributes.src.length === 0) {
-          return;
-        }
+          var insideStartEl = range.checkBoundaryOfElement(startEl, CKEDITOR.START);
+          var insideEndEl = range.checkBoundaryOfElement(endEl, CKEDITOR.END);
 
-        editor.fire('saveSnapshot');
+          return !(insideStartEl && insideEndEl);
+        },
 
-        // Build the HTML for the widget.
-        var html = '<img ';
-        for (var attr in returnValues.attributes) {
-          if (returnValues.attributes.hasOwnProperty(attr) && !attr.match(/^data_/)) {
-            html += attr + '="' + returnValues.attributes[attr] + '" ';
-            html += 'data-cke-saved-' + attr + '="' + returnValues.attributes[attr] + '" ';
+        _insertSaveCallback: function (returnValues) {
+          // We can't create an image with an empty "src" attribute.
+          if (returnValues.attributes.src.length === 0) {
+            return;
           }
-        }
-        if (returnValues.hasCaption) {
-          html += 'data-caption="" ';
-          html += ' data-cke-saved-data-caption=""';
-        }
-        if (returnValues.attributes.data_align && returnValues.attributes.data_align !== 'none') {
-          html += 'data-align="' + returnValues.attributes.data_align + '" ';
-          html += ' data-cke-saved-data-align="' + returnValues.attributes.data_align + '"';
-        }
-        html += ' />';
-        var el = new CKEDITOR.dom.element.createFromHtml(html, editor.document);
-        editor.insertElement(editor.widgets.wrapElement(el, 'drupalimagecaption'));
-
-        // Save snapshot for undo support.
-        editor.fire('saveSnapshot');
-
-        // Initialize and focus the widget.
-        var widget = editor.widgets.initOn(el, 'drupalimagecaption');
-        widget.focus();
-      },
-
-      insert: function () {
-        var override = {
-          imageDOMElement: null,
-          existingValues: { hasCaption: false, data_align: '' },
-          saveCallback: this._insertSaveCallback,
-          dialogTitle: editor.config.drupalImage_dialogTitleAdd
-        };
-        if (this._selectionWillCreateInlineImage()) {
-          override.existingValues.isInline = this._selectionWillCreateInlineImage();
-          delete override.saveCallback;
-        }
-        editor.execCommand('drupalimage', override);
-      },
 
-      edit: function () {
-        var that = this;
-        var saveCallback = function (returnValues) {
           editor.fire('saveSnapshot');
-          // Set the updated widget data.
-          that.setData({
-            'data-editor-file-uuid': returnValues.attributes['data-editor-file-uuid'],
-            src: returnValues.attributes.src,
-            width: returnValues.attributes.width,
-            height: returnValues.attributes.height,
-            alt: returnValues.attributes.alt,
-            hasCaption: !!returnValues.hasCaption,
-            data_caption: returnValues.hasCaption ? that.data.data_caption : '',
-            data_align: returnValues.attributes.data_align === 'none' ? null : returnValues.attributes.data_align
-          });
+
+          // Build the HTML for the widget.
+          var html = '<img ';
+          for (var attr in returnValues.attributes) {
+            if (returnValues.attributes.hasOwnProperty(attr) && !attr.match(/^data_/)) {
+              html += attr + '="' + returnValues.attributes[attr] + '" ';
+              html += 'data-cke-saved-' + attr + '="' + returnValues.attributes[attr] + '" ';
+            }
+          }
+          if (returnValues.hasCaption) {
+            html += 'data-caption="" ';
+            html += ' data-cke-saved-data-caption=""';
+          }
+          if (returnValues.attributes.data_align && returnValues.attributes.data_align !== 'none') {
+            html += 'data-align="' + returnValues.attributes.data_align + '" ';
+            html += ' data-cke-saved-data-align="' + returnValues.attributes.data_align + '"';
+          }
+          html += ' />';
+          var el = new CKEDITOR.dom.element.createFromHtml(html, editor.document);
+          editor.insertElement(editor.widgets.wrapElement(el, 'drupalimagecaption'));
+
           // Save snapshot for undo support.
           editor.fire('saveSnapshot');
-        };
-        var override = {
-          imageDOMElement: this.parts.image.$,
-          existingValues: this.data,
-          saveCallback: saveCallback,
-          dialogTitle: this.data.src === '' ? editor.config.drupalImage_dialogTitleAdd : editor.config.drupalImage_dialogTitleEdit
-        };
-        editor.execCommand('drupalimage', override);
-      }
-    });
-  },
-
-  afterInit: function (editor) {
-    function setupAlignCommand (value) {
-      var command = editor.getCommand('justify' + value);
-      if (command) {
-        if (value in { right: 1, left: 1, center: 1 }) {
-          command.on('exec', function (event) {
-            var widget = getSelectedWidget(editor);
-            if (widget && widget.name === 'drupalimagecaption') {
-              widget.setData({ data_align: value });
-              event.cancel();
-            }
-          });
+
+          // Initialize and focus the widget.
+          var widget = editor.widgets.initOn(el, 'drupalimagecaption');
+          widget.focus();
+        },
+
+        insert: function () {
+          var override = {
+            imageDOMElement: null,
+            existingValues: { hasCaption: false, data_align: '' },
+            saveCallback: this._insertSaveCallback,
+            dialogTitle: editor.config.drupalImage_dialogTitleAdd
+          };
+          if (this._selectionWillCreateInlineImage()) {
+            override.existingValues.isInline = this._selectionWillCreateInlineImage();
+            delete override.saveCallback;
+          }
+          editor.execCommand('drupalimage', override);
+        },
+
+        edit: function () {
+          var that = this;
+          var saveCallback = function (returnValues) {
+            editor.fire('saveSnapshot');
+            // Set the updated widget data.
+            that.setData({
+              'data-editor-file-uuid': returnValues.attributes['data-editor-file-uuid'],
+              src: returnValues.attributes.src,
+              width: returnValues.attributes.width,
+              height: returnValues.attributes.height,
+              alt: returnValues.attributes.alt,
+              hasCaption: !!returnValues.hasCaption,
+              data_caption: returnValues.hasCaption ? that.data.data_caption : '',
+              data_align: returnValues.attributes.data_align === 'none' ? null : returnValues.attributes.data_align
+            });
+            // Save snapshot for undo support.
+            editor.fire('saveSnapshot');
+          };
+          var override = {
+            imageDOMElement: this.parts.image.$,
+            existingValues: this.data,
+            saveCallback: saveCallback,
+            dialogTitle: this.data.src === '' ? editor.config.drupalImage_dialogTitleAdd : editor.config.drupalImage_dialogTitleEdit
+          };
+          editor.execCommand('drupalimage', override);
         }
+      });
+    },
+
+    afterInit: function (editor) {
+      function setupAlignCommand(value) {
+        var command = editor.getCommand('justify' + value);
+        if (command) {
+          if (value in { right: 1, left: 1, center: 1 }) {
+            command.on('exec', function (event) {
+              var widget = getSelectedWidget(editor);
+              if (widget && widget.name === 'drupalimagecaption') {
+                widget.setData({ data_align: value });
+                event.cancel();
+              }
+            });
+          }
 
-        command.on('refresh', function (event) {
-          var widget = getSelectedWidget(editor),
-            allowed = { left: 1, center: 1, right: 1 },
-            align;
+          command.on('refresh', function (event) {
+            var widget = getSelectedWidget(editor),
+              allowed = { left: 1, center: 1, right: 1 },
+              align;
 
-          if (widget) {
-            align = widget.data.data_align;
+            if (widget) {
+              align = widget.data.data_align;
 
-            this.setState(
-              (align === value) ? CKEDITOR.TRISTATE_ON : (value in allowed) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED);
+              this.setState(
+                (align === value) ? CKEDITOR.TRISTATE_ON : (value in allowed) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED);
 
-            event.cancel();
-          }
-        });
+              event.cancel();
+            }
+          });
+        }
       }
-    }
 
-    function getSelectedWidget (editor) {
-      var widget = editor.widgets.focused;
-      if (widget && widget.name === 'drupalimagecaption') {
-        return widget;
+      function getSelectedWidget(editor) {
+        var widget = editor.widgets.focused;
+        if (widget && widget.name === 'drupalimagecaption') {
+          return widget;
+        }
+        return null;
       }
-      return null;
-    }
 
-    // Customize the behavior of the alignment commands.
-    setupAlignCommand('left');
-    setupAlignCommand('right');
-    setupAlignCommand('center');
-  }
-});
+      // Customize the behavior of the alignment commands.
+      setupAlignCommand('left');
+      setupAlignCommand('right');
+      setupAlignCommand('center');
+    }
+  });
 
 })(CKEDITOR);
diff --git a/core/modules/ckeditor/js/plugins/drupalimagecaption/theme.js b/core/modules/ckeditor/js/plugins/drupalimagecaption/theme.js
index b837f6d..21a9f90 100644
--- a/core/modules/ckeditor/js/plugins/drupalimagecaption/theme.js
+++ b/core/modules/ckeditor/js/plugins/drupalimagecaption/theme.js
@@ -5,218 +5,218 @@
 
 (function (CKEDITOR) {
 
-"use strict";
-
-CKEDITOR.on('instanceCreated', function (event) {
-  var editor = event.editor;
-
-  // Listen to widget definitions and customize them as needed. It's
-  // basically rewriting parts of the definition.
-  editor.on('widgetDefinition', function (event) {
-    var widgetDefinition = event.data;
-
-    // Customize the "drupalimagecaption" widget definition.
-    if (widgetDefinition.name === 'drupalimagecaption') {
-
-      widgetDefinition.template =
-        '<figure class="caption caption-img">' +
-          '<img src="" data-caption="" data-align="center" />' +
-          '<figcaption></figcaption>' +
-        '</figure>';
-
-      // Define the editables created by the overridden upcasting.
-      widgetDefinition.editables = {
-        caption: 'figcaption'
-      };
-
-      // Define the additional parts created by the overridden upcasting.
-      widgetDefinition.parts.caption = 'figcaption';
-
-      // Override "data" so we can make the new widget structure
-      // behave according to changes on data.
-      widgetDefinition.data = CKEDITOR.tools.override(widgetDefinition.data, function (originalDataFn) {
-        return function () {
-          // Call the original "data" implementation.
-          originalDataFn.apply(this, arguments);
-
-          // The image is wrapped in <figure>.
-          if (this.element.is('figure')) {
-            // The image is wrapped in <figure>, but it should no longer be.
-            if (!this.data.hasCaption && this.data.data_align === null) {
-              // Destroy this widget, so we can unwrap the <img>.
-              editor.widgets.destroy(this);
-              // Unwrap <img> from <figure>.
-              this.parts.image.replace(this.element);
-              // Reinitialize this widget with the current data.
-              editor.widgets.initOn(this.parts.image, 'drupalimagecaption', this.data);
-            }
-            // The image is wrapped in <figure>, as it should be; update it.
-            else {
-              // Set the caption visibility.
-              this.parts.caption.setStyle('display', this.data.hasCaption ? '' : 'none');
-
-              // Set the alignment, if any.
-              this.element.removeClass('caption-left');
-              this.element.removeClass('caption-center');
-              this.element.removeClass('caption-right');
-              if (this.data.data_align) {
-                this.element.addClass('caption-' + this.data.data_align);
-              }
-            }
-          }
-          // The image is not wrapped in <figure>.
-          else if (this.element.is('img')) {
-            // The image is not wrapped in <figure>, but it should be.
-            if (this.data.hasCaption) {
-              // Destroy this widget, so we can wrap the <img>.
-              editor.widgets.destroy(this);
-              // Replace the widget's element (the <img>) with the template (a
-              // <figure> wrapping an <img>) and then replace the the template's
-              // default <img> by our <img> so we won't lose attributes. We must
-              // do this manually because upcast() won't run.
-              var figure = CKEDITOR.dom.element.createFromHtml(this.template.output(), editor.document);
-              figure.replace(this.element);
-              this.element.replace(figure.findOne('img'));
-              // Reinitialize this widget with the current data.
-              editor.widgets.initOn(figure, 'drupalimagecaption', this.data);
-            }
-            else if (this.data.data_align !== null) {
-              this.element.addClass('align-' + this.data.data_align);
-            }
-          }
+  "use strict";
+
+  CKEDITOR.on('instanceCreated', function (event) {
+    var editor = event.editor;
+
+    // Listen to widget definitions and customize them as needed. It's
+    // basically rewriting parts of the definition.
+    editor.on('widgetDefinition', function (event) {
+      var widgetDefinition = event.data;
+
+      // Customize the "drupalimagecaption" widget definition.
+      if (widgetDefinition.name === 'drupalimagecaption') {
+
+        widgetDefinition.template =
+          '<figure class="caption caption-img">' +
+            '<img src="" data-caption="" data-align="center" />' +
+            '<figcaption></figcaption>' +
+            '</figure>';
+
+        // Define the editables created by the overridden upcasting.
+        widgetDefinition.editables = {
+          caption: 'figcaption'
         };
-      });
-
-      // Upcast to <figure> if data-caption or data-align is set.
-      widgetDefinition.upcast = CKEDITOR.tools.override(widgetDefinition.upcast, function (originalUpcastFn) {
-        return function (el) {
-          // Execute the original upcast first. If "true", this is an
-          // element to be upcasted.
-          if (originalUpcastFn.apply(this, arguments)) {
-            var figure;
-            var captionValue = el.attributes['data-caption'];
-            var alignValue = el.attributes['data-align'];
-
-            // Wrap image in <figure> only if data-caption is set.
-            if (captionValue !== undefined) {
-              var classes = 'caption caption-img';
-              if (alignValue !== null) {
-                classes += ' caption-' + alignValue;
+
+        // Define the additional parts created by the overridden upcasting.
+        widgetDefinition.parts.caption = 'figcaption';
+
+        // Override "data" so we can make the new widget structure
+        // behave according to changes on data.
+        widgetDefinition.data = CKEDITOR.tools.override(widgetDefinition.data, function (originalDataFn) {
+          return function () {
+            // Call the original "data" implementation.
+            originalDataFn.apply(this, arguments);
+
+            // The image is wrapped in <figure>.
+            if (this.element.is('figure')) {
+              // The image is wrapped in <figure>, but it should no longer be.
+              if (!this.data.hasCaption && this.data.data_align === null) {
+                // Destroy this widget, so we can unwrap the <img>.
+                editor.widgets.destroy(this);
+                // Unwrap <img> from <figure>.
+                this.parts.image.replace(this.element);
+                // Reinitialize this widget with the current data.
+                editor.widgets.initOn(this.parts.image, 'drupalimagecaption', this.data);
+              }
+              // The image is wrapped in <figure>, as it should be; update it.
+              else {
+                // Set the caption visibility.
+                this.parts.caption.setStyle('display', this.data.hasCaption ? '' : 'none');
+
+                // Set the alignment, if any.
+                this.element.removeClass('caption-left');
+                this.element.removeClass('caption-center');
+                this.element.removeClass('caption-right');
+                if (this.data.data_align) {
+                  this.element.addClass('caption-' + this.data.data_align);
+                }
               }
-              figure = el.wrapWith(new CKEDITOR.htmlParser.element('figure', { 'class' : classes }));
-              var caption = CKEDITOR.htmlParser.fragment.fromHtml(captionValue || '', 'figcaption');
-              figure.add(caption);
             }
-            else if (alignValue !== undefined) {
-              if (el.attributes['class'] === undefined) {
-                el.attributes['class'] = '';
+            // The image is not wrapped in <figure>.
+            else if (this.element.is('img')) {
+              // The image is not wrapped in <figure>, but it should be.
+              if (this.data.hasCaption) {
+                // Destroy this widget, so we can wrap the <img>.
+                editor.widgets.destroy(this);
+                // Replace the widget's element (the <img>) with the template (a
+                // <figure> wrapping an <img>) and then replace the the template's
+                // default <img> by our <img> so we won't lose attributes. We must
+                // do this manually because upcast() won't run.
+                var figure = CKEDITOR.dom.element.createFromHtml(this.template.output(), editor.document);
+                figure.replace(this.element);
+                this.element.replace(figure.findOne('img'));
+                // Reinitialize this widget with the current data.
+                editor.widgets.initOn(figure, 'drupalimagecaption', this.data);
+              }
+              else if (this.data.data_align !== null) {
+                this.element.addClass('align-' + this.data.data_align);
               }
-              el.attributes['class'] += 'align-' + alignValue;
             }
+          };
+        });
+
+        // Upcast to <figure> if data-caption or data-align is set.
+        widgetDefinition.upcast = CKEDITOR.tools.override(widgetDefinition.upcast, function (originalUpcastFn) {
+          return function (el) {
+            // Execute the original upcast first. If "true", this is an
+            // element to be upcasted.
+            if (originalUpcastFn.apply(this, arguments)) {
+              var figure;
+              var captionValue = el.attributes['data-caption'];
+              var alignValue = el.attributes['data-align'];
+
+              // Wrap image in <figure> only if data-caption is set.
+              if (captionValue !== undefined) {
+                var classes = 'caption caption-img';
+                if (alignValue !== null) {
+                  classes += ' caption-' + alignValue;
+                }
+                figure = el.wrapWith(new CKEDITOR.htmlParser.element('figure', { 'class': classes }));
+                var caption = CKEDITOR.htmlParser.fragment.fromHtml(captionValue || '', 'figcaption');
+                figure.add(caption);
+              }
+              else if (alignValue !== undefined) {
+                if (el.attributes['class'] === undefined) {
+                  el.attributes['class'] = '';
+                }
+                el.attributes['class'] += 'align-' + alignValue;
+              }
 
-            return figure || el;
-          }
-        };
-      });
-
-      // Downcast to <img>.
-      widgetDefinition.downcast = CKEDITOR.tools.override(widgetDefinition.downcast, function (originalDowncastFn) {
-        return function (el) {
-          if (el.name === 'figure') {
-            // Update data with the current caption.
-            var caption = el.getFirst('figcaption');
-            caption = caption ? caption.getHtml() : '';
-            this.setData({
-              data_caption: caption
-            });
-
-            // We downcast to just the <img> element.
-            el = el.getFirst('img');
-          }
-
-          // Call the original downcast to setup the <img>
-          // meta data accordingly.
-          return originalDowncastFn.call(this, el) || el;
-        };
-      });
-
-      // Generate a <figure>-wrapped <img> if either data-caption or data-align
-      // are set for a newly created image.
-      widgetDefinition.insert = CKEDITOR.tools.override(widgetDefinition.downcast, function (originalInsertFn) {
-        return function () {
-          var saveCallback = function (returnValues) {
-            // We can't create an image with an empty "src" attribute.
-            if (returnValues.attributes.src.length === 0) {
-              return;
+              return figure || el;
             }
-            // Normalize the "data_align" attribute and the "hasCaption" value.
-            if (returnValues.attributes.data_align === '' || returnValues.attributes.data_align === 'none') {
-              returnValues.attributes.data_align = null;
-            }
-            if (typeof returnValues.hasCaption === 'number') {
-              returnValues.hasCaption = !!returnValues.hasCaption;
-            }
-            // Use the original save callback if the image has no caption.
-            if (returnValues.hasCaption === false) {
-              widgetDefinition._insertSaveCallback.apply(this, arguments);
-              return;
+          };
+        });
+
+        // Downcast to <img>.
+        widgetDefinition.downcast = CKEDITOR.tools.override(widgetDefinition.downcast, function (originalDowncastFn) {
+          return function (el) {
+            if (el.name === 'figure') {
+              // Update data with the current caption.
+              var caption = el.getFirst('figcaption');
+              caption = caption ? caption.getHtml() : '';
+              this.setData({
+                data_caption: caption
+              });
+
+              // We downcast to just the <img> element.
+              el = el.getFirst('img');
             }
 
-            editor.fire('saveSnapshot');
+            // Call the original downcast to setup the <img>
+            // meta data accordingly.
+            return originalDowncastFn.call(this, el) || el;
+          };
+        });
+
+        // Generate a <figure>-wrapped <img> if either data-caption or data-align
+        // are set for a newly created image.
+        widgetDefinition.insert = CKEDITOR.tools.override(widgetDefinition.downcast, function (originalInsertFn) {
+          return function () {
+            var saveCallback = function (returnValues) {
+              // We can't create an image with an empty "src" attribute.
+              if (returnValues.attributes.src.length === 0) {
+                return;
+              }
+              // Normalize the "data_align" attribute and the "hasCaption" value.
+              if (returnValues.attributes.data_align === '' || returnValues.attributes.data_align === 'none') {
+                returnValues.attributes.data_align = null;
+              }
+              if (typeof returnValues.hasCaption === 'number') {
+                returnValues.hasCaption = !!returnValues.hasCaption;
+              }
+              // Use the original save callback if the image has no caption.
+              if (returnValues.hasCaption === false) {
+                widgetDefinition._insertSaveCallback.apply(this, arguments);
+                return;
+              }
 
-            // Build the HTML for the widget.
-            var html = '<figure class="caption caption-img';
-            if (returnValues.attributes.data_align && returnValues.attributes.data_align !== 'none') {
-              html += ' caption-' + returnValues.attributes.data_align;
-            }
-            html += '"><img ';
-            for (var attr in returnValues.attributes) {
-              if (returnValues.attributes.hasOwnProperty(attr) && !attr.match(/^data_/)) {
-                html += attr + '="' + returnValues.attributes[attr] + '" ';
-                html += 'data-cke-saved-' + attr + '="' + returnValues.attributes[attr] + '" ';
+              editor.fire('saveSnapshot');
+
+              // Build the HTML for the widget.
+              var html = '<figure class="caption caption-img';
+              if (returnValues.attributes.data_align && returnValues.attributes.data_align !== 'none') {
+                html += ' caption-' + returnValues.attributes.data_align;
               }
+              html += '"><img ';
+              for (var attr in returnValues.attributes) {
+                if (returnValues.attributes.hasOwnProperty(attr) && !attr.match(/^data_/)) {
+                  html += attr + '="' + returnValues.attributes[attr] + '" ';
+                  html += 'data-cke-saved-' + attr + '="' + returnValues.attributes[attr] + '" ';
+                }
+              }
+              // The init() method will run on this and if it does not find
+              // data-caption or data-align attributes, the subsequent call to the
+              // data() method will cause the <figure> to be transformed back to
+              // an <img>. Hence, set the data-caption and data-align attributes
+              // on the newly inserted <img>.
+              if (returnValues.hasCaption) {
+                html += ' data-caption=""';
+                html += ' data-cke-saved-data-caption=""';
+              }
+              if (returnValues.attributes.data_align && returnValues.attributes.data_align !== 'none') {
+                html += ' data-align="' + returnValues.attributes.data_align + '"';
+                html += ' data-cke-saved-data-align="' + returnValues.attributes.data_align + '"';
+              }
+              html += '/>';
+              html += '<figcaption data-placeholder="' + Drupal.t('Enter caption here') + '"></figcaption>';
+              html += '</figure>';
+              var el = new CKEDITOR.dom.element.createFromHtml(html, editor.document);
+              editor.insertElement(editor.widgets.wrapElement(el, 'drupalimagecaption'));
+
+              // Save snapshot for undo support.
+              editor.fire('saveSnapshot');
+
+              // Initialize and focus the widget.
+              var widget = editor.widgets.initOn(el, 'drupalimagecaption');
+              widget.focus();
+            };
+            var override = {
+              imageDOMElement: null,
+              existingValues: { hasCaption: false, data_align: '' },
+              saveCallback: saveCallback,
+              dialogTitle: editor.config.drupalImage_dialogTitleAdd
+            };
+            if (this._selectionWillCreateInlineImage()) {
+              override.existingValues.isInline = this._selectionWillCreateInlineImage();
+              delete override.saveCallback;
             }
-            // The init() method will run on this and if it does not find
-            // data-caption or data-align attributes, the subsequent call to the
-            // data() method will cause the <figure> to be transformed back to
-            // an <img>. Hence, set the data-caption and data-align attributes
-            // on the newly inserted <img>.
-            if (returnValues.hasCaption) {
-              html += ' data-caption=""';
-              html += ' data-cke-saved-data-caption=""';
-            }
-            if (returnValues.attributes.data_align && returnValues.attributes.data_align !== 'none') {
-              html += ' data-align="' + returnValues.attributes.data_align + '"';
-              html += ' data-cke-saved-data-align="' + returnValues.attributes.data_align + '"';
-            }
-            html += '/>';
-            html += '<figcaption data-placeholder="' + Drupal.t('Enter caption here') + '"></figcaption>';
-            html += '</figure>';
-            var el = new CKEDITOR.dom.element.createFromHtml(html, editor.document);
-            editor.insertElement(editor.widgets.wrapElement(el, 'drupalimagecaption'));
-
-            // Save snapshot for undo support.
-            editor.fire('saveSnapshot');
-
-            // Initialize and focus the widget.
-            var widget = editor.widgets.initOn(el, 'drupalimagecaption');
-            widget.focus();
+            editor.execCommand('drupalimage', override);
           };
-          var override = {
-            imageDOMElement: null,
-            existingValues: { hasCaption: false, data_align: '' },
-            saveCallback: saveCallback,
-            dialogTitle: editor.config.drupalImage_dialogTitleAdd
-          };
-          if (this._selectionWillCreateInlineImage()) {
-            override.existingValues.isInline = this._selectionWillCreateInlineImage();
-            delete override.saveCallback;
-          }
-          editor.execCommand('drupalimage', override);
-        };
-      });
-    }
+        });
+      }
+    });
   });
-});
 
 })(CKEDITOR);
diff --git a/core/modules/ckeditor/js/plugins/drupallink/plugin.js b/core/modules/ckeditor/js/plugins/drupallink/plugin.js
index cf0e06f..f88c902 100644
--- a/core/modules/ckeditor/js/plugins/drupallink/plugin.js
+++ b/core/modules/ckeditor/js/plugins/drupallink/plugin.js
@@ -5,222 +5,222 @@
 
 (function ($, Drupal, drupalSettings, CKEDITOR) {
 
-"use strict";
-
-CKEDITOR.plugins.add('drupallink', {
-  init: function (editor) {
-    // Add the commands for link and unlink.
-    editor.addCommand('drupallink', {
-      allowedContent: 'a[!href,target]',
-      requiredContent: 'a[href]',
-      modes: { wysiwyg : 1 },
-      canUndo: true,
-      exec: function (editor) {
-        var linkElement = getSelectedLink(editor);
-        var linkDOMElement = null;
-
-        // Set existing values based on selected element.
-        var existingValues = {};
-        if (linkElement && linkElement.$) {
-          linkDOMElement = linkElement.$;
-
-          // Populate an array with the link's current attributes.
-          var attribute = null, attributeName;
-          for (var key = 0; key < linkDOMElement.attributes.length; key++) {
-            attribute = linkDOMElement.attributes.item(key);
-            attributeName = attribute.nodeName.toLowerCase();
-            // Don't consider data-cke-saved- attributes; they're just there to
-            // work around browser quirks.
-            if (attributeName.substring(0, 15) === 'data-cke-saved-') {
-              continue;
+  "use strict";
+
+  CKEDITOR.plugins.add('drupallink', {
+    init: function (editor) {
+      // Add the commands for link and unlink.
+      editor.addCommand('drupallink', {
+        allowedContent: 'a[!href,target]',
+        requiredContent: 'a[href]',
+        modes: { wysiwyg: 1 },
+        canUndo: true,
+        exec: function (editor) {
+          var linkElement = getSelectedLink(editor);
+          var linkDOMElement = null;
+
+          // Set existing values based on selected element.
+          var existingValues = {};
+          if (linkElement && linkElement.$) {
+            linkDOMElement = linkElement.$;
+
+            // Populate an array with the link's current attributes.
+            var attribute = null, attributeName;
+            for (var key = 0; key < linkDOMElement.attributes.length; key++) {
+              attribute = linkDOMElement.attributes.item(key);
+              attributeName = attribute.nodeName.toLowerCase();
+              // Don't consider data-cke-saved- attributes; they're just there to
+              // work around browser quirks.
+              if (attributeName.substring(0, 15) === 'data-cke-saved-') {
+                continue;
+              }
+              // Store the value for this attribute, unless there's a
+              // data-cke-saved- alternative for it, which will contain the quirk-
+              // free, original value.
+              existingValues[attributeName] = linkElement.data('cke-saved-' + attributeName) || attribute.nodeValue;
             }
-            // Store the value for this attribute, unless there's a
-            // data-cke-saved- alternative for it, which will contain the quirk-
-            // free, original value.
-            existingValues[attributeName] = linkElement.data('cke-saved-' + attributeName) || attribute.nodeValue;
           }
-        }
 
-        // Prepare a save callback to be used upon saving the dialog.
-        var saveCallback = function (returnValues) {
-          editor.fire('saveSnapshot');
-
-          // Create a new link element if needed.
-          if (!linkElement && returnValues.attributes.href) {
-            var selection = editor.getSelection();
-            var range = selection.getRanges(1)[0];
-
-            // Use link URL as text with a collapsed cursor.
-            if (range.collapsed) {
-              // Shorten mailto URLs to just the e-mail address.
-              var text = new CKEDITOR.dom.text(returnValues.attributes.href.replace(/^mailto:/, ''), editor.document);
-              range.insertNode(text);
-              range.selectNodeContents(text);
-            }
+          // Prepare a save callback to be used upon saving the dialog.
+          var saveCallback = function (returnValues) {
+            editor.fire('saveSnapshot');
+
+            // Create a new link element if needed.
+            if (!linkElement && returnValues.attributes.href) {
+              var selection = editor.getSelection();
+              var range = selection.getRanges(1)[0];
+
+              // Use link URL as text with a collapsed cursor.
+              if (range.collapsed) {
+                // Shorten mailto URLs to just the e-mail address.
+                var text = new CKEDITOR.dom.text(returnValues.attributes.href.replace(/^mailto:/, ''), editor.document);
+                range.insertNode(text);
+                range.selectNodeContents(text);
+              }
 
-            // Ignore a disabled target attribute.
-            if (returnValues.attributes.target === 0) {
-              delete returnValues.attributes.target;
-            }
+              // Ignore a disabled target attribute.
+              if (returnValues.attributes.target === 0) {
+                delete returnValues.attributes.target;
+              }
 
-            // Create the new link by applying a style to the new text.
-            var style = new CKEDITOR.style({ element: 'a', attributes: returnValues.attributes });
-            style.type = CKEDITOR.STYLE_INLINE;
-            style.applyToRange(range);
-            range.select();
+              // Create the new link by applying a style to the new text.
+              var style = new CKEDITOR.style({ element: 'a', attributes: returnValues.attributes });
+              style.type = CKEDITOR.STYLE_INLINE;
+              style.applyToRange(range);
+              range.select();
 
-            // Set the link so individual properties may be set below.
-            linkElement = getSelectedLink(editor);
-          }
-          // Update the link properties.
-          else if (linkElement) {
-            for (var key in returnValues.attributes) {
-              if (returnValues.attributes.hasOwnProperty(key)) {
-                // Update the property if a value is specified.
-                if (returnValues.attributes[key].length > 0) {
-                  var value = returnValues.attributes[key];
-                  linkElement.data('cke-saved-' + key, value);
-                  linkElement.setAttribute(key, value);
-                }
-                // Delete the property if set to an empty string.
-                else {
-                  linkElement.removeAttribute(key);
+              // Set the link so individual properties may be set below.
+              linkElement = getSelectedLink(editor);
+            }
+            // Update the link properties.
+            else if (linkElement) {
+              for (var key in returnValues.attributes) {
+                if (returnValues.attributes.hasOwnProperty(key)) {
+                  // Update the property if a value is specified.
+                  if (returnValues.attributes[key].length > 0) {
+                    var value = returnValues.attributes[key];
+                    linkElement.data('cke-saved-' + key, value);
+                    linkElement.setAttribute(key, value);
+                  }
+                  // Delete the property if set to an empty string.
+                  else {
+                    linkElement.removeAttribute(key);
+                  }
                 }
               }
             }
-          }
 
-          // Save snapshot for undo support.
-          editor.fire('saveSnapshot');
-        };
-        // Drupal.t() will not work inside CKEditor plugins because CKEditor
-        // loads the JavaScript file instead of Drupal. Pull translated strings
-        // from the plugin settings that are translated server-side.
-        var dialogSettings = {
-          title: linkElement ? editor.config.drupalLink_dialogTitleEdit : editor.config.drupalLink_dialogTitleAdd,
-          dialogClass: 'editor-link-dialog'
-        };
-
-        // Open the dialog for the edit form.
-        Drupal.ckeditor.openDialog(editor, Drupal.url('editor/dialog/link/' + editor.config.drupal.format), existingValues, saveCallback, dialogSettings);
-      }
-    });
-    editor.addCommand('drupalunlink', {
-      contextSensitive: 1,
-      startDisabled: 1,
-      allowedContent: 'a[!href]',
-      requiredContent: 'a[href]',
-      exec: function (editor) {
-        var style = new CKEDITOR.style({ element:'a', type: CKEDITOR.STYLE_INLINE, alwaysRemoveElement: 1 });
-        editor.removeStyle(style);
-      },
-      refresh: function ( editor, path ) {
-        var element = path.lastElement && path.lastElement.getAscendant('a', true);
-        if (element && element.getName() === 'a' && element.getAttribute('href') && element.getChildCount()) {
-          this.setState(CKEDITOR.TRISTATE_OFF);
-        }
-        else {
-          this.setState(CKEDITOR.TRISTATE_DISABLED);
+            // Save snapshot for undo support.
+            editor.fire('saveSnapshot');
+          };
+          // Drupal.t() will not work inside CKEditor plugins because CKEditor
+          // loads the JavaScript file instead of Drupal. Pull translated strings
+          // from the plugin settings that are translated server-side.
+          var dialogSettings = {
+            title: linkElement ? editor.config.drupalLink_dialogTitleEdit : editor.config.drupalLink_dialogTitleAdd,
+            dialogClass: 'editor-link-dialog'
+          };
+
+          // Open the dialog for the edit form.
+          Drupal.ckeditor.openDialog(editor, Drupal.url('editor/dialog/link/' + editor.config.drupal.format), existingValues, saveCallback, dialogSettings);
         }
-      }
-    });
-
-    editor.setKeystroke(CKEDITOR.CTRL + 75 /*K*/, 'drupallink');
-
-    // Add buttons for link and unlink.
-    if (editor.ui.addButton) {
-      editor.ui.addButton('DrupalLink', {
-        label: Drupal.t('Link'),
-        command: 'drupallink',
-        icon: this.path.replace(/plugin\.js.*/, 'link.png')
       });
-      editor.ui.addButton('DrupalUnlink', {
-        label: Drupal.t('Unlink'),
-        command: 'drupalunlink',
-        icon: this.path.replace(/plugin\.js.*/, 'unlink.png')
+      editor.addCommand('drupalunlink', {
+        contextSensitive: 1,
+        startDisabled: 1,
+        allowedContent: 'a[!href]',
+        requiredContent: 'a[href]',
+        exec: function (editor) {
+          var style = new CKEDITOR.style({ element: 'a', type: CKEDITOR.STYLE_INLINE, alwaysRemoveElement: 1 });
+          editor.removeStyle(style);
+        },
+        refresh: function (editor, path) {
+          var element = path.lastElement && path.lastElement.getAscendant('a', true);
+          if (element && element.getName() === 'a' && element.getAttribute('href') && element.getChildCount()) {
+            this.setState(CKEDITOR.TRISTATE_OFF);
+          }
+          else {
+            this.setState(CKEDITOR.TRISTATE_DISABLED);
+          }
+        }
       });
-    }
-
-    editor.on('doubleclick', function (evt) {
-      var element = getSelectedLink(editor) || evt.data.element;
 
-      if (!element.isReadOnly()) {
-        if (element.is('a')) {
-          editor.getSelection().selectElement(element);
-          editor.getCommand('drupallink').exec();
-        }
-      }
-    });
+      editor.setKeystroke(CKEDITOR.CTRL + 75 /*K*/, 'drupallink');
 
-    // If the "menu" plugin is loaded, register the menu items.
-    if (editor.addMenuItems) {
-      editor.addMenuItems({
-        link: {
-          label: Drupal.t('Edit Link'),
+      // Add buttons for link and unlink.
+      if (editor.ui.addButton) {
+        editor.ui.addButton('DrupalLink', {
+          label: Drupal.t('Link'),
           command: 'drupallink',
-          group: 'link',
-          order: 1
-        },
-
-        unlink: {
+          icon: this.path.replace(/plugin\.js.*/, 'link.png')
+        });
+        editor.ui.addButton('DrupalUnlink', {
           label: Drupal.t('Unlink'),
           command: 'drupalunlink',
-          group: 'link',
-          order: 5
-        }
-      });
-    }
+          icon: this.path.replace(/plugin\.js.*/, 'unlink.png')
+        });
+      }
 
-    // If the "contextmenu" plugin is loaded, register the listeners.
-    if (editor.contextMenu) {
-      editor.contextMenu.addListener(function (element, selection) {
-        if (!element || element.isReadOnly()) {
-          return null;
-        }
-        var anchor = getSelectedLink(editor);
-        if (!anchor) {
-          return null;
-        }
+      editor.on('doubleclick', function (evt) {
+        var element = getSelectedLink(editor) || evt.data.element;
 
-        var menu = {};
-        if (anchor.getAttribute('href') && anchor.getChildCount()) {
-          menu = { link: CKEDITOR.TRISTATE_OFF, unlink: CKEDITOR.TRISTATE_OFF };
+        if (!element.isReadOnly()) {
+          if (element.is('a')) {
+            editor.getSelection().selectElement(element);
+            editor.getCommand('drupallink').exec();
+          }
         }
-        return menu;
       });
-    }
-  }
-});
 
+      // If the "menu" plugin is loaded, register the menu items.
+      if (editor.addMenuItems) {
+        editor.addMenuItems({
+          link: {
+            label: Drupal.t('Edit Link'),
+            command: 'drupallink',
+            group: 'link',
+            order: 1
+          },
+
+          unlink: {
+            label: Drupal.t('Unlink'),
+            command: 'drupalunlink',
+            group: 'link',
+            order: 5
+          }
+        });
+      }
 
-/**
- * Get the surrounding link element of current selection.
- *
- * The following selection will all return the link element.
- *
- *  <a href="#">li^nk</a>
- *  <a href="#">[link]</a>
- *  text[<a href="#">link]</a>
- *  <a href="#">li[nk</a>]
- *  [<b><a href="#">li]nk</a></b>]
- *  [<a href="#"><b>li]nk</b></a>
- *
- * @param {CKEDITOR.editor} editor
- */
-function getSelectedLink(editor) {
-  var selection = editor.getSelection();
-  var selectedElement = selection.getSelectedElement();
-  if (selectedElement && selectedElement.is('a')) {
-    return selectedElement;
-  }
+      // If the "contextmenu" plugin is loaded, register the listeners.
+      if (editor.contextMenu) {
+        editor.contextMenu.addListener(function (element, selection) {
+          if (!element || element.isReadOnly()) {
+            return null;
+          }
+          var anchor = getSelectedLink(editor);
+          if (!anchor) {
+            return null;
+          }
 
-  var range = selection.getRanges(true)[0];
+          var menu = {};
+          if (anchor.getAttribute('href') && anchor.getChildCount()) {
+            menu = { link: CKEDITOR.TRISTATE_OFF, unlink: CKEDITOR.TRISTATE_OFF };
+          }
+          return menu;
+        });
+      }
+    }
+  });
+
+
+  /**
+   * Get the surrounding link element of current selection.
+   *
+   * The following selection will all return the link element.
+   *
+   *  <a href="#">li^nk</a>
+   *  <a href="#">[link]</a>
+   *  text[<a href="#">link]</a>
+   *  <a href="#">li[nk</a>]
+   *  [<b><a href="#">li]nk</a></b>]
+   *  [<a href="#"><b>li]nk</b></a>
+   *
+   * @param {CKEDITOR.editor} editor
+   */
+  function getSelectedLink(editor) {
+    var selection = editor.getSelection();
+    var selectedElement = selection.getSelectedElement();
+    if (selectedElement && selectedElement.is('a')) {
+      return selectedElement;
+    }
 
-  if (range) {
-    range.shrink(CKEDITOR.SHRINK_TEXT);
-    return editor.elementPath(range.getCommonAncestor()).contains('a', 1);
+    var range = selection.getRanges(true)[0];
+
+    if (range) {
+      range.shrink(CKEDITOR.SHRINK_TEXT);
+      return editor.elementPath(range.getCommonAncestor()).contains('a', 1);
+    }
+    return null;
   }
-  return null;
-}
 
 })(jQuery, Drupal, drupalSettings, CKEDITOR);
diff --git a/core/modules/color/color.js b/core/modules/color/color.js
index 704fc1c..62eab38 100644
--- a/core/modules/color/color.js
+++ b/core/modules/color/color.js
@@ -5,255 +5,255 @@
 
 (function ($) {
 
-"use strict";
+  "use strict";
 
-Drupal.behaviors.color = {
-  attach: function (context, settings) {
-    var i, j, colors;
-    // This behavior attaches by ID, so is only valid once on a page.
-    var form = $(context).find('#system-theme-settings .color-form').once('color');
-    if (form.length === 0) {
-      return;
-    }
-    var inputs = [];
-    var hooks = [];
-    var locks = [];
-    var focused = null;
+  Drupal.behaviors.color = {
+    attach: function (context, settings) {
+      var i, j, colors;
+      // This behavior attaches by ID, so is only valid once on a page.
+      var form = $(context).find('#system-theme-settings .color-form').once('color');
+      if (form.length === 0) {
+        return;
+      }
+      var inputs = [];
+      var hooks = [];
+      var locks = [];
+      var focused = null;
 
-    // Add Farbtastic.
-    $('<div id="placeholder"></div>').once('color').prependTo(form);
-    var farb = $.farbtastic('#placeholder');
+      // Add Farbtastic.
+      $('<div id="placeholder"></div>').once('color').prependTo(form);
+      var farb = $.farbtastic('#placeholder');
 
-    // Decode reference colors to HSL.
-    var reference = settings.color.reference;
-    for (i in reference) {
-      if (reference.hasOwnProperty(i)) {
-        reference[i] = farb.RGBToHSL(farb.unpack(reference[i]));
+      // Decode reference colors to HSL.
+      var reference = settings.color.reference;
+      for (i in reference) {
+        if (reference.hasOwnProperty(i)) {
+          reference[i] = farb.RGBToHSL(farb.unpack(reference[i]));
+        }
       }
-    }
 
-    // Build a preview.
-    var height = [];
-    var width = [];
-    // Loop through all defined gradients.
-    for (i in settings.gradients) {
-      if (settings.gradients.hasOwnProperty(i)) {
-        // Add element to display the gradient.
-        $('#preview').once('color').append('<div id="gradient-' + i + '"></div>');
-        var gradient = $('#preview #gradient-' + i);
-        // Add height of current gradient to the list (divided by 10).
-        height.push(parseInt(gradient.css('height'), 10) / 10);
-        // Add width of current gradient to the list (divided by 10).
-        width.push(parseInt(gradient.css('width'), 10) / 10);
-        // Add rows (or columns for horizontal gradients).
-        // Each gradient line should have a height (or width for horizontal
-        // gradients) of 10px (because we divided the height/width by 10 above).
-        for (j = 0; j < (settings.gradients[i].direction === 'vertical' ? height[i] : width[i]); ++j) {
-          gradient.append('<div class="gradient-line"></div>');
+      // Build a preview.
+      var height = [];
+      var width = [];
+      // Loop through all defined gradients.
+      for (i in settings.gradients) {
+        if (settings.gradients.hasOwnProperty(i)) {
+          // Add element to display the gradient.
+          $('#preview').once('color').append('<div id="gradient-' + i + '"></div>');
+          var gradient = $('#preview #gradient-' + i);
+          // Add height of current gradient to the list (divided by 10).
+          height.push(parseInt(gradient.css('height'), 10) / 10);
+          // Add width of current gradient to the list (divided by 10).
+          width.push(parseInt(gradient.css('width'), 10) / 10);
+          // Add rows (or columns for horizontal gradients).
+          // Each gradient line should have a height (or width for horizontal
+          // gradients) of 10px (because we divided the height/width by 10 above).
+          for (j = 0; j < (settings.gradients[i].direction === 'vertical' ? height[i] : width[i]); ++j) {
+            gradient.append('<div class="gradient-line"></div>');
+          }
         }
       }
-    }
 
-    // Set up colorScheme selector.
-    form.find('#edit-scheme').on('change', function () {
-      var schemes = settings.color.schemes, colorScheme = this.options[this.selectedIndex].value;
-      if (colorScheme !== '' && schemes[colorScheme]) {
-        // Get colors of active scheme.
-        colors = schemes[colorScheme];
-        for (var fieldName in colors) {
-          if (colors.hasOwnProperty(fieldName)) {
-            callback($('#edit-palette-' + fieldName), colors[fieldName], false, true);
+      // Set up colorScheme selector.
+      form.find('#edit-scheme').on('change', function () {
+        var schemes = settings.color.schemes, colorScheme = this.options[this.selectedIndex].value;
+        if (colorScheme !== '' && schemes[colorScheme]) {
+          // Get colors of active scheme.
+          colors = schemes[colorScheme];
+          for (var fieldName in colors) {
+            if (colors.hasOwnProperty(fieldName)) {
+              callback($('#edit-palette-' + fieldName), colors[fieldName], false, true);
+            }
           }
+          preview();
         }
-        preview();
-      }
-    });
+      });
 
-    /**
-     * Renders the preview.
-     */
-    function preview() {
-      Drupal.color.callback(context, settings, form, farb, height, width);
-    }
+      /**
+       * Renders the preview.
+       */
+      function preview() {
+        Drupal.color.callback(context, settings, form, farb, height, width);
+      }
 
-    /**
-     * Shifts a given color, using a reference pair (ref in HSL).
-     *
-     * This algorithm ensures relative ordering on the saturation and luminance
-     * axes is preserved, and performs a simple hue shift.
-     *
-     * It is also symmetrical. If: shift_color(c, a, b) === d, then
-     * shift_color(d, b, a) === c.
-     */
-    function shift_color(given, ref1, ref2) {
-      var d;
-      // Convert to HSL.
-      given = farb.RGBToHSL(farb.unpack(given));
+      /**
+       * Shifts a given color, using a reference pair (ref in HSL).
+       *
+       * This algorithm ensures relative ordering on the saturation and luminance
+       * axes is preserved, and performs a simple hue shift.
+       *
+       * It is also symmetrical. If: shift_color(c, a, b) === d, then
+       * shift_color(d, b, a) === c.
+       */
+      function shift_color(given, ref1, ref2) {
+        var d;
+        // Convert to HSL.
+        given = farb.RGBToHSL(farb.unpack(given));
 
-      // Hue: apply delta.
-      given[0] += ref2[0] - ref1[0];
+        // Hue: apply delta.
+        given[0] += ref2[0] - ref1[0];
 
-      // Saturation: interpolate.
-      if (ref1[1] === 0 || ref2[1] === 0) {
-        given[1] = ref2[1];
-      }
-      else {
-        d = ref1[1] / ref2[1];
-        if (d > 1) {
-          given[1] /= d;
+        // Saturation: interpolate.
+        if (ref1[1] === 0 || ref2[1] === 0) {
+          given[1] = ref2[1];
         }
         else {
-          given[1] = 1 - (1 - given[1]) * d;
+          d = ref1[1] / ref2[1];
+          if (d > 1) {
+            given[1] /= d;
+          }
+          else {
+            given[1] = 1 - (1 - given[1]) * d;
+          }
         }
-      }
 
-      // Luminance: interpolate.
-      if (ref1[2] === 0 || ref2[2] === 0) {
-        given[2] = ref2[2];
-      }
-      else {
-        d = ref1[2] / ref2[2];
-        if (d > 1) {
-          given[2] /= d;
+        // Luminance: interpolate.
+        if (ref1[2] === 0 || ref2[2] === 0) {
+          given[2] = ref2[2];
         }
         else {
-          given[2] = 1 - (1 - given[2]) * d;
+          d = ref1[2] / ref2[2];
+          if (d > 1) {
+            given[2] /= d;
+          }
+          else {
+            given[2] = 1 - (1 - given[2]) * d;
+          }
         }
-      }
 
-      return farb.pack(farb.HSLToRGB(given));
-    }
+        return farb.pack(farb.HSLToRGB(given));
+      }
 
-    /**
-     * Callback for Farbtastic when a new color is chosen.
-     */
-    function callback(input, color, propagate, colorScheme) {
-      var matched;
-      // Set background/foreground colors.
-      $(input).css({
-        backgroundColor: color,
-        'color': farb.RGBToHSL(farb.unpack(color))[2] > 0.5 ? '#000' : '#fff'
-      });
+      /**
+       * Callback for Farbtastic when a new color is chosen.
+       */
+      function callback(input, color, propagate, colorScheme) {
+        var matched;
+        // Set background/foreground colors.
+        $(input).css({
+          backgroundColor: color,
+          'color': farb.RGBToHSL(farb.unpack(color))[2] > 0.5 ? '#000' : '#fff'
+        });
 
-      // Change input value.
-      if ($(input).val() && $(input).val() !== color) {
-        $(input).val(color);
+        // Change input value.
+        if ($(input).val() && $(input).val() !== color) {
+          $(input).val(color);
 
-        // Update locked values.
-        if (propagate) {
-          i = input.i;
-          for (j = i + 1; ; ++j) {
-            if (!locks[j - 1] || $(locks[j - 1]).is('.unlocked')) {
-              break;
+          // Update locked values.
+          if (propagate) {
+            i = input.i;
+            for (j = i + 1; ; ++j) {
+              if (!locks[j - 1] || $(locks[j - 1]).is('.unlocked')) {
+                break;
+              }
+              matched = shift_color(color, reference[input.key], reference[inputs[j].key]);
+              callback(inputs[j], matched, false);
             }
-            matched = shift_color(color, reference[input.key], reference[inputs[j].key]);
-            callback(inputs[j], matched, false);
-          }
-          for (j = i - 1; ; --j) {
-            if (!locks[j] || $(locks[j]).is('.unlocked')) {
-              break;
+            for (j = i - 1; ; --j) {
+              if (!locks[j] || $(locks[j]).is('.unlocked')) {
+                break;
+              }
+              matched = shift_color(color, reference[input.key], reference[inputs[j].key]);
+              callback(inputs[j], matched, false);
             }
-            matched = shift_color(color, reference[input.key], reference[inputs[j].key]);
-            callback(inputs[j], matched, false);
+
+            // Update preview.
+            preview();
           }
 
-          // Update preview.
-          preview();
+          // Reset colorScheme selector.
+          if (!colorScheme) {
+            resetScheme();
+          }
         }
+      }
 
-        // Reset colorScheme selector.
-        if (!colorScheme) {
-          resetScheme();
-        }
+      /**
+       * Resets the color scheme selector.
+       */
+      function resetScheme() {
+        form.find('#edit-scheme').each(function () {
+          this.selectedIndex = this.options.length - 1;
+        });
       }
-    }
 
-    /**
-     * Resets the color scheme selector.
-     */
-    function resetScheme() {
-      form.find('#edit-scheme').each(function () {
-        this.selectedIndex = this.options.length - 1;
-      });
-    }
+      /**
+       * Focuses Farbtastic on a particular field.
+       */
+      function focus(e) {
+        var input = e.target;
+        // Remove old bindings.
+        if (focused) {
+          $(focused).off('keyup', farb.updateValue)
+            .off('keyup', preview).off('keyup', resetScheme)
+            .parent().removeClass('item-selected');
+        }
 
-    /**
-     * Focuses Farbtastic on a particular field.
-     */
-    function focus(e) {
-      var input = e.target;
-      // Remove old bindings.
-      if (focused) {
-        $(focused).off('keyup', farb.updateValue)
-          .off('keyup', preview).off('keyup', resetScheme)
-          .parent().removeClass('item-selected');
+        // Add new bindings.
+        focused = input;
+        farb.linkTo(function (color) { callback(input, color, true, false); });
+        farb.setColor(input.value);
+        $(focused).on('keyup', farb.updateValue).on('keyup', preview).on('keyup', resetScheme)
+          .parent().addClass('item-selected');
       }
 
-      // Add new bindings.
-      focused = input;
-      farb.linkTo(function (color) { callback(input, color, true, false); });
-      farb.setColor(input.value);
-      $(focused).on('keyup', farb.updateValue).on('keyup', preview).on('keyup', resetScheme)
-        .parent().addClass('item-selected');
-    }
-
-    // Initialize color fields.
-    form.find('#palette input.form-text')
-    .each(function () {
-      // Extract palette field name
-      this.key = this.id.substring(13);
+      // Initialize color fields.
+      form.find('#palette input.form-text')
+        .each(function () {
+          // Extract palette field name
+          this.key = this.id.substring(13);
 
-      // Link to color picker temporarily to initialize.
-      farb.linkTo(function () {}).setColor('#000').linkTo(this);
+          // Link to color picker temporarily to initialize.
+          farb.linkTo(function () {}).setColor('#000').linkTo(this);
 
-      // Add lock.
-      var i = inputs.length;
-      if (inputs.length) {
-        var toggleClick = true;
-        var lock = $('<div class="lock"></div>').on('click', function () {
-          if (toggleClick) {
-            $(this).addClass('unlocked');
-            $(hooks[i - 1]).attr('class',
-              locks[i - 2] && $(locks[i - 2]).is(':not(.unlocked)') ? 'hook up' : 'hook'
-            );
-            $(hooks[i]).attr('class',
-              locks[i] && $(locks[i]).is(':not(.unlocked)') ? 'hook down' : 'hook'
-            );
+          // Add lock.
+          var i = inputs.length;
+          if (inputs.length) {
+            var toggleClick = true;
+            var lock = $('<div class="lock"></div>').on('click', function () {
+              if (toggleClick) {
+                $(this).addClass('unlocked');
+                $(hooks[i - 1]).attr('class',
+                  locks[i - 2] && $(locks[i - 2]).is(':not(.unlocked)') ? 'hook up' : 'hook'
+                );
+                $(hooks[i]).attr('class',
+                  locks[i] && $(locks[i]).is(':not(.unlocked)') ? 'hook down' : 'hook'
+                );
+              }
+              else {
+                $(this).removeClass('unlocked');
+                $(hooks[i - 1]).attr('class',
+                  locks[i - 2] && $(locks[i - 2]).is(':not(.unlocked)') ? 'hook both' : 'hook down'
+                );
+                $(hooks[i]).attr('class',
+                  locks[i] && $(locks[i]).is(':not(.unlocked)') ? 'hook both' : 'hook up'
+                );
+              }
+              toggleClick = !toggleClick;
+            });
+            $(this).after(lock);
+            locks.push(lock);
           }
-          else {
-            $(this).removeClass('unlocked');
-            $(hooks[i - 1]).attr('class',
-              locks[i - 2] && $(locks[i - 2]).is(':not(.unlocked)') ? 'hook both' : 'hook down'
-            );
-            $(hooks[i]).attr('class',
-              locks[i] && $(locks[i]).is(':not(.unlocked)') ? 'hook both' : 'hook up'
-            );
-          }
-          toggleClick = !toggleClick;
-        });
-        $(this).after(lock);
-        locks.push(lock);
-      }
 
-      // Add hook.
-      var hook = $('<div class="hook"></div>');
-      $(this).after(hook);
-      hooks.push(hook);
+          // Add hook.
+          var hook = $('<div class="hook"></div>');
+          $(this).after(hook);
+          hooks.push(hook);
 
-      $(this).parent().find('.lock').trigger('click');
-      this.i = i;
-      inputs.push(this);
-    })
-    .on('focus', focus);
+          $(this).parent().find('.lock').trigger('click');
+          this.i = i;
+          inputs.push(this);
+        })
+        .on('focus', focus);
 
-    form.find('#palette label');
+      form.find('#palette label');
 
-    // Focus first color.
-    inputs[0].focus();
+      // Focus first color.
+      inputs[0].focus();
 
-    // Render preview.
-    preview();
-  }
-};
+      // Render preview.
+      preview();
+    }
+  };
 
 })(jQuery);
diff --git a/core/modules/color/preview.js b/core/modules/color/preview.js
index b15a7b6..5e79855 100644
--- a/core/modules/color/preview.js
+++ b/core/modules/color/preview.js
@@ -8,7 +8,7 @@
   "use strict";
 
   Drupal.color = {
-    callback: function(context, settings, form, farb, height, width) {
+    callback: function (context, settings, form, farb, height, width) {
       // Solid background.
       form.find('#preview').css('backgroundColor', form.find('#palette input[name="palette[base]"]').val());
 
diff --git a/core/modules/comment/comment-entity-form.js b/core/modules/comment/comment-entity-form.js
index e068ef5..b41ab84 100644
--- a/core/modules/comment/comment-entity-form.js
+++ b/core/modules/comment/comment-entity-form.js
@@ -5,15 +5,15 @@
 
 (function ($) {
 
-"use strict";
+  "use strict";
 
-Drupal.behaviors.commentFieldsetSummaries = {
-  attach: function (context) {
-    var $context = $(context);
-    $context.find('fieldset.comment-entity-settings-form').drupalSetSummary(function (context) {
-      return Drupal.checkPlain($(context).find('.form-item-comment input:checked').next('label').text());
-    });
-  }
-};
+  Drupal.behaviors.commentFieldsetSummaries = {
+    attach: function (context) {
+      var $context = $(context);
+      $context.find('fieldset.comment-entity-settings-form').drupalSetSummary(function (context) {
+        return Drupal.checkPlain($(context).find('.form-item-comment input:checked').next('label').text());
+      });
+    }
+  };
 
 })(jQuery);
diff --git a/core/modules/comment/js/comment-by-viewer.js b/core/modules/comment/js/comment-by-viewer.js
index 06d5b82..0abea0d 100644
--- a/core/modules/comment/js/comment-by-viewer.js
+++ b/core/modules/comment/js/comment-by-viewer.js
@@ -3,20 +3,20 @@
  */
 (function ($, Drupal, drupalSettings) {
 
-"use strict";
+  "use strict";
 
-/**
- * Add 'by-viewer' class to comments written by the current user.
- */
-Drupal.behaviors.commentByViewer = {
-  attach: function (context) {
-    var currentUserID = parseInt(drupalSettings.user.uid, 10);
-    $('[data-comment-user-id]')
-      .filter(function () {
-        return parseInt(this.getAttribute('data-comment-user-id'), 10) === currentUserID;
-      })
-      .addClass('by-viewer');
-  }
-};
+  /**
+   * Add 'by-viewer' class to comments written by the current user.
+   */
+  Drupal.behaviors.commentByViewer = {
+    attach: function (context) {
+      var currentUserID = parseInt(drupalSettings.user.uid, 10);
+      $('[data-comment-user-id]')
+        .filter(function () {
+          return parseInt(this.getAttribute('data-comment-user-id'), 10) === currentUserID;
+        })
+        .addClass('by-viewer');
+    }
+  };
 
 })(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/comment/js/comment-new-indicator.js b/core/modules/comment/js/comment-new-indicator.js
index 9b47dd9..fae5cbb 100644
--- a/core/modules/comment/js/comment-new-indicator.js
+++ b/core/modules/comment/js/comment-new-indicator.js
@@ -5,78 +5,78 @@
  */
 (function ($, Drupal, window) {
 
-"use strict";
+  "use strict";
 
-/**
- * Render "new" comment indicators wherever necessary.
- */
-Drupal.behaviors.commentNewIndicator = {
-  attach: function (context) {
-    // Collect all "new" comment indicator placeholders (and their corresponding
-    // node IDs) newer than 30 days ago that have not already been read after
-    // their last comment timestamp.
-    var nodeIDs = [];
-    var $placeholders = $(context)
-      .find('[data-comment-timestamp]')
-      .once('history')
-      .filter(function () {
-        var $placeholder = $(this);
-        var commentTimestamp = parseInt($placeholder.attr('data-comment-timestamp'), 10);
-        var nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id');
-        if (Drupal.history.needsServerCheck(nodeID, commentTimestamp)) {
-          nodeIDs.push(nodeID);
-          return true;
-        }
-        else {
-          return false;
-        }
-      });
+  /**
+   * Render "new" comment indicators wherever necessary.
+   */
+  Drupal.behaviors.commentNewIndicator = {
+    attach: function (context) {
+      // Collect all "new" comment indicator placeholders (and their corresponding
+      // node IDs) newer than 30 days ago that have not already been read after
+      // their last comment timestamp.
+      var nodeIDs = [];
+      var $placeholders = $(context)
+        .find('[data-comment-timestamp]')
+        .once('history')
+        .filter(function () {
+          var $placeholder = $(this);
+          var commentTimestamp = parseInt($placeholder.attr('data-comment-timestamp'), 10);
+          var nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id');
+          if (Drupal.history.needsServerCheck(nodeID, commentTimestamp)) {
+            nodeIDs.push(nodeID);
+            return true;
+          }
+          else {
+            return false;
+          }
+        });
 
-    if ($placeholders.length === 0) {
-      return;
-    }
+      if ($placeholders.length === 0) {
+        return;
+      }
 
-    // Fetch the node read timestamps from the server.
-    Drupal.history.fetchTimestamps(nodeIDs, function () {
-      processCommentNewIndicators($placeholders);
-    });
-  }
-};
+      // Fetch the node read timestamps from the server.
+      Drupal.history.fetchTimestamps(nodeIDs, function () {
+        processCommentNewIndicators($placeholders);
+      });
+    }
+  };
 
-function processCommentNewIndicators($placeholders) {
-  var isFirstNewComment = true;
-  var newCommentString = Drupal.t('new');
-  var $placeholder;
+  function processCommentNewIndicators($placeholders) {
+    var isFirstNewComment = true;
+    var newCommentString = Drupal.t('new');
+    var $placeholder;
 
-  $placeholders.each(function (index, placeholder) {
-    $placeholder = $(placeholder);
-    var timestamp = parseInt($placeholder.attr('data-comment-timestamp'), 10);
-    var $node = $placeholder.closest('[data-history-node-id]');
-    var nodeID = $node.attr('data-history-node-id');
-    var lastViewTimestamp = Drupal.history.getLastRead(nodeID);
+    $placeholders.each(function (index, placeholder) {
+      $placeholder = $(placeholder);
+      var timestamp = parseInt($placeholder.attr('data-comment-timestamp'), 10);
+      var $node = $placeholder.closest('[data-history-node-id]');
+      var nodeID = $node.attr('data-history-node-id');
+      var lastViewTimestamp = Drupal.history.getLastRead(nodeID);
 
-    if (timestamp > lastViewTimestamp) {
-      // Turn the placeholder into an actual "new" indicator.
-      var $comment = $(placeholder)
-        .removeClass('hidden')
-        .text(newCommentString)
-        .closest('.comment')
-        // Add 'new' class to the comment, so it can be styled.
-        .addClass('new');
+      if (timestamp > lastViewTimestamp) {
+        // Turn the placeholder into an actual "new" indicator.
+        var $comment = $(placeholder)
+          .removeClass('hidden')
+          .text(newCommentString)
+          .closest('.comment')
+          // Add 'new' class to the comment, so it can be styled.
+          .addClass('new');
 
-      // Insert "new" anchor just before the "comment-<cid>" anchor if
-      // this is the first new comment in the DOM.
-      if (isFirstNewComment) {
-        isFirstNewComment = false;
-        $comment.prev().before('<a id="new" />');
-        // If the URL points to the first new comment, then scroll to that
-        // comment.
-        if (window.location.hash === '#new') {
-          window.scrollTo(0, $comment.offset().top - Drupal.displace.offsets.top);
+        // Insert "new" anchor just before the "comment-<cid>" anchor if
+        // this is the first new comment in the DOM.
+        if (isFirstNewComment) {
+          isFirstNewComment = false;
+          $comment.prev().before('<a id="new" />');
+          // If the URL points to the first new comment, then scroll to that
+          // comment.
+          if (window.location.hash === '#new') {
+            window.scrollTo(0, $comment.offset().top - Drupal.displace.offsets.top);
+          }
         }
       }
-    }
-  });
-}
+    });
+  }
 
 })(jQuery, Drupal, window);
diff --git a/core/modules/comment/js/node-new-comments-link.js b/core/modules/comment/js/node-new-comments-link.js
index 443b4fe..490f25f 100644
--- a/core/modules/comment/js/node-new-comments-link.js
+++ b/core/modules/comment/js/node-new-comments-link.js
@@ -5,126 +5,126 @@
  */
 (function ($, Drupal) {
 
-"use strict";
+  "use strict";
 
-/**
- * Render "X new comments" links wherever necessary.
- */
-Drupal.behaviors.nodeNewCommentsLink = {
-  attach: function (context) {
-    // Collect all "X new comments" node link placeholders (and their
-    // corresponding node IDs) newer than 30 days ago that have not already been
-    // read after their last comment timestamp.
-    var nodeIDs = [];
-    var $placeholders = $(context)
-      .find('[data-history-node-last-comment-timestamp]')
-      .once('history')
-      .filter(function () {
-        var $placeholder = $(this);
-        var lastCommentTimestamp = parseInt($placeholder.attr('data-history-node-last-comment-timestamp'), 10);
-        var nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id');
-        if (Drupal.history.needsServerCheck(nodeID, lastCommentTimestamp)) {
-          nodeIDs.push(nodeID);
-          // Hide this placeholder link until it is certain we'll need it.
-          hide($placeholder);
-          return true;
-        }
-        else {
-          // Remove this placeholder link from the DOM because we won't need it.
-          remove($placeholder);
-          return false;
-        }
-      });
+  /**
+   * Render "X new comments" links wherever necessary.
+   */
+  Drupal.behaviors.nodeNewCommentsLink = {
+    attach: function (context) {
+      // Collect all "X new comments" node link placeholders (and their
+      // corresponding node IDs) newer than 30 days ago that have not already been
+      // read after their last comment timestamp.
+      var nodeIDs = [];
+      var $placeholders = $(context)
+        .find('[data-history-node-last-comment-timestamp]')
+        .once('history')
+        .filter(function () {
+          var $placeholder = $(this);
+          var lastCommentTimestamp = parseInt($placeholder.attr('data-history-node-last-comment-timestamp'), 10);
+          var nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id');
+          if (Drupal.history.needsServerCheck(nodeID, lastCommentTimestamp)) {
+            nodeIDs.push(nodeID);
+            // Hide this placeholder link until it is certain we'll need it.
+            hide($placeholder);
+            return true;
+          }
+          else {
+            // Remove this placeholder link from the DOM because we won't need it.
+            remove($placeholder);
+            return false;
+          }
+        });
 
-    if ($placeholders.length === 0) {
-      return;
+      if ($placeholders.length === 0) {
+        return;
+      }
+
+      // Perform an AJAX request to retrieve node read timestamps.
+      Drupal.history.fetchTimestamps(nodeIDs, function () {
+        processNodeNewCommentLinks($placeholders);
+      });
     }
+  };
 
-    // Perform an AJAX request to retrieve node read timestamps.
-    Drupal.history.fetchTimestamps(nodeIDs, function () {
-      processNodeNewCommentLinks($placeholders);
-    });
+  function hide($placeholder) {
+    return $placeholder
+      // Find the parent <li>.
+      .closest('.comment-new-comments')
+      // Find the preceding <li>, if any, and give it the 'last' class.
+      .prev().addClass('last')
+      // Go back to the parent <li> and hide it.
+      .end().hide();
   }
-};
 
-function hide($placeholder) {
-  return $placeholder
-    // Find the parent <li>.
-    .closest('.comment-new-comments')
-    // Find the preceding <li>, if any, and give it the 'last' class.
-    .prev().addClass('last')
-    // Go back to the parent <li> and hide it.
-    .end().hide();
-}
+  function remove($placeholder) {
+    hide($placeholder).remove();
+  }
 
-function remove($placeholder) {
-  hide($placeholder).remove();
-}
+  function show($placeholder) {
+    return $placeholder
+      // Find the parent <li>.
+      .closest('.comment-new-comments')
+      // Find the preceding <li>, if any, and remove its 'last' class, if any.
+      .prev().removeClass('last')
+      // Go back to the parent <li> and show it.
+      .end().show();
+  }
 
-function show($placeholder) {
-  return $placeholder
-    // Find the parent <li>.
-    .closest('.comment-new-comments')
-    // Find the preceding <li>, if any, and remove its 'last' class, if any.
-    .prev().removeClass('last')
-    // Go back to the parent <li> and show it.
-    .end().show();
-}
+  function processNodeNewCommentLinks($placeholders) {
+    // Figure out which placeholders need the "x new comments" links.
+    var $placeholdersToUpdate = {};
+    var fieldName = 'comment';
+    var $placeholder;
+    $placeholders.each(function (index, placeholder) {
+      $placeholder = $(placeholder);
+      var timestamp = parseInt($placeholder.attr('data-history-node-last-comment-timestamp'), 10);
+      fieldName = $placeholder.attr('data-history-node-field-name');
+      var nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id');
+      var lastViewTimestamp = Drupal.history.getLastRead(nodeID);
 
-function processNodeNewCommentLinks($placeholders) {
-  // Figure out which placeholders need the "x new comments" links.
-  var $placeholdersToUpdate = {};
-  var fieldName = 'comment';
-  var $placeholder;
-  $placeholders.each(function (index, placeholder) {
-    $placeholder = $(placeholder);
-    var timestamp = parseInt($placeholder.attr('data-history-node-last-comment-timestamp'), 10);
-    fieldName = $placeholder.attr('data-history-node-field-name');
-    var nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id');
-    var lastViewTimestamp = Drupal.history.getLastRead(nodeID);
+      // Queue this placeholder's "X new comments" link to be downloaded from the
+      // server.
+      if (timestamp > lastViewTimestamp) {
+        $placeholdersToUpdate[nodeID] = $placeholder;
+      }
+      // No "X new comments" link necessary; remove it from the DOM.
+      else {
+        remove($placeholder);
+      }
+    });
 
-    // Queue this placeholder's "X new comments" link to be downloaded from the
-    // server.
-    if (timestamp > lastViewTimestamp) {
-      $placeholdersToUpdate[nodeID] = $placeholder;
-    }
-    // No "X new comments" link necessary; remove it from the DOM.
-    else {
-      remove($placeholder);
+    // Perform an AJAX request to retrieve node view timestamps.
+    var nodeIDs = Object.keys($placeholdersToUpdate);
+    if (nodeIDs.length === 0) {
+      return;
     }
-  });
 
-  // Perform an AJAX request to retrieve node view timestamps.
-  var nodeIDs = Object.keys($placeholdersToUpdate);
-  if (nodeIDs.length === 0) {
-    return;
-  }
-
-  // Render the "X new comments" links. Either use the data embedded in the page
-  // or perform an AJAX request to retrieve the same data.
-  function render (results) {
-    for (var nodeID in results) {
-      if (results.hasOwnProperty(nodeID) && $placeholdersToUpdate.hasOwnProperty(nodeID)) {
-        $placeholdersToUpdate[nodeID]
-          .attr('href', results[nodeID].first_new_comment_link)
-          .text(Drupal.formatPlural(results[nodeID].new_comment_count, '1 new comment', '@count new comments'))
-          .removeClass('hidden');
-        show($placeholdersToUpdate[nodeID]);
+    // Render the "X new comments" links. Either use the data embedded in the page
+    // or perform an AJAX request to retrieve the same data.
+    function render(results) {
+      for (var nodeID in results) {
+        if (results.hasOwnProperty(nodeID) && $placeholdersToUpdate.hasOwnProperty(nodeID)) {
+          $placeholdersToUpdate[nodeID]
+            .attr('href', results[nodeID].first_new_comment_link)
+            .text(Drupal.formatPlural(results[nodeID].new_comment_count, '1 new comment', '@count new comments'))
+            .removeClass('hidden');
+          show($placeholdersToUpdate[nodeID]);
+        }
       }
     }
+    if (drupalSettings.comment && drupalSettings.comment.newCommentsLinks) {
+      render(drupalSettings.comment.newCommentsLinks.node[fieldName]);
+    }
+    else {
+      $.ajax({
+        url: Drupal.url('comments/render_new_comments_node_links'),
+        type: 'POST',
+        data: { 'node_ids[]': nodeIDs, 'field_name': fieldName },
+        dataType: 'json',
+        success: render
+      });
+    }
   }
-  if (drupalSettings.comment && drupalSettings.comment.newCommentsLinks) {
-    render(drupalSettings.comment.newCommentsLinks.node[fieldName]);
-  }
-  else {
-    $.ajax({
-      url: Drupal.url('comments/render_new_comments_node_links'),
-      type: 'POST',
-      data: { 'node_ids[]' : nodeIDs, 'field_name' : fieldName },
-      dataType: 'json',
-      success: render
-    });
-  }
-}
 
 })(jQuery, Drupal);
diff --git a/core/modules/content_translation/content_translation.admin.js b/core/modules/content_translation/content_translation.admin.js
index 42ec2bd..13a752f 100644
--- a/core/modules/content_translation/content_translation.admin.js
+++ b/core/modules/content_translation/content_translation.admin.js
@@ -1,115 +1,115 @@
 (function ($, Drupal, drupalSettings) {
 
-"use strict";
+  "use strict";
 
-/**
- * Forces applicable options to be checked as translatable.
- */
-Drupal.behaviors.contentTranslationDependentOptions = {
-  attach: function (context) {
-    var $context = $(context);
-    var options = drupalSettings.contentTranslationDependentOptions;
-    var $fields, dependent_columns;
+  /**
+   * Forces applicable options to be checked as translatable.
+   */
+  Drupal.behaviors.contentTranslationDependentOptions = {
+    attach: function (context) {
+      var $context = $(context);
+      var options = drupalSettings.contentTranslationDependentOptions;
+      var $fields, dependent_columns;
 
-    function fieldsChangeHandler ($fields, dependent_columns) {
-      return function (e) {
-        Drupal.behaviors.contentTranslationDependentOptions.check($fields, dependent_columns, $(e.target));
-      };
-    }
+      function fieldsChangeHandler($fields, dependent_columns) {
+        return function (e) {
+          Drupal.behaviors.contentTranslationDependentOptions.check($fields, dependent_columns, $(e.target));
+        };
+      }
 
-    // We're given a generic name to look for so we find all inputs containing
-    // that name and copy over the input values that require all columns to be
-    // translatable.
-    if (options.dependent_selectors) {
-      for (var field in options.dependent_selectors) {
-        if (options.dependent_selectors.hasOwnProperty(field)) {
-          $fields = $context.find('input[name^="' + field + '"]');
-          dependent_columns = options.dependent_selectors[field];
+      // We're given a generic name to look for so we find all inputs containing
+      // that name and copy over the input values that require all columns to be
+      // translatable.
+      if (options.dependent_selectors) {
+        for (var field in options.dependent_selectors) {
+          if (options.dependent_selectors.hasOwnProperty(field)) {
+            $fields = $context.find('input[name^="' + field + '"]');
+            dependent_columns = options.dependent_selectors[field];
 
-          $fields.on('change', fieldsChangeHandler($fields, dependent_columns));
-          Drupal.behaviors.contentTranslationDependentOptions.check($fields, dependent_columns);
+            $fields.on('change', fieldsChangeHandler($fields, dependent_columns));
+            Drupal.behaviors.contentTranslationDependentOptions.check($fields, dependent_columns);
+          }
         }
       }
-    }
-  },
-  check: function ($fields, dependent_columns, $changed) {
-    var $element = $changed;
-    var column;
+    },
+    check: function ($fields, dependent_columns, $changed) {
+      var $element = $changed;
+      var column;
 
-    function filterFieldsList (index, field) {
-      return $(field).val() === column;
-    }
+      function filterFieldsList(index, field) {
+        return $(field).val() === column;
+      }
+
+      // A field that has many different translatable parts can also define one
+      // or more columns that require all columns to be translatable.
+      for (var index in dependent_columns) {
+        if (dependent_columns.hasOwnProperty(index)) {
+          column = dependent_columns[index];
 
-    // A field that has many different translatable parts can also define one
-    // or more columns that require all columns to be translatable.
-    for (var index in dependent_columns) {
-      if (dependent_columns.hasOwnProperty(index)) {
-        column = dependent_columns[index];
+          if (!$changed) {
+            $element = $fields.filter(filterFieldsList);
+          }
+
+          if ($element.is('input[value="' + column + '"]:checked')) {
+            $fields.prop('checked', true)
+              .not($element).prop('disabled', true);
+          }
+          else {
+            $fields.prop('disabled', false);
+          }
 
-        if (!$changed) {
-          $element = $fields.filter(filterFieldsList);
         }
+      }
+    }
+  };
 
-        if ($element.is('input[value="' + column + '"]:checked')) {
-          $fields.prop('checked', true)
-            .not($element).prop('disabled', true);
+  /**
+   * Makes field translatability inherit bundle translatability.
+   */
+  Drupal.behaviors.contentTranslation = {
+    attach: function (context) {
+      // Initially hide all field rows for non translatable bundles and all column
+      // rows for non translatable fields.
+      $(context).find('table .bundle-settings .translatable :input').once('translation-entity-admin-hide', function () {
+        var $input = $(this);
+        var $bundleSettings = $input.closest('.bundle-settings');
+        if (!$input.is(':checked')) {
+          $bundleSettings.nextUntil('.bundle-settings').hide();
         }
         else {
-          $fields.prop('disabled', false);
+          $bundleSettings.nextUntil('.bundle-settings', '.field-settings').find('.translatable :input:not(:checked)').closest('.field-settings').nextUntil(':not(.column-settings)').hide();
         }
+      });
 
-      }
+      // When a bundle is made translatable all of its field instances should
+      // inherit this setting. Instead when it is made non translatable its field
+      // instances are hidden, since their translatability no longer matters.
+      $('body').once('translation-entity-admin-bind').on('click', 'table .bundle-settings .translatable :input', function (e) {
+        var $target = $(e.target);
+        var $bundleSettings = $target.closest('.bundle-settings');
+        var $settings = $bundleSettings.nextUntil('.bundle-settings');
+        var $fieldSettings = $settings.filter('.field-settings');
+        if ($target.is(':checked')) {
+          $bundleSettings.find('.operations :input[name$="[language_show]"]').prop('checked', true);
+          $fieldSettings.find('.translatable :input').prop('checked', true);
+          $settings.show();
+        }
+        else {
+          $settings.hide();
+        }
+      })
+        .on('click', 'table .field-settings .translatable :input', function (e) {
+          var $target = $(e.target);
+          var $fieldSettings = $target.closest('.field-settings');
+          var $columnSettings = $fieldSettings.nextUntil('.field-settings, .bundle-settings');
+          if ($target.is(':checked')) {
+            $columnSettings.show();
+          }
+          else {
+            $columnSettings.hide();
+          }
+        });
     }
-  }
-};
-
-/**
- * Makes field translatability inherit bundle translatability.
- */
-Drupal.behaviors.contentTranslation = {
-  attach: function (context) {
-    // Initially hide all field rows for non translatable bundles and all column
-    // rows for non translatable fields.
-    $(context).find('table .bundle-settings .translatable :input').once('translation-entity-admin-hide', function () {
-      var $input = $(this);
-      var $bundleSettings = $input.closest('.bundle-settings');
-      if (!$input.is(':checked')) {
-        $bundleSettings.nextUntil('.bundle-settings').hide();
-      }
-      else {
-        $bundleSettings.nextUntil('.bundle-settings', '.field-settings').find('.translatable :input:not(:checked)').closest('.field-settings').nextUntil(':not(.column-settings)').hide();
-      }
-    });
-
-    // When a bundle is made translatable all of its field instances should
-    // inherit this setting. Instead when it is made non translatable its field
-    // instances are hidden, since their translatability no longer matters.
-    $('body').once('translation-entity-admin-bind').on('click', 'table .bundle-settings .translatable :input', function (e) {
-      var $target = $(e.target);
-      var $bundleSettings = $target.closest('.bundle-settings');
-      var $settings = $bundleSettings.nextUntil('.bundle-settings');
-      var $fieldSettings = $settings.filter('.field-settings');
-      if ($target.is(':checked')) {
-        $bundleSettings.find('.operations :input[name$="[language_show]"]').prop('checked', true);
-        $fieldSettings.find('.translatable :input').prop('checked', true);
-        $settings.show();
-      }
-      else {
-        $settings.hide();
-      }
-    })
-    .on('click', 'table .field-settings .translatable :input', function (e) {
-      var $target = $(e.target);
-      var $fieldSettings = $target.closest('.field-settings');
-      var $columnSettings = $fieldSettings.nextUntil('.field-settings, .bundle-settings');
-      if ($target.is(':checked')) {
-        $columnSettings.show();
-      }
-      else {
-        $columnSettings.hide();
-      }
-    });
-  }
-};
+  };
 
 })(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/contextual/js/contextual.js b/core/modules/contextual/js/contextual.js
index 92ecbd7..a843a9a 100644
--- a/core/modules/contextual/js/contextual.js
+++ b/core/modules/contextual/js/contextual.js
@@ -5,221 +5,221 @@
 
 (function ($, Drupal, drupalSettings, _, Backbone, JSON, storage) {
 
-"use strict";
-
-var options = $.extend(drupalSettings.contextual,
-  // Merge strings on top of drupalSettings so that they are not mutable.
-  {
-    strings: {
-      open: Drupal.t('Open'),
-      close: Drupal.t('Close')
-    }
-  }
-);
-
-// Clear the cached contextual links whenever the current user's set of
-// permissions changes.
-var cachedPermissionsHash = storage.getItem('Drupal.contextual.permissionsHash');
-var permissionsHash = drupalSettings.user.permissionsHash;
-if (cachedPermissionsHash !== permissionsHash) {
-  if (typeof permissionsHash === 'string') {
-    _.chain(storage).keys().each(function (key) {
-      if (key.substring(0, 18) === 'Drupal.contextual.') {
-        storage.removeItem(key);
+  "use strict";
+
+  var options = $.extend(drupalSettings.contextual,
+    // Merge strings on top of drupalSettings so that they are not mutable.
+    {
+      strings: {
+        open: Drupal.t('Open'),
+        close: Drupal.t('Close')
       }
-    });
-  }
-  storage.setItem('Drupal.contextual.permissionsHash', permissionsHash);
-}
-
-/**
- * Initializes a contextual link: updates its DOM, sets up model and views
- *
- * @param jQuery $contextual
- *   A contextual links placeholder DOM element, containing the actual
- *   contextual links as rendered by the server.
- * @param string html
- *   The server-side rendered HTML for this contextual link.
- */
-function initContextual ($contextual, html) {
-  var $region = $contextual.closest('.contextual-region');
-  var contextual = Drupal.contextual;
-
-  $contextual
-    // Update the placeholder to contain its rendered contextual links.
-    .html(html)
-    // Use the placeholder as a wrapper with a specific class to provide
-    // positioning and behavior attachment context.
-    .addClass('contextual')
-    // Ensure a trigger element exists before the actual contextual links.
-    .prepend(Drupal.theme('contextualTrigger'));
-
-  // Set the destination parameter on each of the contextual links.
-  var destination = 'destination=' + Drupal.encodePath(drupalSettings.path.currentPath);
-  $contextual.find('.contextual-links a').each(function () {
-    var url = this.getAttribute('href');
-    var glue = (url.indexOf('?') === -1) ? '?' : '&';
-    this.setAttribute('href', url + glue + destination);
-  });
-
-  // Create a model and the appropriate views.
-  var model = new contextual.StateModel({
-    title: $region.find('h2:first').text().trim()
-  });
-  var viewOptions = $.extend({ el: $contextual, model: model }, options);
-  contextual.views.push({
-    visual: new contextual.VisualView(viewOptions),
-    aural: new contextual.AuralView(viewOptions),
-    keyboard: new contextual.KeyboardView(viewOptions)
-  });
-  contextual.regionViews.push(new contextual.RegionView(
-    $.extend({ el: $region, model: model }, options))
+    }
   );
 
-  // Add the model to the collection. This must happen after the views have been
-  // associated with it, otherwise collection change event handlers can't
-  // trigger the model change event handler in its views.
-  contextual.collection.add(model);
-
-  // Let other JavaScript react to the adding of a new contextual link.
-  $(document).trigger('drupalContextualLinkAdded', {
-    $el: $contextual,
-    $region: $region,
-    model: model
-  });
+  // Clear the cached contextual links whenever the current user's set of
+  // permissions changes.
+  var cachedPermissionsHash = storage.getItem('Drupal.contextual.permissionsHash');
+  var permissionsHash = drupalSettings.user.permissionsHash;
+  if (cachedPermissionsHash !== permissionsHash) {
+    if (typeof permissionsHash === 'string') {
+      _.chain(storage).keys().each(function (key) {
+        if (key.substring(0, 18) === 'Drupal.contextual.') {
+          storage.removeItem(key);
+        }
+      });
+    }
+    storage.setItem('Drupal.contextual.permissionsHash', permissionsHash);
+  }
 
-  // Fix visual collisions between contextual link triggers.
-  adjustIfNestedAndOverlapping($contextual);
-}
+  /**
+   * Initializes a contextual link: updates its DOM, sets up model and views
+   *
+   * @param jQuery $contextual
+   *   A contextual links placeholder DOM element, containing the actual
+   *   contextual links as rendered by the server.
+   * @param string html
+   *   The server-side rendered HTML for this contextual link.
+   */
+  function initContextual($contextual, html) {
+    var $region = $contextual.closest('.contextual-region');
+    var contextual = Drupal.contextual;
+
+    $contextual
+      // Update the placeholder to contain its rendered contextual links.
+      .html(html)
+      // Use the placeholder as a wrapper with a specific class to provide
+      // positioning and behavior attachment context.
+      .addClass('contextual')
+      // Ensure a trigger element exists before the actual contextual links.
+      .prepend(Drupal.theme('contextualTrigger'));
+
+    // Set the destination parameter on each of the contextual links.
+    var destination = 'destination=' + Drupal.encodePath(drupalSettings.path.currentPath);
+    $contextual.find('.contextual-links a').each(function () {
+      var url = this.getAttribute('href');
+      var glue = (url.indexOf('?') === -1) ? '?' : '&';
+      this.setAttribute('href', url + glue + destination);
+    });
 
-/**
- * Determines if a contextual link is nested & overlapping, if so: adjusts it.
- *
- * This only deals with two levels of nesting; deeper levels are not touched.
- *
- * @param jQuery $contextual
- *   A contextual links placeholder DOM element, containing the actual
- *   contextual links as rendered by the server.
- */
-function adjustIfNestedAndOverlapping ($contextual) {
-  var $contextuals = $contextual
-    // @todo confirm that .closest() is not sufficient
-    .parents('.contextual-region').eq(-1)
-    .find('.contextual');
-
-  // Early-return when there's no nesting.
-  if ($contextuals.length === 1) {
-    return;
-  }
+    // Create a model and the appropriate views.
+    var model = new contextual.StateModel({
+      title: $region.find('h2:first').text().trim()
+    });
+    var viewOptions = $.extend({ el: $contextual, model: model }, options);
+    contextual.views.push({
+      visual: new contextual.VisualView(viewOptions),
+      aural: new contextual.AuralView(viewOptions),
+      keyboard: new contextual.KeyboardView(viewOptions)
+    });
+    contextual.regionViews.push(new contextual.RegionView(
+      $.extend({ el: $region, model: model }, options))
+    );
+
+    // Add the model to the collection. This must happen after the views have been
+    // associated with it, otherwise collection change event handlers can't
+    // trigger the model change event handler in its views.
+    contextual.collection.add(model);
+
+    // Let other JavaScript react to the adding of a new contextual link.
+    $(document).trigger('drupalContextualLinkAdded', {
+      $el: $contextual,
+      $region: $region,
+      model: model
+    });
 
-  // If the two contextual links overlap, then we move the second one.
-  var firstTop = $contextuals.eq(0).offset().top;
-  var secondTop = $contextuals.eq(1).offset().top;
-  if (firstTop === secondTop) {
-    var $nestedContextual = $contextuals.eq(1);
-
-    // Retrieve height of nested contextual link.
-    var height = 0;
-    var $trigger = $nestedContextual.find('.trigger');
-    // Elements with the .visually-hidden class have no dimensions, so this
-    // class must be temporarily removed to the calculate the height.
-    $trigger.removeClass('visually-hidden');
-    height = $nestedContextual.height();
-    $trigger.addClass('visually-hidden');
-
-    // Adjust nested contextual link's position.
-    $nestedContextual.css({ top: $nestedContextual.position().top + height });
+    // Fix visual collisions between contextual link triggers.
+    adjustIfNestedAndOverlapping($contextual);
   }
-}
 
-/**
- * Attaches outline behavior for regions associated with contextual links.
- *
- * Events
- *   Contextual triggers an event that can be used by other scripts.
- *   - drupalContextualLinkAdded: Triggered when a contextual link is added.
- */
-Drupal.behaviors.contextual = {
-  attach: function (context) {
-    var $context = $(context);
-
-    // Find all contextual links placeholders, if any.
-    var $placeholders = $context.find('[data-contextual-id]').once('contextual-render');
-    if ($placeholders.length === 0) {
+  /**
+   * Determines if a contextual link is nested & overlapping, if so: adjusts it.
+   *
+   * This only deals with two levels of nesting; deeper levels are not touched.
+   *
+   * @param jQuery $contextual
+   *   A contextual links placeholder DOM element, containing the actual
+   *   contextual links as rendered by the server.
+   */
+  function adjustIfNestedAndOverlapping($contextual) {
+    var $contextuals = $contextual
+      // @todo confirm that .closest() is not sufficient
+      .parents('.contextual-region').eq(-1)
+      .find('.contextual');
+
+    // Early-return when there's no nesting.
+    if ($contextuals.length === 1) {
       return;
     }
 
-    // Collect the IDs for all contextual links placeholders.
-    var ids = [];
-    $placeholders.each(function () {
-      ids.push($(this).attr('data-contextual-id'));
-    });
+    // If the two contextual links overlap, then we move the second one.
+    var firstTop = $contextuals.eq(0).offset().top;
+    var secondTop = $contextuals.eq(1).offset().top;
+    if (firstTop === secondTop) {
+      var $nestedContextual = $contextuals.eq(1);
+
+      // Retrieve height of nested contextual link.
+      var height = 0;
+      var $trigger = $nestedContextual.find('.trigger');
+      // Elements with the .visually-hidden class have no dimensions, so this
+      // class must be temporarily removed to the calculate the height.
+      $trigger.removeClass('visually-hidden');
+      height = $nestedContextual.height();
+      $trigger.addClass('visually-hidden');
+
+      // Adjust nested contextual link's position.
+      $nestedContextual.css({ top: $nestedContextual.position().top + height });
+    }
+  }
 
-    // Update all contextual links placeholders whose HTML is cached.
-    var uncachedIDs = _.filter(ids, function initIfCached (contextualID) {
-      var html = storage.getItem('Drupal.contextual.' + contextualID);
-      if (html !== null) {
-        initContextual($context.find('[data-contextual-id="' + contextualID + '"]'), html);
-        return false;
+  /**
+   * Attaches outline behavior for regions associated with contextual links.
+   *
+   * Events
+   *   Contextual triggers an event that can be used by other scripts.
+   *   - drupalContextualLinkAdded: Triggered when a contextual link is added.
+   */
+  Drupal.behaviors.contextual = {
+    attach: function (context) {
+      var $context = $(context);
+
+      // Find all contextual links placeholders, if any.
+      var $placeholders = $context.find('[data-contextual-id]').once('contextual-render');
+      if ($placeholders.length === 0) {
+        return;
       }
-      return true;
-    });
 
-    // Perform an AJAX request to let the server render the contextual links for
-    // each of the placeholders.
-    if (uncachedIDs.length > 0) {
-      $.ajax({
-        url: Drupal.url('contextual/render'),
-        type: 'POST',
-        data: { 'ids[]' : uncachedIDs },
-        dataType: 'json',
-        success: function (results) {
-          _.each(results, function (html, contextualID) {
-            // Store the metadata.
-            storage.setItem('Drupal.contextual.' + contextualID, html);
-            // If the rendered contextual links are empty, then the current user
-            // does not have permission to access the associated links: don't
-            // render anything.
-            if (html.length > 0) {
-              // Update the placeholders to contain its rendered contextual links.
-              // Usually there will only be one placeholder, but it's possible for
-              // multiple identical placeholders exist on the page (probably
-              // because the same content appears more than once).
-              var $placeholders = $context.find('[data-contextual-id="' + contextualID + '"]');
-
-              // Initialize the contextual links.
-              for (var i = 0; i < $placeholders.length; i++) {
-                initContextual($placeholders.eq(i), html);
-              }
-            }
-          });
+      // Collect the IDs for all contextual links placeholders.
+      var ids = [];
+      $placeholders.each(function () {
+        ids.push($(this).attr('data-contextual-id'));
+      });
+
+      // Update all contextual links placeholders whose HTML is cached.
+      var uncachedIDs = _.filter(ids, function initIfCached(contextualID) {
+        var html = storage.getItem('Drupal.contextual.' + contextualID);
+        if (html !== null) {
+          initContextual($context.find('[data-contextual-id="' + contextualID + '"]'), html);
+          return false;
         }
-       });
+        return true;
+      });
+
+      // Perform an AJAX request to let the server render the contextual links for
+      // each of the placeholders.
+      if (uncachedIDs.length > 0) {
+        $.ajax({
+          url: Drupal.url('contextual/render'),
+          type: 'POST',
+          data: { 'ids[]': uncachedIDs },
+          dataType: 'json',
+          success: function (results) {
+            _.each(results, function (html, contextualID) {
+              // Store the metadata.
+              storage.setItem('Drupal.contextual.' + contextualID, html);
+              // If the rendered contextual links are empty, then the current user
+              // does not have permission to access the associated links: don't
+              // render anything.
+              if (html.length > 0) {
+                // Update the placeholders to contain its rendered contextual links.
+                // Usually there will only be one placeholder, but it's possible for
+                // multiple identical placeholders exist on the page (probably
+                // because the same content appears more than once).
+                var $placeholders = $context.find('[data-contextual-id="' + contextualID + '"]');
+
+                // Initialize the contextual links.
+                for (var i = 0; i < $placeholders.length; i++) {
+                  initContextual($placeholders.eq(i), html);
+                }
+              }
+            });
+          }
+        });
+      }
     }
-  }
-};
-
-Drupal.contextual = {
-  // The Drupal.contextual.View instances associated with each list element of
-  // contextual links.
-  views: [],
-
-  // The Drupal.contextual.RegionView instances associated with each contextual
-  // region element.
-  regionViews: []
-};
-
-// A Backbone.Collection of Drupal.contextual.StateModel instances.
-Drupal.contextual.collection = new Backbone.Collection([], { model: Drupal.contextual.StateModel });
-
-/**
- * A trigger is an interactive element often bound to a click handler.
- *
- * @return String
- *   A string representing a DOM fragment.
- */
-Drupal.theme.contextualTrigger = function () {
-  return '<button class="trigger visually-hidden focusable" type="button"></button>';
-};
+  };
+
+  Drupal.contextual = {
+    // The Drupal.contextual.View instances associated with each list element of
+    // contextual links.
+    views: [],
+
+    // The Drupal.contextual.RegionView instances associated with each contextual
+    // region element.
+    regionViews: []
+  };
+
+  // A Backbone.Collection of Drupal.contextual.StateModel instances.
+  Drupal.contextual.collection = new Backbone.Collection([], { model: Drupal.contextual.StateModel });
+
+  /**
+   * A trigger is an interactive element often bound to a click handler.
+   *
+   * @return String
+   *   A string representing a DOM fragment.
+   */
+  Drupal.theme.contextualTrigger = function () {
+    return '<button class="trigger visually-hidden focusable" type="button"></button>';
+  };
 
 })(jQuery, Drupal, drupalSettings, _, Backbone, window.JSON, window.sessionStorage);
diff --git a/core/modules/contextual/js/contextual.toolbar.js b/core/modules/contextual/js/contextual.toolbar.js
index 0c6a8b8..d93b8d7 100644
--- a/core/modules/contextual/js/contextual.toolbar.js
+++ b/core/modules/contextual/js/contextual.toolbar.js
@@ -5,288 +5,288 @@
 
 (function ($, Drupal, Backbone) {
 
-"use strict";
+  "use strict";
 
-var strings = {
-  tabbingReleased: Drupal.t('Tabbing is no longer constrained by the Contextual module.'),
-  tabbingConstrained: Drupal.t('Tabbing is constrained to a set of @contextualsCount and the edit mode toggle.'),
-  pressEsc: Drupal.t('Press the esc key to exit.')
-};
-
-/**
- * Initializes a contextual link: updates its DOM, sets up model and views
- *
- * @param DOM links
- *   A contextual links DOM element as rendered by the server.
- */
-function initContextualToolbar (context) {
-  if (!Drupal.contextual || !Drupal.contextual.collection) {
-    return;
-  }
-
-  var contextualToolbar = Drupal.contextualToolbar;
-  var model = contextualToolbar.model = new contextualToolbar.Model({
-    // Checks whether localStorage indicates we should start in edit mode
-    // rather than view mode.
-    // @see Drupal.contextualToolbar.VisualView.persist()
-    isViewing: localStorage.getItem('Drupal.contextualToolbar.isViewing') !== 'false'
-  }, {
-    contextualCollection: Drupal.contextual.collection,
-  });
-
-  var viewOptions = {
-    el: $('.toolbar .toolbar-bar .contextual-toolbar-tab'),
-    model: model,
-    strings: strings
+  var strings = {
+    tabbingReleased: Drupal.t('Tabbing is no longer constrained by the Contextual module.'),
+    tabbingConstrained: Drupal.t('Tabbing is constrained to a set of @contextualsCount and the edit mode toggle.'),
+    pressEsc: Drupal.t('Press the esc key to exit.')
   };
-  new contextualToolbar.VisualView(viewOptions);
-  new contextualToolbar.AuralView(viewOptions);
-}
 
-/**
- * Attaches contextual's edit toolbar tab behavior.
- */
-Drupal.behaviors.contextualToolbar = {
-  attach: function (context) {
-    if ($('body').once('contextualToolbar-init').length) {
-      initContextualToolbar(context);
+  /**
+   * Initializes a contextual link: updates its DOM, sets up model and views
+   *
+   * @param DOM links
+   *   A contextual links DOM element as rendered by the server.
+   */
+  function initContextualToolbar(context) {
+    if (!Drupal.contextual || !Drupal.contextual.collection) {
+      return;
     }
+
+    var contextualToolbar = Drupal.contextualToolbar;
+    var model = contextualToolbar.model = new contextualToolbar.Model({
+      // Checks whether localStorage indicates we should start in edit mode
+      // rather than view mode.
+      // @see Drupal.contextualToolbar.VisualView.persist()
+      isViewing: localStorage.getItem('Drupal.contextualToolbar.isViewing') !== 'false'
+    }, {
+      contextualCollection: Drupal.contextual.collection,
+    });
+
+    var viewOptions = {
+      el: $('.toolbar .toolbar-bar .contextual-toolbar-tab'),
+      model: model,
+      strings: strings
+    };
+    new contextualToolbar.VisualView(viewOptions);
+    new contextualToolbar.AuralView(viewOptions);
   }
-};
 
-/**
- * Model and View definitions.
- */
-Drupal.contextualToolbar = {
-  // The Drupal.contextualToolbar.Model instance.
-  model: null,
+  /**
+   * Attaches contextual's edit toolbar tab behavior.
+   */
+  Drupal.behaviors.contextualToolbar = {
+    attach: function (context) {
+      if ($('body').once('contextualToolbar-init').length) {
+        initContextualToolbar(context);
+      }
+    }
+  };
 
   /**
-   * Models the state of the edit mode toggle.
+   * Model and View definitions.
    */
-  Model: Backbone.Model.extend({
-    defaults: {
-      // Indicates whether the toggle is currently in "view" or "edit" mode.
-      isViewing: true,
-      // Indicates whether the toggle should be visible or hidden. Automatically
-      // calculated, depends on contextualCount.
-      isVisible: false,
-      // Tracks how many contextual links exist on the page.
-      contextualCount: 0,
-      // A TabbingContext object as returned by Drupal.TabbingManager: the set
-      // of tabbable elements when edit mode is enabled.
-      tabbingContext: null
-    },
+  Drupal.contextualToolbar = {
+    // The Drupal.contextualToolbar.Model instance.
+    model: null,
 
     /**
-     * {@inheritdoc}
-     *
-     * @param Object attrs
-     * @param Object options
-     *   An object with the following option:
-     *     - Backbone.collection contextualCollection: the collection of
-     *       Drupal.contextual.Model models that represent the contextual links
-     *       on the page.
+     * Models the state of the edit mode toggle.
      */
-    initialize: function (attrs, options) {
-      // Respond to new/removed contextual links.
-      this.listenTo(options.contextualCollection, {
-        'reset remove add': this.countCountextualLinks,
-        'add': this.lockNewContextualLinks
-      });
+    Model: Backbone.Model.extend({
+      defaults: {
+        // Indicates whether the toggle is currently in "view" or "edit" mode.
+        isViewing: true,
+        // Indicates whether the toggle should be visible or hidden. Automatically
+        // calculated, depends on contextualCount.
+        isVisible: false,
+        // Tracks how many contextual links exist on the page.
+        contextualCount: 0,
+        // A TabbingContext object as returned by Drupal.TabbingManager: the set
+        // of tabbable elements when edit mode is enabled.
+        tabbingContext: null
+      },
+
+      /**
+       * {@inheritdoc}
+       *
+       * @param Object attrs
+       * @param Object options
+       *   An object with the following option:
+       *     - Backbone.collection contextualCollection: the collection of
+       *       Drupal.contextual.Model models that represent the contextual links
+       *       on the page.
+       */
+      initialize: function (attrs, options) {
+        // Respond to new/removed contextual links.
+        this.listenTo(options.contextualCollection, {
+          'reset remove add': this.countCountextualLinks,
+          'add': this.lockNewContextualLinks
+        });
+
+        this.listenTo(this, {
+          // Automatically determine visibility.
+          'change:contextualCount': this.updateVisibility,
+          // Whenever edit mode is toggled, lock all contextual links.
+          'change:isViewing': function (model, isViewing) {
+            options.contextualCollection.each(function (contextualModel) {
+              contextualModel.set('isLocked', !isViewing);
+            });
+          }
+        });
+      },
 
-      this.listenTo(this, {
-        // Automatically determine visibility.
-        'change:contextualCount': this.updateVisibility,
-        // Whenever edit mode is toggled, lock all contextual links.
-        'change:isViewing': function (model, isViewing) {
-          options.contextualCollection.each(function (contextualModel) {
-            contextualModel.set('isLocked', !isViewing);
-          });
+      /**
+       * Tracks the number of contextual link models in the collection.
+       *
+       * @param Drupal.contextual.Model affectedModel
+       *   The contextual links model that was added or removed.
+       * @param Backbone.Collection contextualCollection
+       *    The collection of contextual link models.
+       */
+      countCountextualLinks: function (contextualModel, contextualCollection) {
+        this.set('contextualCount', contextualCollection.length);
+      },
+
+      /**
+       * Lock newly added contextual links if edit mode is enabled.
+       *
+       * @param Drupal.contextual.Model addedContextualModel
+       *   The contextual links model that was added.
+       * @param Backbone.Collection contextualCollection
+       *    The collection of contextual link models.
+       */
+      lockNewContextualLinks: function (contextualModel, contextualCollection) {
+        if (!this.get('isViewing')) {
+          contextualModel.set('isLocked', true);
         }
-      });
-    },
+      },
 
-    /**
-     * Tracks the number of contextual link models in the collection.
-     *
-     * @param Drupal.contextual.Model affectedModel
-     *   The contextual links model that was added or removed.
-     * @param Backbone.Collection contextualCollection
-     *    The collection of contextual link models.
-     */
-    countCountextualLinks: function (contextualModel, contextualCollection) {
-      this.set('contextualCount', contextualCollection.length);
-    },
+      /**
+       * Automatically updates visibility of the view/edit mode toggle.
+       */
+      updateVisibility: function () {
+        this.set('isVisible', this.get('contextualCount') > 0);
+      }
+    }),
 
     /**
-     * Lock newly added contextual links if edit mode is enabled.
+     * Renders the visual view of the edit mode toggle. Listens to mouse & touch.
      *
-     * @param Drupal.contextual.Model addedContextualModel
-     *   The contextual links model that was added.
-     * @param Backbone.Collection contextualCollection
-     *    The collection of contextual link models.
+     * Handles edit mode toggle interactions.
      */
-    lockNewContextualLinks: function (contextualModel, contextualCollection) {
-      if (!this.get('isViewing')) {
-        contextualModel.set('isLocked', true);
-      }
-    },
+    VisualView: Backbone.View.extend({
+      events: function () {
+        // Prevents delay and simulated mouse events.
+        var touchEndToClick = function (event) {
+          event.preventDefault();
+          event.target.click();
+        };
 
-    /**
-     * Automatically updates visibility of the view/edit mode toggle.
-     */
-    updateVisibility: function () {
-      this.set('isVisible', this.get('contextualCount') > 0);
-    }
-  }),
+        return {
+          'click': function () {
+            this.model.set('isViewing', !this.model.get('isViewing'));
+          },
+          'touchend': touchEndToClick
+        };
+      },
 
-  /**
-   * Renders the visual view of the edit mode toggle. Listens to mouse & touch.
-   *
-   * Handles edit mode toggle interactions.
-   */
-  VisualView: Backbone.View.extend({
-    events: function () {
-      // Prevents delay and simulated mouse events.
-      var touchEndToClick = function (event) {
-        event.preventDefault();
-        event.target.click();
-      };
+      /**
+       * {@inheritdoc}
+       */
+      initialize: function () {
+        this.listenTo(this.model, 'change', this.render);
+        this.listenTo(this.model, 'change:isViewing', this.persist);
+      },
 
-      return {
-        'click': function () {
-          this.model.set('isViewing', !this.model.get('isViewing'));
-        },
-        'touchend': touchEndToClick
-      };
-    },
+      /**
+       * {@inheritdoc}
+       */
+      render: function () {
+        // Render the visibility.
+        this.$el.toggleClass('hidden', !this.model.get('isVisible'));
+        // Render the state.
+        this.$el.find('button').toggleClass('active', !this.model.get('isViewing'));
 
-    /**
-     * {@inheritdoc}
-     */
-    initialize: function () {
-      this.listenTo(this.model, 'change', this.render);
-      this.listenTo(this.model, 'change:isViewing', this.persist);
-    },
+        return this;
+      },
 
-    /**
-     * {@inheritdoc}
-     */
-    render: function () {
-      // Render the visibility.
-      this.$el.toggleClass('hidden', !this.model.get('isVisible'));
-      // Render the state.
-      this.$el.find('button').toggleClass('active', !this.model.get('isViewing'));
-
-      return this;
-    },
+      /**
+       * Model change handler; persists the isViewing value to localStorage.
+       *
+       * isViewing === true is the default, so only stores in localStorage when
+       * it's not the default value (i.e. false).
+       *
+       * @param Drupal.contextualToolbar.Model model
+       *   A Drupal.contextualToolbar.Model model.
+       * @param bool isViewing
+       *   The value of the isViewing attribute in the model.
+       */
+      persist: function (model, isViewing) {
+        if (!isViewing) {
+          localStorage.setItem('Drupal.contextualToolbar.isViewing', 'false');
+        }
+        else {
+          localStorage.removeItem('Drupal.contextualToolbar.isViewing');
+        }
+      }
+    }),
 
     /**
-     * Model change handler; persists the isViewing value to localStorage.
-     *
-     * isViewing === true is the default, so only stores in localStorage when
-     * it's not the default value (i.e. false).
-     *
-     * @param Drupal.contextualToolbar.Model model
-     *   A Drupal.contextualToolbar.Model model.
-     * @param bool isViewing
-     *   The value of the isViewing attribute in the model.
+     * Renders the aural view of the edit mode toggle (i.e.screen reader support).
      */
-    persist: function (model, isViewing) {
-      if (!isViewing) {
-        localStorage.setItem('Drupal.contextualToolbar.isViewing', 'false');
-      }
-      else {
-        localStorage.removeItem('Drupal.contextualToolbar.isViewing');
-      }
-    }
-  }),
+    AuralView: Backbone.View.extend({
+      // Tracks whether the tabbing constraint announcement has been read once yet.
+      announcedOnce: false,
 
-  /**
-   * Renders the aural view of the edit mode toggle (i.e.screen reader support).
-   */
-  AuralView: Backbone.View.extend({
-    // Tracks whether the tabbing constraint announcement has been read once yet.
-    announcedOnce: false,
+      /*
+       * {@inheritdoc}
+       */
+      initialize: function (options) {
+        this.options = options;
 
-    /*
-     * {@inheritdoc}
-     */
-    initialize: function (options) {
-      this.options = options;
-
-      this.listenTo(this.model, 'change', this.render);
-      this.listenTo(this.model, 'change:isViewing', this.manageTabbing);
+        this.listenTo(this.model, 'change', this.render);
+        this.listenTo(this.model, 'change:isViewing', this.manageTabbing);
 
-      $(document).on('keyup', _.bind(this.onKeypress, this));
-    },
+        $(document).on('keyup', _.bind(this.onKeypress, this));
+      },
 
-    /**
-     * {@inheritdoc}
-     */
-    render: function () {
-      // Render the state.
-      this.$el.find('button').attr('aria-pressed', !this.model.get('isViewing'));
+      /**
+       * {@inheritdoc}
+       */
+      render: function () {
+        // Render the state.
+        this.$el.find('button').attr('aria-pressed', !this.model.get('isViewing'));
 
-      return this;
-    },
+        return this;
+      },
 
-    /**
-     * Limits tabbing to the contextual links and edit mode toolbar tab.
-     *
-     * @param Drupal.contextualToolbar.Model model
-     *   A Drupal.contextualToolbar.Model model.
-     * @param bool isViewing
-     *   The value of the isViewing attribute in the model.
-     */
-    manageTabbing: function () {
-      var tabbingContext = this.model.get('tabbingContext');
-      // Always release an existing tabbing context.
-      if (tabbingContext) {
-        tabbingContext.release();
-        Drupal.announce(this.options.strings.tabbingReleased);
-      }
-      // Create a new tabbing context when edit mode is enabled.
-      if (!this.model.get('isViewing')) {
-        tabbingContext = Drupal.tabbingManager.constrain($('.contextual-toolbar-tab, .contextual'));
-        this.model.set('tabbingContext', tabbingContext);
-        this.announceTabbingConstraint();
-        this.announcedOnce = true;
-      }
-    },
+      /**
+       * Limits tabbing to the contextual links and edit mode toolbar tab.
+       *
+       * @param Drupal.contextualToolbar.Model model
+       *   A Drupal.contextualToolbar.Model model.
+       * @param bool isViewing
+       *   The value of the isViewing attribute in the model.
+       */
+      manageTabbing: function () {
+        var tabbingContext = this.model.get('tabbingContext');
+        // Always release an existing tabbing context.
+        if (tabbingContext) {
+          tabbingContext.release();
+          Drupal.announce(this.options.strings.tabbingReleased);
+        }
+        // Create a new tabbing context when edit mode is enabled.
+        if (!this.model.get('isViewing')) {
+          tabbingContext = Drupal.tabbingManager.constrain($('.contextual-toolbar-tab, .contextual'));
+          this.model.set('tabbingContext', tabbingContext);
+          this.announceTabbingConstraint();
+          this.announcedOnce = true;
+        }
+      },
 
-    /**
-     * Announces the current tabbing constraint.
-     */
-    announceTabbingConstraint: function () {
-      var strings = this.options.strings;
-      Drupal.announce(Drupal.formatString(strings.tabbingConstrained, {
-        '@contextualsCount': Drupal.formatPlural(Drupal.contextual.collection.length, '@count contextual link', '@count contextual links')
-      }));
-      Drupal.announce(strings.pressEsc);
-    },
+      /**
+       * Announces the current tabbing constraint.
+       */
+      announceTabbingConstraint: function () {
+        var strings = this.options.strings;
+        Drupal.announce(Drupal.formatString(strings.tabbingConstrained, {
+          '@contextualsCount': Drupal.formatPlural(Drupal.contextual.collection.length, '@count contextual link', '@count contextual links')
+        }));
+        Drupal.announce(strings.pressEsc);
+      },
 
-    /**
-     * Responds to esc and tab key press events.
-     *
-     * @param jQuery.Event event
-     */
-    onKeypress: function (event) {
-      // The first tab key press is tracked so that an annoucement about tabbing
-      // constraints can be raised if edit mode is enabled when the page is
-      // loaded.
-      if (!this.announcedOnce && event.keyCode === 9 && !this.model.get('isViewing')) {
-        this.announceTabbingConstraint();
-        // Set announce to true so that this conditional block won't run again.
-        this.announcedOnce = true;
-      }
-      // Respond to the ESC key. Exit out of edit mode.
-      if (event.keyCode === 27) {
-        this.model.set('isViewing', true);
+      /**
+       * Responds to esc and tab key press events.
+       *
+       * @param jQuery.Event event
+       */
+      onKeypress: function (event) {
+        // The first tab key press is tracked so that an annoucement about tabbing
+        // constraints can be raised if edit mode is enabled when the page is
+        // loaded.
+        if (!this.announcedOnce && event.keyCode === 9 && !this.model.get('isViewing')) {
+          this.announceTabbingConstraint();
+          // Set announce to true so that this conditional block won't run again.
+          this.announcedOnce = true;
+        }
+        // Respond to the ESC key. Exit out of edit mode.
+        if (event.keyCode === 27) {
+          this.model.set('isViewing', true);
+        }
       }
-    }
-  })
-};
+    })
+  };
 
 })(jQuery, Drupal, Backbone);
diff --git a/core/modules/contextual/js/models/StateModel.js b/core/modules/contextual/js/models/StateModel.js
index dc633f2..24907b0 100644
--- a/core/modules/contextual/js/models/StateModel.js
+++ b/core/modules/contextual/js/models/StateModel.js
@@ -5,78 +5,78 @@
 
 (function (Drupal, Backbone) {
 
-"use strict";
-
-/**
- * Models the state of a contextual link's trigger, list & region.
- */
-Drupal.contextual.StateModel = Backbone.Model.extend({
-
-  defaults: {
-    // The title of the entity to which these contextual links apply.
-    title: '',
-    // Represents if the contextual region is being hovered.
-    regionIsHovered: false,
-    // Represents if the contextual trigger or options have focus.
-    hasFocus: false,
-    // Represents if the contextual options for an entity are available to
-    // be selected (i.e. whether the list of options is visible).
-    isOpen: false,
-    // When the model is locked, the trigger remains active.
-    isLocked: false
-  },
+  "use strict";
 
   /**
-   * Opens or closes the contextual link.
-   *
-   * If it is opened, then also give focus.
+   * Models the state of a contextual link's trigger, list & region.
    */
-  toggleOpen: function () {
-    var newIsOpen = !this.get('isOpen');
-    this.set('isOpen', newIsOpen);
-    if (newIsOpen) {
-      this.focus();
-    }
-    return this;
-  },
+  Drupal.contextual.StateModel = Backbone.Model.extend({
 
-  /**
-   * Closes this contextual link.
-   *
-   * Does not call blur() because we want to allow a contextual link to have
-   * focus, yet be closed for example when hovering.
-   */
-  close: function () {
-    this.set('isOpen', false);
-    return this;
-  },
+    defaults: {
+      // The title of the entity to which these contextual links apply.
+      title: '',
+      // Represents if the contextual region is being hovered.
+      regionIsHovered: false,
+      // Represents if the contextual trigger or options have focus.
+      hasFocus: false,
+      // Represents if the contextual options for an entity are available to
+      // be selected (i.e. whether the list of options is visible).
+      isOpen: false,
+      // When the model is locked, the trigger remains active.
+      isLocked: false
+    },
 
-  /**
-   * Gives focus to this contextual link.
-   *
-   * Also closes + removes focus from every other contextual link.
-   */
-  focus: function () {
-    this.set('hasFocus', true);
-    var cid = this.cid;
-    this.collection.each(function (model) {
-      if (model.cid !== cid) {
-        model.close().blur();
+    /**
+     * Opens or closes the contextual link.
+     *
+     * If it is opened, then also give focus.
+     */
+    toggleOpen: function () {
+      var newIsOpen = !this.get('isOpen');
+      this.set('isOpen', newIsOpen);
+      if (newIsOpen) {
+        this.focus();
       }
-    });
-    return this;
-  },
+      return this;
+    },
 
-  /**
-   * Removes focus from this contextual link, unless it is open.
-   */
-  blur: function () {
-    if (!this.get('isOpen')) {
-      this.set('hasFocus', false);
+    /**
+     * Closes this contextual link.
+     *
+     * Does not call blur() because we want to allow a contextual link to have
+     * focus, yet be closed for example when hovering.
+     */
+    close: function () {
+      this.set('isOpen', false);
+      return this;
+    },
+
+    /**
+     * Gives focus to this contextual link.
+     *
+     * Also closes + removes focus from every other contextual link.
+     */
+    focus: function () {
+      this.set('hasFocus', true);
+      var cid = this.cid;
+      this.collection.each(function (model) {
+        if (model.cid !== cid) {
+          model.close().blur();
+        }
+      });
+      return this;
+    },
+
+    /**
+     * Removes focus from this contextual link, unless it is open.
+     */
+    blur: function () {
+      if (!this.get('isOpen')) {
+        this.set('hasFocus', false);
+      }
+      return this;
     }
-    return this;
-  }
 
-});
+  });
 
 })(Drupal, Backbone);
diff --git a/core/modules/contextual/js/views/AuralView.js b/core/modules/contextual/js/views/AuralView.js
index 9ce04c1..40aff22 100644
--- a/core/modules/contextual/js/views/AuralView.js
+++ b/core/modules/contextual/js/views/AuralView.js
@@ -5,47 +5,47 @@
 
 (function (Drupal, Backbone) {
 
-"use strict";
-
-/**
- * Renders the aural view of a contextual link (i.e. screen reader support).
- */
-Drupal.contextual.AuralView = Backbone.View.extend({
-
-  /**
-   * {@inheritdoc}
-   */
-  initialize: function (options) {
-    this.options = options;
-
-    this.listenTo(this.model, 'change', this.render);
-
-    // Use aria-role form so that the number of items in the list is spoken.
-    this.$el.attr('role', 'form');
-
-    // Initial render.
-    this.render();
-  },
+  "use strict";
 
   /**
-   * {@inheritdoc}
+   * Renders the aural view of a contextual link (i.e. screen reader support).
    */
-  render: function () {
-    var isOpen = this.model.get('isOpen');
-
-    // Set the hidden property of the links.
-    this.$el.find('.contextual-links')
-      .prop('hidden', !isOpen);
-
-    // Update the view of the trigger.
-    this.$el.find('.trigger')
-      .text(Drupal.t('@action @title configuration options', {
-        '@action': (!isOpen) ? this.options.strings.open : this.options.strings.close,
-        '@title': this.model.get('title')
-      }))
-      .attr('aria-pressed', isOpen);
-  }
-
-});
+  Drupal.contextual.AuralView = Backbone.View.extend({
+
+    /**
+     * {@inheritdoc}
+     */
+    initialize: function (options) {
+      this.options = options;
+
+      this.listenTo(this.model, 'change', this.render);
+
+      // Use aria-role form so that the number of items in the list is spoken.
+      this.$el.attr('role', 'form');
+
+      // Initial render.
+      this.render();
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    render: function () {
+      var isOpen = this.model.get('isOpen');
+
+      // Set the hidden property of the links.
+      this.$el.find('.contextual-links')
+        .prop('hidden', !isOpen);
+
+      // Update the view of the trigger.
+      this.$el.find('.trigger')
+        .text(Drupal.t('@action @title configuration options', {
+          '@action': (!isOpen) ? this.options.strings.open : this.options.strings.close,
+          '@title': this.model.get('title')
+        }))
+        .attr('aria-pressed', isOpen);
+    }
+
+  });
 
 })(Drupal, Backbone);
diff --git a/core/modules/contextual/js/views/KeyboardView.js b/core/modules/contextual/js/views/KeyboardView.js
index ec55523..d6ebcec 100644
--- a/core/modules/contextual/js/views/KeyboardView.js
+++ b/core/modules/contextual/js/views/KeyboardView.js
@@ -5,47 +5,47 @@
 
 (function (Drupal, Backbone) {
 
-"use strict";
-
-/**
- * Provides keyboard interaction for a contextual link.
- */
-Drupal.contextual.KeyboardView = Backbone.View.extend({
-  events: {
-    'focus .trigger': 'focus',
-    'focus .contextual-links a': 'focus',
-    'blur .trigger': function () { this.model.blur(); },
-    'blur .contextual-links a': function () {
-      // Set up a timeout to allow a user to tab between the trigger and the
-      // contextual links without the menu dismissing.
-      var that = this;
-      this.timer = window.setTimeout(function () {
-        that.model.close().blur();
-      }, 150);
-    }
-  },
+  "use strict";
 
   /**
-   * {@inheritdoc}
+   * Provides keyboard interaction for a contextual link.
    */
-  initialize: function () {
-    // The timer is used to create a delay before dismissing the contextual
-    // links on blur. This is only necessary when keyboard users tab into
-    // contextual links without edit mode (i.e. without TabbingManager).
-    // That means that if we decide to disable tabbing of contextual links
-    // without edit mode, all this timer logic can go away.
-    this.timer = NaN;
-  },
+  Drupal.contextual.KeyboardView = Backbone.View.extend({
+    events: {
+      'focus .trigger': 'focus',
+      'focus .contextual-links a': 'focus',
+      'blur .trigger': function () { this.model.blur(); },
+      'blur .contextual-links a': function () {
+        // Set up a timeout to allow a user to tab between the trigger and the
+        // contextual links without the menu dismissing.
+        var that = this;
+        this.timer = window.setTimeout(function () {
+          that.model.close().blur();
+        }, 150);
+      }
+    },
 
-  /**
-   * Sets focus on the model; Clears the timer that dismisses the links.
-   */
-  focus: function () {
-    // Clear the timeout that might have been set by blurring a link.
-    window.clearTimeout(this.timer);
-    this.model.focus();
-  }
+    /**
+     * {@inheritdoc}
+     */
+    initialize: function () {
+      // The timer is used to create a delay before dismissing the contextual
+      // links on blur. This is only necessary when keyboard users tab into
+      // contextual links without edit mode (i.e. without TabbingManager).
+      // That means that if we decide to disable tabbing of contextual links
+      // without edit mode, all this timer logic can go away.
+      this.timer = NaN;
+    },
+
+    /**
+     * Sets focus on the model; Clears the timer that dismisses the links.
+     */
+    focus: function () {
+      // Clear the timeout that might have been set by blurring a link.
+      window.clearTimeout(this.timer);
+      this.model.focus();
+    }
 
-});
+  });
 
 })(Drupal, Backbone);
diff --git a/core/modules/contextual/js/views/RegionView.js b/core/modules/contextual/js/views/RegionView.js
index c29ce47..10c5c51 100644
--- a/core/modules/contextual/js/views/RegionView.js
+++ b/core/modules/contextual/js/views/RegionView.js
@@ -5,43 +5,43 @@
 
 (function (Drupal, Backbone, Modernizr) {
 
-"use strict";
-
-/**
- * Renders the visual view of a contextual region element.
- */
-Drupal.contextual.RegionView = Backbone.View.extend({
-
-  events: function () {
-    var mapping = {
-      mouseenter: function () { this.model.set('regionIsHovered', true); },
-      mouseleave: function () {
-        this.model.close().blur().set('regionIsHovered', false);
-      }
-    };
-    // We don't want mouse hover events on touch.
-    if (Modernizr.touch) {
-      mapping = {};
-    }
-    return mapping;
-  },
+  "use strict";
 
   /**
-   * {@inheritdoc}
+   * Renders the visual view of a contextual region element.
    */
-  initialize: function () {
-    this.listenTo(this.model, 'change:hasFocus', this.render);
-  },
-
-  /**
-   * {@inheritdoc}
-   */
-  render: function () {
-    this.$el.toggleClass('focus', this.model.get('hasFocus'));
-
-    return this;
-  }
+  Drupal.contextual.RegionView = Backbone.View.extend({
+
+    events: function () {
+      var mapping = {
+        mouseenter: function () { this.model.set('regionIsHovered', true); },
+        mouseleave: function () {
+          this.model.close().blur().set('regionIsHovered', false);
+        }
+      };
+      // We don't want mouse hover events on touch.
+      if (Modernizr.touch) {
+        mapping = {};
+      }
+      return mapping;
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    initialize: function () {
+      this.listenTo(this.model, 'change:hasFocus', this.render);
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    render: function () {
+      this.$el.toggleClass('focus', this.model.get('hasFocus'));
+
+      return this;
+    }
 
-});
+  });
 
 })(Drupal, Backbone, Modernizr);
diff --git a/core/modules/contextual/js/views/VisualView.js b/core/modules/contextual/js/views/VisualView.js
index 3c9119b..c40b30a 100644
--- a/core/modules/contextual/js/views/VisualView.js
+++ b/core/modules/contextual/js/views/VisualView.js
@@ -5,66 +5,66 @@
 
 (function (Drupal, Backbone, Modernizr) {
 
-"use strict";
-
-/**
- * Renders the visual view of a contextual link. Listens to mouse & touch.
- */
-Drupal.contextual.VisualView = Backbone.View.extend({
-
-  events: function () {
-    // Prevents delay and simulated mouse events.
-    var touchEndToClick = function (event) {
-      event.preventDefault();
-      event.target.click();
-    };
-    var mapping = {
-      'click .trigger': function () { this.model.toggleOpen(); },
-      'touchend .trigger': touchEndToClick,
-      'click .contextual-links a': function () { this.model.close().blur(); },
-      'touchend .contextual-links a': touchEndToClick
-    };
-    // We only want mouse hover events on non-touch.
-    if (!Modernizr.touch) {
-      mapping.mouseenter =  function () { this.model.focus(); };
-    }
-    return mapping;
-  },
+  "use strict";
 
   /**
-   * {@inheritdoc}
+   * Renders the visual view of a contextual link. Listens to mouse & touch.
    */
-  initialize: function () {
-    this.listenTo(this.model, 'change', this.render);
-  },
+  Drupal.contextual.VisualView = Backbone.View.extend({
 
-  /**
-   * {@inheritdoc}
-   */
-  render: function () {
-    var isOpen = this.model.get('isOpen');
-    // The trigger should be visible when:
-    //  - the mouse hovered over the region,
-    //  - the trigger is locked,
-    //  - and for as long as the contextual menu is open.
-    var isVisible = this.model.get('isLocked') || this.model.get('regionIsHovered') || isOpen;
+    events: function () {
+      // Prevents delay and simulated mouse events.
+      var touchEndToClick = function (event) {
+        event.preventDefault();
+        event.target.click();
+      };
+      var mapping = {
+        'click .trigger': function () { this.model.toggleOpen(); },
+        'touchend .trigger': touchEndToClick,
+        'click .contextual-links a': function () { this.model.close().blur(); },
+        'touchend .contextual-links a': touchEndToClick
+      };
+      // We only want mouse hover events on non-touch.
+      if (!Modernizr.touch) {
+        mapping.mouseenter = function () { this.model.focus(); };
+      }
+      return mapping;
+    },
 
-    this.$el
-      // The open state determines if the links are visible.
-      .toggleClass('open', isOpen)
-      // Update the visibility of the trigger.
-      .find('.trigger').toggleClass('visually-hidden', !isVisible);
+    /**
+     * {@inheritdoc}
+     */
+    initialize: function () {
+      this.listenTo(this.model, 'change', this.render);
+    },
 
-    // Nested contextual region handling: hide any nested contextual triggers.
-    if ('isOpen' in this.model.changed) {
-      this.$el.closest('.contextual-region')
-        .find('.contextual .trigger:not(:first)')
-        .toggle(!isOpen);
-    }
+    /**
+     * {@inheritdoc}
+     */
+    render: function () {
+      var isOpen = this.model.get('isOpen');
+      // The trigger should be visible when:
+      //  - the mouse hovered over the region,
+      //  - the trigger is locked,
+      //  - and for as long as the contextual menu is open.
+      var isVisible = this.model.get('isLocked') || this.model.get('regionIsHovered') || isOpen;
+
+      this.$el
+        // The open state determines if the links are visible.
+        .toggleClass('open', isOpen)
+        // Update the visibility of the trigger.
+        .find('.trigger').toggleClass('visually-hidden', !isVisible);
 
-    return this;
-  }
+      // Nested contextual region handling: hide any nested contextual triggers.
+      if ('isOpen' in this.model.changed) {
+        this.$el.closest('.contextual-region')
+          .find('.contextual .trigger:not(:first)')
+          .toggle(!isOpen);
+      }
+
+      return this;
+    }
 
-});
+  });
 
 })(Drupal, Backbone, Modernizr);
diff --git a/core/modules/edit/js/edit.js b/core/modules/edit/js/edit.js
index 127fbbe..611886d 100644
--- a/core/modules/edit/js/edit.js
+++ b/core/modules/edit/js/edit.js
@@ -19,563 +19,563 @@
 
 (function ($, _, Backbone, Drupal, drupalSettings, JSON, storage) {
 
-"use strict";
+  "use strict";
 
-var options = $.extend(drupalSettings.edit,
-  // Merge strings on top of drupalSettings so that they are not mutable.
-  {
-    strings: {
-      quickEdit: Drupal.t('Quick edit')
+  var options = $.extend(drupalSettings.edit,
+    // Merge strings on top of drupalSettings so that they are not mutable.
+    {
+      strings: {
+        quickEdit: Drupal.t('Quick edit')
+      }
     }
-  }
-);
-
-/**
- * Tracks fields without metadata. Contains objects with the following keys:
- *   - DOM el
- *   - String fieldID
- *   - String entityID
- */
-var fieldsMetadataQueue = [];
+  );
+
+  /**
+   * Tracks fields without metadata. Contains objects with the following keys:
+   *   - DOM el
+   *   - String fieldID
+   *   - String entityID
+   */
+  var fieldsMetadataQueue = [];
+
+  /**
+   * Tracks fields ready for use. Contains objects with the following keys:
+   *   - DOM el
+   *   - String fieldID
+   *   - String entityID
+   */
+  var fieldsAvailableQueue = [];
+
+  /**
+   * Tracks contextual links on entities. Contains objects with the following
+   * keys:
+   *   - String entityID
+   *   - DOM el
+   *   - DOM region
+   */
+  var contextualLinksQueue = [];
+
+  /**
+   * Tracks how many instances exist for each unique entity. Contains key-value
+   * pairs:
+   * - String entityID
+   * - Number count
+   */
+  var entityInstancesTracker = {};
+
+  Drupal.behaviors.edit = {
+    attach: function (context) {
+      // Initialize the Edit app once per page load.
+      $('body').once('edit-init', initEdit);
+
+      // Find all in-place editable fields, if any.
+      var $fields = $(context).find('[data-edit-field-id]').once('edit');
+      if ($fields.length === 0) {
+        return;
+      }
 
-/**
- * Tracks fields ready for use. Contains objects with the following keys:
- *   - DOM el
- *   - String fieldID
- *   - String entityID
- */
-var fieldsAvailableQueue = [];
+      // Process each entity element: identical entities that appear multiple
+      // times will get a numeric identifier, starting at 0.
+      $(context).find('[data-edit-entity-id]').once('edit').each(function (index, entityElement) {
+        processEntity(entityElement);
+      });
 
-/**
- * Tracks contextual links on entities. Contains objects with the following
- * keys:
- *   - String entityID
- *   - DOM el
- *   - DOM region
- */
-var contextualLinksQueue = [];
+      // Process each field element: queue to be used or to fetch metadata.
+      // When a field is being rerendered after editing, it will be processed
+      // immediately. New fields will be unable to be processed immediately, but
+      // will instead be queued to have their metadata fetched, which occurs below
+      // in fetchMissingMetaData().
+      $fields.each(function (index, fieldElement) {
+        processField(fieldElement);
+      });
 
-/**
- * Tracks how many instances exist for each unique entity. Contains key-value
- * pairs:
- * - String entityID
- * - Number count
- */
-var entityInstancesTracker = {};
+      // Entities and fields on the page have been detected, try to set up the
+      // contextual links for those entities that already have the necessary meta-
+      // data in the client-side cache.
+      contextualLinksQueue = _.filter(contextualLinksQueue, function (contextualLink) {
+        return !initializeEntityContextualLink(contextualLink);
+      });
 
-Drupal.behaviors.edit = {
-  attach: function (context) {
-    // Initialize the Edit app once per page load.
-    $('body').once('edit-init', initEdit);
+      // Fetch metadata for any fields that are queued to retrieve it.
+      fetchMissingMetadata(function (fieldElementsWithFreshMetadata) {
+        // Metadata has been fetched, reprocess fields whose metadata was missing.
+        _.each(fieldElementsWithFreshMetadata, processField);
 
-    // Find all in-place editable fields, if any.
-    var $fields = $(context).find('[data-edit-field-id]').once('edit');
-    if ($fields.length === 0) {
-      return;
+        // Metadata has been fetched, try to set up more contextual links now.
+        contextualLinksQueue = _.filter(contextualLinksQueue, function (contextualLink) {
+          return !initializeEntityContextualLink(contextualLink);
+        });
+      });
+    },
+    detach: function (context, settings, trigger) {
+      if (trigger === 'unload') {
+        deleteContainedModelsAndQueues($(context));
+      }
     }
+  };
 
-    // Process each entity element: identical entities that appear multiple
-    // times will get a numeric identifier, starting at 0.
-    $(context).find('[data-edit-entity-id]').once('edit').each(function (index, entityElement) {
-      processEntity(entityElement);
-    });
+  Drupal.edit = {
+    // A Drupal.edit.AppView instance.
+    app: null,
 
-    // Process each field element: queue to be used or to fetch metadata.
-    // When a field is being rerendered after editing, it will be processed
-    // immediately. New fields will be unable to be processed immediately, but
-    // will instead be queued to have their metadata fetched, which occurs below
-    // in fetchMissingMetaData().
-    $fields.each(function (index, fieldElement) {
-      processField(fieldElement);
-    });
+    collections: {
+      // All in-place editable entities (Drupal.edit.EntityModel) on the page.
+      entities: null,
+      // All in-place editable fields (Drupal.edit.FieldModel) on the page.
+      fields: null
+    },
 
-    // Entities and fields on the page have been detected, try to set up the
-    // contextual links for those entities that already have the necessary meta-
-    // data in the client-side cache.
-    contextualLinksQueue = _.filter(contextualLinksQueue, function (contextualLink) {
-      return !initializeEntityContextualLink(contextualLink);
-    });
+    // In-place editors will register themselves in this object.
+    editors: {},
 
-    // Fetch metadata for any fields that are queued to retrieve it.
-    fetchMissingMetadata(function (fieldElementsWithFreshMetadata) {
-      // Metadata has been fetched, reprocess fields whose metadata was missing.
-      _.each(fieldElementsWithFreshMetadata, processField);
+    // Per-field metadata that indicates whether in-place editing is allowed,
+    // which in-place editor should be used, etc.
+    metadata: {
+      has: function (fieldID) {
+        return storage.getItem(this._prefixFieldID(fieldID)) !== null;
+      },
+      add: function (fieldID, metadata) {
+        storage.setItem(this._prefixFieldID(fieldID), JSON.stringify(metadata));
+      },
+      get: function (fieldID, key) {
+        var metadata = JSON.parse(storage.getItem(this._prefixFieldID(fieldID)));
+        return (key === undefined) ? metadata : metadata[key];
+      },
+      _prefixFieldID: function (fieldID) {
+        return 'Drupal.edit.metadata.' + fieldID;
+      },
+      _unprefixFieldID: function (fieldID) {
+        // Strip "Drupal.edit.metadata.", which is 21 characters long.
+        return fieldID.substring(21);
+      },
+      intersection: function (fieldIDs) {
+        var prefixedFieldIDs = _.map(fieldIDs, this._prefixFieldID);
+        var intersection = _.intersection(prefixedFieldIDs, _.keys(sessionStorage));
+        return _.map(intersection, this._unprefixFieldID);
+      }
+    }
+  };
 
-      // Metadata has been fetched, try to set up more contextual links now.
-      contextualLinksQueue = _.filter(contextualLinksQueue, function (contextualLink) {
-        return !initializeEntityContextualLink(contextualLink);
+  // Clear the Edit metadata cache whenever the current user's set of permissions
+  // changes.
+  var permissionsHashKey = Drupal.edit.metadata._prefixFieldID('permissionsHash');
+  var permissionsHashValue = storage.getItem(permissionsHashKey);
+  var permissionsHash = drupalSettings.user.permissionsHash;
+  if (permissionsHashValue !== permissionsHash) {
+    if (typeof permissionsHash === 'string') {
+      _.chain(storage).keys().each(function (key) {
+        if (key.substring(0, 21) === 'Drupal.edit.metadata.') {
+          storage.removeItem(key);
+        }
       });
-    });
-  },
-  detach: function (context, settings, trigger) {
-    if (trigger === 'unload') {
-      deleteContainedModelsAndQueues($(context));
     }
+    storage.setItem(permissionsHashKey, permissionsHash);
   }
-};
-
-Drupal.edit = {
-  // A Drupal.edit.AppView instance.
-  app: null,
-
-  collections: {
-    // All in-place editable entities (Drupal.edit.EntityModel) on the page.
-    entities: null,
-    // All in-place editable fields (Drupal.edit.FieldModel) on the page.
-    fields: null
-  },
-
-  // In-place editors will register themselves in this object.
-  editors: {},
-
-  // Per-field metadata that indicates whether in-place editing is allowed,
-  // which in-place editor should be used, etc.
-  metadata: {
-    has: function (fieldID) {
-      return storage.getItem(this._prefixFieldID(fieldID)) !== null;
-    },
-    add: function (fieldID, metadata) {
-      storage.setItem(this._prefixFieldID(fieldID), JSON.stringify(metadata));
-    },
-    get: function (fieldID, key) {
-      var metadata = JSON.parse(storage.getItem(this._prefixFieldID(fieldID)));
-      return (key === undefined) ? metadata : metadata[key];
-    },
-    _prefixFieldID: function (fieldID) {
-      return 'Drupal.edit.metadata.' + fieldID;
-    },
-    _unprefixFieldID: function (fieldID) {
-      // Strip "Drupal.edit.metadata.", which is 21 characters long.
-      return fieldID.substring(21);
-    },
-    intersection: function (fieldIDs) {
-      var prefixedFieldIDs = _.map(fieldIDs, this._prefixFieldID);
-      var intersection = _.intersection(prefixedFieldIDs, _.keys(sessionStorage));
-      return _.map(intersection, this._unprefixFieldID);
+
+  /**
+   * Detect contextual links on entities annotated by Edit; queue these to be
+   * processed.
+   */
+  $(document).on('drupalContextualLinkAdded', function (event, data) {
+    if (data.$region.is('[data-edit-entity-id]')) {
+      // If the contextual link is cached on the client side, an entity instance
+      // will not yet have been assigned. So assign one.
+      if (!data.$region.is('[data-edit-entity-instance-id]')) {
+        data.$region.once('edit');
+        processEntity(data.$region.get(0));
+      }
+      var contextualLink = {
+        entityID: data.$region.attr('data-edit-entity-id'),
+        entityInstanceID: data.$region.attr('data-edit-entity-instance-id'),
+        el: data.$el[0],
+        region: data.$region[0]
+      };
+      // Set up contextual links for this, otherwise queue it to be set up later.
+      if (!initializeEntityContextualLink(contextualLink)) {
+        contextualLinksQueue.push(contextualLink);
+      }
     }
+  });
+
+  /**
+   * Extracts the entity ID from a field ID.
+   *
+   * @param String fieldID
+   *   A field ID: a string of the format
+   *   `<entity type>/<id>/<field name>/<language>/<view mode>`.
+   * @return String
+   *   An entity ID: a string of the format `<entity type>/<id>`.
+   */
+  function extractEntityID(fieldID) {
+    return fieldID.split('/').slice(0, 2).join('/');
   }
-};
-
-// Clear the Edit metadata cache whenever the current user's set of permissions
-// changes.
-var permissionsHashKey = Drupal.edit.metadata._prefixFieldID('permissionsHash');
-var permissionsHashValue = storage.getItem(permissionsHashKey);
-var permissionsHash = drupalSettings.user.permissionsHash;
-if (permissionsHashValue !== permissionsHash) {
-  if (typeof permissionsHash === 'string') {
-    _.chain(storage).keys().each(function (key) {
-      if (key.substring(0, 21) === 'Drupal.edit.metadata.') {
-        storage.removeItem(key);
-      }
+
+  /**
+   * Initialize the Edit app.
+   *
+   * @param DOM bodyElement
+   *   This document's body element.
+   */
+  function initEdit(bodyElement) {
+    Drupal.edit.collections.entities = new Drupal.edit.EntityCollection();
+    Drupal.edit.collections.fields = new Drupal.edit.FieldCollection();
+
+    // Instantiate AppModel (application state) and AppView, which is the
+    // controller of the whole in-place editing experience.
+    Drupal.edit.app = new Drupal.edit.AppView({
+      el: bodyElement,
+      model: new Drupal.edit.AppModel(),
+      entitiesCollection: Drupal.edit.collections.entities,
+      fieldsCollection: Drupal.edit.collections.fields
     });
   }
-  storage.setItem(permissionsHashKey, permissionsHash);
-}
 
-/**
- * Detect contextual links on entities annotated by Edit; queue these to be
- * processed.
- */
-$(document).on('drupalContextualLinkAdded', function (event, data) {
-  if (data.$region.is('[data-edit-entity-id]')) {
-    // If the contextual link is cached on the client side, an entity instance
-    // will not yet have been assigned. So assign one.
-    if (!data.$region.is('[data-edit-entity-instance-id]')) {
-      data.$region.once('edit');
-      processEntity(data.$region.get(0));
+  /**
+   * Assigns the entity an instance ID.
+   *
+   * @param DOM entityElement.
+   *   A Drupal Entity API entity's DOM element with a data-edit-entity-id
+   *   attribute.
+   */
+  function processEntity(entityElement) {
+    var entityID = entityElement.getAttribute('data-edit-entity-id');
+    if (!entityInstancesTracker.hasOwnProperty(entityID)) {
+      entityInstancesTracker[entityID] = 0;
     }
-    var contextualLink = {
-      entityID: data.$region.attr('data-edit-entity-id'),
-      entityInstanceID: data.$region.attr('data-edit-entity-instance-id'),
-      el: data.$el[0],
-      region: data.$region[0]
-    };
-    // Set up contextual links for this, otherwise queue it to be set up later.
-    if (!initializeEntityContextualLink(contextualLink)) {
-      contextualLinksQueue.push(contextualLink);
+    else {
+      entityInstancesTracker[entityID]++;
     }
-  }
-});
-
-/**
- * Extracts the entity ID from a field ID.
- *
- * @param String fieldID
- *   A field ID: a string of the format
- *   `<entity type>/<id>/<field name>/<language>/<view mode>`.
- * @return String
- *   An entity ID: a string of the format `<entity type>/<id>`.
- */
-function extractEntityID (fieldID) {
-  return fieldID.split('/').slice(0, 2).join('/');
-}
-
-/**
- * Initialize the Edit app.
- *
- * @param DOM bodyElement
- *   This document's body element.
- */
-function initEdit (bodyElement) {
-  Drupal.edit.collections.entities = new Drupal.edit.EntityCollection();
-  Drupal.edit.collections.fields = new Drupal.edit.FieldCollection();
-
-  // Instantiate AppModel (application state) and AppView, which is the
-  // controller of the whole in-place editing experience.
-  Drupal.edit.app = new Drupal.edit.AppView({
-    el: bodyElement,
-    model: new Drupal.edit.AppModel(),
-    entitiesCollection: Drupal.edit.collections.entities,
-    fieldsCollection: Drupal.edit.collections.fields
-  });
-}
 
-/**
- * Assigns the entity an instance ID.
- *
- * @param DOM entityElement.
- *   A Drupal Entity API entity's DOM element with a data-edit-entity-id
- *   attribute.
- */
-function processEntity (entityElement) {
-  var entityID = entityElement.getAttribute('data-edit-entity-id');
-  if (!entityInstancesTracker.hasOwnProperty(entityID)) {
-    entityInstancesTracker[entityID] = 0;
-  }
-  else {
-    entityInstancesTracker[entityID]++;
+    // Set the calculated entity instance ID for this element.
+    var entityInstanceID = entityInstancesTracker[entityID];
+    entityElement.setAttribute('data-edit-entity-instance-id', entityInstanceID);
   }
 
-  // Set the calculated entity instance ID for this element.
-  var entityInstanceID = entityInstancesTracker[entityID];
-  entityElement.setAttribute('data-edit-entity-instance-id', entityInstanceID);
-}
+  /**
+   * Fetch the field's metadata; queue or initialize it (if EntityModel exists).
+   *
+   * @param DOM fieldElement
+   *   A Drupal Field API field's DOM element with a data-edit-field-id attribute.
+   */
+  function processField(fieldElement) {
+    var metadata = Drupal.edit.metadata;
+    var fieldID = fieldElement.getAttribute('data-edit-field-id');
+    var entityID = extractEntityID(fieldID);
+    // Figure out the instance ID by looking at the ancestor [data-edit-entity-id]
+    // element's data-edit-entity-instance-id attribute.
+    var entityElementSelector = '[data-edit-entity-id="' + entityID + '"]';
+    var entityElement = $(fieldElement).closest(entityElementSelector);
+    // In the case of a full entity view page, the entity title is rendered
+    // outside of "the entity DOM node": it's rendered as the page title. So in
+    // this case, we must find the entity in the mandatory "content" region.
+    if (entityElement.length === 0) {
+      entityElement = $('.region-content').find(entityElementSelector);
+    }
+    var entityInstanceID = entityElement
+      .get(0)
+      .getAttribute('data-edit-entity-instance-id');
+
+    // Early-return if metadata for this field is missing.
+    if (!metadata.has(fieldID)) {
+      fieldsMetadataQueue.push({
+        el: fieldElement,
+        fieldID: fieldID,
+        entityID: entityID,
+        entityInstanceID: entityInstanceID
+      });
+      return;
+    }
+    // Early-return if the user is not allowed to in-place edit this field.
+    if (metadata.get(fieldID, 'access') !== true) {
+      return;
+    }
 
-/**
- * Fetch the field's metadata; queue or initialize it (if EntityModel exists).
- *
- * @param DOM fieldElement
- *   A Drupal Field API field's DOM element with a data-edit-field-id attribute.
- */
-function processField (fieldElement) {
-  var metadata = Drupal.edit.metadata;
-  var fieldID = fieldElement.getAttribute('data-edit-field-id');
-  var entityID = extractEntityID(fieldID);
-  // Figure out the instance ID by looking at the ancestor [data-edit-entity-id]
-  // element's data-edit-entity-instance-id attribute.
-  var entityElementSelector = '[data-edit-entity-id="' + entityID + '"]';
-  var entityElement = $(fieldElement).closest(entityElementSelector);
-  // In the case of a full entity view page, the entity title is rendered
-  // outside of "the entity DOM node": it's rendered as the page title. So in
-  // this case, we must find the entity in the mandatory "content" region.
-  if (entityElement.length === 0) {
-    entityElement = $('.region-content').find(entityElementSelector);
+    // If an EntityModel for this field already exists (and hence also a "Quick
+    // edit" contextual link), then initialize it immediately.
+    if (Drupal.edit.collections.entities.findWhere({ entityID: entityID, entityInstanceID: entityInstanceID })) {
+      initializeField(fieldElement, fieldID, entityID, entityInstanceID);
+    }
+    // Otherwise: queue the field. It is now available to be set up when its
+    // corresponding entity becomes in-place editable.
+    else {
+      fieldsAvailableQueue.push({ el: fieldElement, fieldID: fieldID, entityID: entityID, entityInstanceID: entityInstanceID });
+    }
   }
-  var entityInstanceID = entityElement
-    .get(0)
-    .getAttribute('data-edit-entity-instance-id');
 
-  // Early-return if metadata for this field is missing.
-  if (!metadata.has(fieldID)) {
-    fieldsMetadataQueue.push({
-      el: fieldElement,
-      fieldID: fieldID,
+  /**
+   * Initialize a field; create FieldModel.
+   *
+   * @param DOM fieldElement
+   *   The field's DOM element.
+   * @param String fieldID
+   *   The field's ID.
+   * @param String entityID
+   *   The field's entity's ID.
+   * @param String entityInstanceID
+   *   The field's entity's instance ID.
+   */
+  function initializeField(fieldElement, fieldID, entityID, entityInstanceID) {
+    var entity = Drupal.edit.collections.entities.findWhere({
       entityID: entityID,
       entityInstanceID: entityInstanceID
     });
-    return;
-  }
-  // Early-return if the user is not allowed to in-place edit this field.
-  if (metadata.get(fieldID, 'access') !== true) {
-    return;
-  }
-
-  // If an EntityModel for this field already exists (and hence also a "Quick
-  // edit" contextual link), then initialize it immediately.
-  if (Drupal.edit.collections.entities.findWhere({ entityID: entityID, entityInstanceID: entityInstanceID })) {
-    initializeField(fieldElement, fieldID, entityID, entityInstanceID);
-  }
-  // Otherwise: queue the field. It is now available to be set up when its
-  // corresponding entity becomes in-place editable.
-  else {
-    fieldsAvailableQueue.push({ el: fieldElement, fieldID: fieldID, entityID: entityID, entityInstanceID: entityInstanceID });
-  }
-}
-
-/**
- * Initialize a field; create FieldModel.
- *
- * @param DOM fieldElement
- *   The field's DOM element.
- * @param String fieldID
- *   The field's ID.
- * @param String entityID
- *   The field's entity's ID.
- * @param String entityInstanceID
- *   The field's entity's instance ID.
- */
-function initializeField (fieldElement, fieldID, entityID, entityInstanceID) {
-  var entity = Drupal.edit.collections.entities.findWhere({
-    entityID: entityID,
-    entityInstanceID: entityInstanceID
-  });
 
-  $(fieldElement).addClass('edit-field');
-
-  // The FieldModel stores the state of an in-place editable entity field.
-  var field = new Drupal.edit.FieldModel({
-    el: fieldElement,
-    fieldID: fieldID,
-    id: fieldID + '[' + entity.get('entityInstanceID') + ']',
-    entity: entity,
-    metadata: Drupal.edit.metadata.get(fieldID),
-    acceptStateChange: _.bind(Drupal.edit.app.acceptEditorStateChange, Drupal.edit.app)
-  });
-
-  // Track all fields on the page.
-  Drupal.edit.collections.fields.add(field);
-}
-
-/**
- * Fetches metadata for fields whose metadata is missing.
- *
- * Fields whose metadata is missing are tracked at fieldsMetadataQueue.
- *
- * @param Function callback
- *   A callback function that receives field elements whose metadata will just
- *   have been fetched.
- */
-function fetchMissingMetadata (callback) {
-  if (fieldsMetadataQueue.length) {
-    var fieldIDs = _.pluck(fieldsMetadataQueue, 'fieldID');
-    var fieldElementsWithoutMetadata = _.pluck(fieldsMetadataQueue, 'el');
-    var entityIDs = _.uniq(_.pluck(fieldsMetadataQueue, 'entityID'), true);
-    // Ensure we only request entityIDs for which we don't have metadata yet.
-    entityIDs = _.difference(entityIDs, Drupal.edit.metadata.intersection(entityIDs));
-    fieldsMetadataQueue = [];
-
-    $.ajax({
-      url: Drupal.url('edit/metadata'),
-      type: 'POST',
-      data: {
-        'fields[]': fieldIDs,
-        'entities[]': entityIDs
-      },
-      dataType: 'json',
-      success: function(results) {
-        // Store the metadata.
-        _.each(results, function (fieldMetadata, fieldID) {
-          Drupal.edit.metadata.add(fieldID, fieldMetadata);
-        });
+    $(fieldElement).addClass('edit-field');
 
-        callback(fieldElementsWithoutMetadata);
-      }
+    // The FieldModel stores the state of an in-place editable entity field.
+    var field = new Drupal.edit.FieldModel({
+      el: fieldElement,
+      fieldID: fieldID,
+      id: fieldID + '[' + entity.get('entityInstanceID') + ']',
+      entity: entity,
+      metadata: Drupal.edit.metadata.get(fieldID),
+      acceptStateChange: _.bind(Drupal.edit.app.acceptEditorStateChange, Drupal.edit.app)
     });
+
+    // Track all fields on the page.
+    Drupal.edit.collections.fields.add(field);
   }
-}
 
-/**
- * Loads missing in-place editor's attachments (JavaScript and CSS files).
- *
- * Missing in-place editors are those whose fields are actively being used on
- * the page but don't have
- *
- * @param Function callback
- *   Callback function to be called when the missing in-place editors (if any)
- *   have been inserted into the DOM. i.e. they may still be loading.
- */
-function loadMissingEditors (callback) {
-  var loadedEditors = _.keys(Drupal.edit.editors);
-  var missingEditors = [];
-  Drupal.edit.collections.fields.each(function (fieldModel) {
-    var metadata = Drupal.edit.metadata.get(fieldModel.get('fieldID'));
-    if (metadata.access && _.indexOf(loadedEditors, metadata.editor) === -1) {
-      missingEditors.push(metadata.editor);
+  /**
+   * Fetches metadata for fields whose metadata is missing.
+   *
+   * Fields whose metadata is missing are tracked at fieldsMetadataQueue.
+   *
+   * @param Function callback
+   *   A callback function that receives field elements whose metadata will just
+   *   have been fetched.
+   */
+  function fetchMissingMetadata(callback) {
+    if (fieldsMetadataQueue.length) {
+      var fieldIDs = _.pluck(fieldsMetadataQueue, 'fieldID');
+      var fieldElementsWithoutMetadata = _.pluck(fieldsMetadataQueue, 'el');
+      var entityIDs = _.uniq(_.pluck(fieldsMetadataQueue, 'entityID'), true);
+      // Ensure we only request entityIDs for which we don't have metadata yet.
+      entityIDs = _.difference(entityIDs, Drupal.edit.metadata.intersection(entityIDs));
+      fieldsMetadataQueue = [];
+
+      $.ajax({
+        url: Drupal.url('edit/metadata'),
+        type: 'POST',
+        data: {
+          'fields[]': fieldIDs,
+          'entities[]': entityIDs
+        },
+        dataType: 'json',
+        success: function (results) {
+          // Store the metadata.
+          _.each(results, function (fieldMetadata, fieldID) {
+            Drupal.edit.metadata.add(fieldID, fieldMetadata);
+          });
+
+          callback(fieldElementsWithoutMetadata);
+        }
+      });
     }
-  });
-  missingEditors = _.uniq(missingEditors);
-  if (missingEditors.length === 0) {
-    callback();
   }
 
-  // @todo Simplify this once https://drupal.org/node/1533366 lands.
-  // @see https://drupal.org/node/2029999.
-  var id = 'edit-load-editors';
-  // Create a temporary element to be able to use Drupal.ajax.
-  var $el = $('<div id="' + id + '" class="element-hidden"></div>').appendTo('body');
-  // Create a Drupal.ajax instance to load the form.
-  var loadEditorsAjax = new Drupal.ajax(id, $el, {
-    url: Drupal.url('edit/attachments'),
-    event: 'edit-internal.edit',
-    submit: { 'editors[]': missingEditors },
-    // No progress indicator.
-    progress: { type: null }
-  });
-  // Implement a scoped insert AJAX command: calls the callback after all AJAX
-  // command functions have been executed (hence the deferred calling).
-  var realInsert = Drupal.AjaxCommands.prototype.insert;
-  loadEditorsAjax.commands.insert = function (ajax, response, status) {
-    _.defer(callback);
-    realInsert(ajax, response, status);
-    $el.off('edit-internal.edit');
-    $el.remove();
-  };
-  // Trigger the AJAX request, which will should return AJAX commands to insert
-  // any missing attachments.
-  $el.trigger('edit-internal.edit');
-}
-
-/**
- * Attempts to set up a "Quick edit" link and corresponding EntityModel.
- *
- * @param Object contextualLink
- *   An object with the following properties:
- *     - String entityID: an Edit entity identifier, e.g. "node/1" or
- *       "custom_block/5".
- *     - String entityInstanceID: an Edit entity instance identifier, e.g. 0, 1
- *       or n (depending on whether it's the first, second, or n+1st instance of
- *       this entity).
- *     - DOM el: element pointing to the contextual links placeholder for this
- *       entity.
- *     - DOM region: element pointing to the contextual region for this entity.
- * @return Boolean
- *   Returns true when a contextual the given contextual link metadata can be
- *   removed from the queue (either because the contextual link has been set up
- *   or because it is certain that in-place editing is not allowed for any of
- *   its fields).
- *   Returns false otherwise.
- */
-function initializeEntityContextualLink (contextualLink) {
-  var metadata = Drupal.edit.metadata;
-  // Check if the user has permission to edit at least one of them.
-  function hasFieldWithPermission (fieldIDs) {
-    for (var i = 0; i < fieldIDs.length; i++) {
-      var fieldID = fieldIDs[i];
-      if (metadata.get(fieldID, 'access') === true) {
-        return true;
+  /**
+   * Loads missing in-place editor's attachments (JavaScript and CSS files).
+   *
+   * Missing in-place editors are those whose fields are actively being used on
+   * the page but don't have
+   *
+   * @param Function callback
+   *   Callback function to be called when the missing in-place editors (if any)
+   *   have been inserted into the DOM. i.e. they may still be loading.
+   */
+  function loadMissingEditors(callback) {
+    var loadedEditors = _.keys(Drupal.edit.editors);
+    var missingEditors = [];
+    Drupal.edit.collections.fields.each(function (fieldModel) {
+      var metadata = Drupal.edit.metadata.get(fieldModel.get('fieldID'));
+      if (metadata.access && _.indexOf(loadedEditors, metadata.editor) === -1) {
+        missingEditors.push(metadata.editor);
       }
+    });
+    missingEditors = _.uniq(missingEditors);
+    if (missingEditors.length === 0) {
+      callback();
     }
-    return false;
-  }
 
-  // Checks if the metadata for all given field IDs exists.
-  function allMetadataExists (fieldIDs) {
-    return fieldIDs.length === metadata.intersection(fieldIDs).length;
+    // @todo Simplify this once https://drupal.org/node/1533366 lands.
+    // @see https://drupal.org/node/2029999.
+    var id = 'edit-load-editors';
+    // Create a temporary element to be able to use Drupal.ajax.
+    var $el = $('<div id="' + id + '" class="element-hidden"></div>').appendTo('body');
+    // Create a Drupal.ajax instance to load the form.
+    var loadEditorsAjax = new Drupal.ajax(id, $el, {
+      url: Drupal.url('edit/attachments'),
+      event: 'edit-internal.edit',
+      submit: { 'editors[]': missingEditors },
+      // No progress indicator.
+      progress: { type: null }
+    });
+    // Implement a scoped insert AJAX command: calls the callback after all AJAX
+    // command functions have been executed (hence the deferred calling).
+    var realInsert = Drupal.AjaxCommands.prototype.insert;
+    loadEditorsAjax.commands.insert = function (ajax, response, status) {
+      _.defer(callback);
+      realInsert(ajax, response, status);
+      $el.off('edit-internal.edit');
+      $el.remove();
+    };
+    // Trigger the AJAX request, which will should return AJAX commands to insert
+    // any missing attachments.
+    $el.trigger('edit-internal.edit');
   }
 
-  // Find all fields for this entity instance and collect their field IDs.
-  var fields = _.where(fieldsAvailableQueue, {
-    entityID: contextualLink.entityID,
-    entityInstanceID: contextualLink.entityInstanceID
-  });
-  var fieldIDs = _.pluck(fields, 'fieldID');
+  /**
+   * Attempts to set up a "Quick edit" link and corresponding EntityModel.
+   *
+   * @param Object contextualLink
+   *   An object with the following properties:
+   *     - String entityID: an Edit entity identifier, e.g. "node/1" or
+   *       "custom_block/5".
+   *     - String entityInstanceID: an Edit entity instance identifier, e.g. 0, 1
+   *       or n (depending on whether it's the first, second, or n+1st instance of
+   *       this entity).
+   *     - DOM el: element pointing to the contextual links placeholder for this
+   *       entity.
+   *     - DOM region: element pointing to the contextual region for this entity.
+   * @return Boolean
+   *   Returns true when a contextual the given contextual link metadata can be
+   *   removed from the queue (either because the contextual link has been set up
+   *   or because it is certain that in-place editing is not allowed for any of
+   *   its fields).
+   *   Returns false otherwise.
+   */
+  function initializeEntityContextualLink(contextualLink) {
+    var metadata = Drupal.edit.metadata;
+    // Check if the user has permission to edit at least one of them.
+    function hasFieldWithPermission(fieldIDs) {
+      for (var i = 0; i < fieldIDs.length; i++) {
+        var fieldID = fieldIDs[i];
+        if (metadata.get(fieldID, 'access') === true) {
+          return true;
+        }
+      }
+      return false;
+    }
 
-  // No fields found yet.
-  if (fieldIDs.length === 0) {
-    return false;
-  }
-  // The entity for the given contextual link contains at least one field that
-  // the current user may edit in-place; instantiate EntityModel,
-  // EntityDecorationView and ContextualLinkView.
-  else if (hasFieldWithPermission(fieldIDs)) {
-    var entityModel = new Drupal.edit.EntityModel({
-      el: contextualLink.region,
-      entityID: contextualLink.entityID,
-      entityInstanceID: contextualLink.entityInstanceID,
-      id: contextualLink.entityID + '[' + contextualLink.entityInstanceID + ']',
-      label: Drupal.edit.metadata.get(contextualLink.entityID, 'label')
-    });
-    Drupal.edit.collections.entities.add(entityModel);
-    // Create an EntityDecorationView associated with the root DOM node of the
-    // entity.
-    var entityDecorationView = new Drupal.edit.EntityDecorationView({
-      el: contextualLink.region,
-      model: entityModel
-    });
-    entityModel.set('entityDecorationView', entityDecorationView);
+    // Checks if the metadata for all given field IDs exists.
+    function allMetadataExists(fieldIDs) {
+      return fieldIDs.length === metadata.intersection(fieldIDs).length;
+    }
 
-    // Initialize all queued fields within this entity (creates FieldModels).
-    _.each(fields, function (field) {
-      initializeField(field.el, field.fieldID, contextualLink.entityID, contextualLink.entityInstanceID);
-    });
-    fieldsAvailableQueue = _.difference(fieldsAvailableQueue, fields);
-
-    // Initialization should only be called once. Use Underscore's once method
-    // to get a one-time use version of the function.
-    var initContextualLink = _.once(function () {
-      var $links = $(contextualLink.el).find('.contextual-links');
-      var contextualLinkView = new Drupal.edit.ContextualLinkView($.extend({
-        el: $('<li class="quick-edit"><a href="" role="button" aria-pressed="false"></a></li>').prependTo($links),
-        model: entityModel,
-        appModel: Drupal.edit.app.model
-      }, options));
-      entityModel.set('contextualLinkView', contextualLinkView);
+    // Find all fields for this entity instance and collect their field IDs.
+    var fields = _.where(fieldsAvailableQueue, {
+      entityID: contextualLink.entityID,
+      entityInstanceID: contextualLink.entityInstanceID
     });
+    var fieldIDs = _.pluck(fields, 'fieldID');
 
-    // Set up ContextualLinkView after loading any missing in-place editors.
-    loadMissingEditors(initContextualLink);
+    // No fields found yet.
+    if (fieldIDs.length === 0) {
+      return false;
+    }
+    // The entity for the given contextual link contains at least one field that
+    // the current user may edit in-place; instantiate EntityModel,
+    // EntityDecorationView and ContextualLinkView.
+    else if (hasFieldWithPermission(fieldIDs)) {
+      var entityModel = new Drupal.edit.EntityModel({
+        el: contextualLink.region,
+        entityID: contextualLink.entityID,
+        entityInstanceID: contextualLink.entityInstanceID,
+        id: contextualLink.entityID + '[' + contextualLink.entityInstanceID + ']',
+        label: Drupal.edit.metadata.get(contextualLink.entityID, 'label')
+      });
+      Drupal.edit.collections.entities.add(entityModel);
+      // Create an EntityDecorationView associated with the root DOM node of the
+      // entity.
+      var entityDecorationView = new Drupal.edit.EntityDecorationView({
+        el: contextualLink.region,
+        model: entityModel
+      });
+      entityModel.set('entityDecorationView', entityDecorationView);
 
-    return true;
-  }
-  // There was not at least one field that the current user may edit in-place,
-  // even though the metadata for all fields within this entity is available.
-  else if (allMetadataExists(fieldIDs)) {
-    return true;
-  }
+      // Initialize all queued fields within this entity (creates FieldModels).
+      _.each(fields, function (field) {
+        initializeField(field.el, field.fieldID, contextualLink.entityID, contextualLink.entityInstanceID);
+      });
+      fieldsAvailableQueue = _.difference(fieldsAvailableQueue, fields);
+
+      // Initialization should only be called once. Use Underscore's once method
+      // to get a one-time use version of the function.
+      var initContextualLink = _.once(function () {
+        var $links = $(contextualLink.el).find('.contextual-links');
+        var contextualLinkView = new Drupal.edit.ContextualLinkView($.extend({
+          el: $('<li class="quick-edit"><a href="" role="button" aria-pressed="false"></a></li>').prependTo($links),
+          model: entityModel,
+          appModel: Drupal.edit.app.model
+        }, options));
+        entityModel.set('contextualLinkView', contextualLinkView);
+      });
 
-  return false;
-}
+      // Set up ContextualLinkView after loading any missing in-place editors.
+      loadMissingEditors(initContextualLink);
 
-/**
- * Delete models and queue items that are contained within a given context.
- *
- * Deletes any contained EntityModels (plus their associated FieldModels and
- * ContextualLinkView) and FieldModels, as well as the corresponding queues.
- *
- * After EntityModels, FieldModels must also be deleted, because it is possible
- * in Drupal for a field DOM element to exist outside of the entity DOM element,
- * e.g. when viewing the full node, the title of the node is not rendered within
- * the node (the entity) but as the page title.
- *
- * Note: this will not delete an entity that is actively being in-place edited.
- *
- * @param jQuery $context
- *   The context within which to delete.
- */
-function deleteContainedModelsAndQueues($context) {
-  $context.find('[data-edit-entity-id]').addBack('[data-edit-entity-id]').each(function (index, entityElement) {
-    // Delete entity model.
-    var entityModel = Drupal.edit.collections.entities.findWhere({el: entityElement});
-    if (entityModel) {
-      var contextualLinkView = entityModel.get('contextualLinkView');
-      contextualLinkView.undelegateEvents();
-      contextualLinkView.remove();
-      // Remove the EntityDecorationView.
-      entityModel.get('entityDecorationView').remove();
-      // Destroy the EntityModel; this will also destroy its FieldModels.
-      entityModel.destroy();
+      return true;
     }
-
-    // Filter queue.
-    function hasOtherRegion (contextualLink) {
-      return contextualLink.region !== entityElement;
+    // There was not at least one field that the current user may edit in-place,
+    // even though the metadata for all fields within this entity is available.
+    else if (allMetadataExists(fieldIDs)) {
+      return true;
     }
-    contextualLinksQueue = _.filter(contextualLinksQueue, hasOtherRegion);
-  });
 
-  $context.find('[data-edit-field-id]').addBack('[data-edit-field-id]').each(function (index, fieldElement) {
-    // Delete field models.
-    Drupal.edit.collections.fields.chain()
-      .filter(function (fieldModel) { return fieldModel.get('el') === fieldElement; })
-      .invoke('destroy');
+    return false;
+  }
 
-    // Filter queues.
-    function hasOtherFieldElement (field) {
-      return field.el !== fieldElement;
-    }
-    fieldsMetadataQueue = _.filter(fieldsMetadataQueue, hasOtherFieldElement);
-    fieldsAvailableQueue = _.filter(fieldsAvailableQueue, hasOtherFieldElement);
-  });
-}
+  /**
+   * Delete models and queue items that are contained within a given context.
+   *
+   * Deletes any contained EntityModels (plus their associated FieldModels and
+   * ContextualLinkView) and FieldModels, as well as the corresponding queues.
+   *
+   * After EntityModels, FieldModels must also be deleted, because it is possible
+   * in Drupal for a field DOM element to exist outside of the entity DOM element,
+   * e.g. when viewing the full node, the title of the node is not rendered within
+   * the node (the entity) but as the page title.
+   *
+   * Note: this will not delete an entity that is actively being in-place edited.
+   *
+   * @param jQuery $context
+   *   The context within which to delete.
+   */
+  function deleteContainedModelsAndQueues($context) {
+    $context.find('[data-edit-entity-id]').addBack('[data-edit-entity-id]').each(function (index, entityElement) {
+      // Delete entity model.
+      var entityModel = Drupal.edit.collections.entities.findWhere({el: entityElement});
+      if (entityModel) {
+        var contextualLinkView = entityModel.get('contextualLinkView');
+        contextualLinkView.undelegateEvents();
+        contextualLinkView.remove();
+        // Remove the EntityDecorationView.
+        entityModel.get('entityDecorationView').remove();
+        // Destroy the EntityModel; this will also destroy its FieldModels.
+        entityModel.destroy();
+      }
+
+      // Filter queue.
+      function hasOtherRegion(contextualLink) {
+        return contextualLink.region !== entityElement;
+      }
+      contextualLinksQueue = _.filter(contextualLinksQueue, hasOtherRegion);
+    });
+
+    $context.find('[data-edit-field-id]').addBack('[data-edit-field-id]').each(function (index, fieldElement) {
+      // Delete field models.
+      Drupal.edit.collections.fields.chain()
+        .filter(function (fieldModel) { return fieldModel.get('el') === fieldElement; })
+        .invoke('destroy');
+
+      // Filter queues.
+      function hasOtherFieldElement(field) {
+        return field.el !== fieldElement;
+      }
+      fieldsMetadataQueue = _.filter(fieldsMetadataQueue, hasOtherFieldElement);
+      fieldsAvailableQueue = _.filter(fieldsAvailableQueue, hasOtherFieldElement);
+    });
+  }
 
 })(jQuery, _, Backbone, Drupal, drupalSettings, window.JSON, window.sessionStorage);
diff --git a/core/modules/edit/js/editors/formEditor.js b/core/modules/edit/js/editors/formEditor.js
index 9687f58..7a8e5ce 100644
--- a/core/modules/edit/js/editors/formEditor.js
+++ b/core/modules/edit/js/editors/formEditor.js
@@ -5,221 +5,221 @@
 
 (function ($, Drupal) {
 
-"use strict";
-
-Drupal.edit.editors.form = Drupal.edit.EditorView.extend({
-
-  // Tracks the form container DOM element that is used while in-place editing.
-  $formContainer: null,
-
-  // Holds the Drupal.ajax object
-  formSaveAjax: null,
-
-  /**
-   * {@inheritdoc}
-   */
-  stateChange: function (fieldModel, state) {
-    var from = fieldModel.previous('state');
-    var to = state;
-    switch (to) {
-      case 'inactive':
-        break;
-      case 'candidate':
-        if (from !== 'inactive') {
-          this.removeForm();
-        }
-        break;
-      case 'highlighted':
-        break;
-      case 'activating':
-        // If coming from an invalid state, then the form is already loaded.
-        if (from !== 'invalid') {
-          this.loadForm();
-        }
-        break;
-      case 'active':
-        break;
-      case 'changed':
-        break;
-      case 'saving':
-        this.save();
-        break;
-      case 'saved':
-        break;
-      case 'invalid':
-        this.showValidationErrors();
-        break;
-    }
-  },
-
-  /**
-   * {@inheritdoc}
-   */
-  getEditUISettings: function () {
-    return { padding: true, unifiedToolbar: true, fullWidthToolbar: true, popup: true };
-  },
-
-  /**
-   * Loads the form for this field, displays it on top of the actual field.
-   */
-  loadForm: function () {
-    var fieldModel = this.fieldModel;
-
-    // Generate a DOM-compatible ID for the form container DOM element.
-    var id = 'edit-form-for-' + fieldModel.id.replace(/[\/\[\]]/g, '_');
-
-    // Render form container.
-    var $formContainer = this.$formContainer = $(Drupal.theme('editFormContainer', {
-      id: id,
-      loadingMsg: Drupal.t('Loading…')}
-    ));
-    $formContainer
-      .find('.edit-form')
-      .addClass('edit-editable edit-highlighted edit-editing')
-      .attr('role', 'dialog');
-
-    // Insert form container in DOM.
-    if (this.$el.css('display') === 'inline') {
-      $formContainer.prependTo(this.$el.offsetParent());
-      // Position the form container to render on top of the field's element.
-      var pos = this.$el.position();
-      $formContainer.css('left', pos.left).css('top', pos.top);
-    }
-    else {
-      $formContainer.insertBefore(this.$el);
-    }
-
-    // Load form, insert it into the form container and attach event handlers.
-    var formOptions = {
-      fieldID: fieldModel.get('fieldID'),
-      $el: this.$el,
-      nocssjs: false,
-      // Reset an existing entry for this entity in the TempStore (if any) when
-      // loading the field. Logically speaking, this should happen in a separate
-      // request because this is an entity-level operation, not a field-level
-      // operation. But that would require an additional request, that might not
-      // even be necessary: it is only when a user loads a first changed field
-      // for an entity that this needs to happen: precisely now!
-      reset: !fieldModel.get('entity').get('inTempStore')
-    };
-    Drupal.edit.util.form.load(formOptions, function (form, ajax) {
-      Drupal.AjaxCommands.prototype.insert(ajax, {
-        data: form,
-        selector: '#' + id + ' .placeholder'
-      });
-
-      $formContainer
-        .on('formUpdated.edit', ':input', function (event) {
-          var state = fieldModel.get('state');
-          // If the form is in an invalid state, it will persist on the page.
-          // Set the field to activating so that the user can correct the
-          // invalid value.
-          if (state === 'invalid') {
-            fieldModel.set('state', 'activating');
+  "use strict";
+
+  Drupal.edit.editors.form = Drupal.edit.EditorView.extend({
+
+    // Tracks the form container DOM element that is used while in-place editing.
+    $formContainer: null,
+
+    // Holds the Drupal.ajax object
+    formSaveAjax: null,
+
+    /**
+     * {@inheritdoc}
+     */
+    stateChange: function (fieldModel, state) {
+      var from = fieldModel.previous('state');
+      var to = state;
+      switch (to) {
+        case 'inactive':
+          break;
+        case 'candidate':
+          if (from !== 'inactive') {
+            this.removeForm();
           }
-          // Otherwise assume that the fieldModel is in a candidate state and
-          // set it to changed on formUpdate.
-          else {
-            fieldModel.set('state', 'changed');
-          }
-        })
-        .on('keypress.edit', 'input', function (event) {
-          if (event.keyCode === 13) {
-            return false;
+          break;
+        case 'highlighted':
+          break;
+        case 'activating':
+          // If coming from an invalid state, then the form is already loaded.
+          if (from !== 'invalid') {
+            this.loadForm();
           }
+          break;
+        case 'active':
+          break;
+        case 'changed':
+          break;
+        case 'saving':
+          this.save();
+          break;
+        case 'saved':
+          break;
+        case 'invalid':
+          this.showValidationErrors();
+          break;
+      }
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    getEditUISettings: function () {
+      return { padding: true, unifiedToolbar: true, fullWidthToolbar: true, popup: true };
+    },
+
+    /**
+     * Loads the form for this field, displays it on top of the actual field.
+     */
+    loadForm: function () {
+      var fieldModel = this.fieldModel;
+
+      // Generate a DOM-compatible ID for the form container DOM element.
+      var id = 'edit-form-for-' + fieldModel.id.replace(/[\/\[\]]/g, '_');
+
+      // Render form container.
+      var $formContainer = this.$formContainer = $(Drupal.theme('editFormContainer', {
+          id: id,
+          loadingMsg: Drupal.t('Loading…')}
+      ));
+      $formContainer
+        .find('.edit-form')
+        .addClass('edit-editable edit-highlighted edit-editing')
+        .attr('role', 'dialog');
+
+      // Insert form container in DOM.
+      if (this.$el.css('display') === 'inline') {
+        $formContainer.prependTo(this.$el.offsetParent());
+        // Position the form container to render on top of the field's element.
+        var pos = this.$el.position();
+        $formContainer.css('left', pos.left).css('top', pos.top);
+      }
+      else {
+        $formContainer.insertBefore(this.$el);
+      }
+
+      // Load form, insert it into the form container and attach event handlers.
+      var formOptions = {
+        fieldID: fieldModel.get('fieldID'),
+        $el: this.$el,
+        nocssjs: false,
+        // Reset an existing entry for this entity in the TempStore (if any) when
+        // loading the field. Logically speaking, this should happen in a separate
+        // request because this is an entity-level operation, not a field-level
+        // operation. But that would require an additional request, that might not
+        // even be necessary: it is only when a user loads a first changed field
+        // for an entity that this needs to happen: precisely now!
+        reset: !fieldModel.get('entity').get('inTempStore')
+      };
+      Drupal.edit.util.form.load(formOptions, function (form, ajax) {
+        Drupal.AjaxCommands.prototype.insert(ajax, {
+          data: form,
+          selector: '#' + id + ' .placeholder'
         });
 
-      // The in-place editor has loaded; change state to 'active'.
-      fieldModel.set('state', 'active');
-    });
-  },
-
-  /**
-   * Removes the form for this field and detaches behaviors and event handlers.
-   */
-  removeForm: function () {
-    if (this.$formContainer === null) {
-      return;
-    }
-
-    delete this.formSaveAjax;
-    // Allow form widgets to detach properly.
-    Drupal.detachBehaviors(this.$formContainer.get(0), null, 'unload');
-    this.$formContainer
-      .off('change.edit', ':input')
-      .off('keypress.edit', 'input')
-      .remove();
-    this.$formContainer = null;
-  },
-
-  /**
-   * {@inheritdoc}
-   */
-  save: function () {
-    var $formContainer = this.$formContainer;
-    var $submit = $formContainer.find('.edit-form-submit');
-    var editorModel = this.model;
-    var fieldModel = this.fieldModel;
-
-    function cleanUpAjax () {
-      Drupal.edit.util.form.unajaxifySaving(formSaveAjax);
-      formSaveAjax = null;
-    }
-
-    // Create an AJAX object for the form associated with the field.
-    var formSaveAjax = Drupal.edit.util.form.ajaxifySaving({
-      nocssjs: false,
-      other_view_modes: fieldModel.findOtherViewModes()
-    }, $submit);
-
-    // Successfully saved.
-    formSaveAjax.commands.editFieldFormSaved = function (ajax, response, status) {
-      cleanUpAjax();
-      // First, transition the state to 'saved'.
-      fieldModel.set('state', 'saved');
-      // Second, set the 'htmlForOtherViewModes' attribute, so that when this
-      // field is rerendered, the change can be propagated to other instances of
-      // this field, which may be displayed in different view modes.
-      fieldModel.set('htmlForOtherViewModes', response.other_view_modes);
-      // Finally, set the 'html' attribute on the field model. This will cause
-      // the field to be rerendered.
-      _.defer(function () {
-        fieldModel.set('html', response.data);
+        $formContainer
+          .on('formUpdated.edit', ':input', function (event) {
+            var state = fieldModel.get('state');
+            // If the form is in an invalid state, it will persist on the page.
+            // Set the field to activating so that the user can correct the
+            // invalid value.
+            if (state === 'invalid') {
+              fieldModel.set('state', 'activating');
+            }
+            // Otherwise assume that the fieldModel is in a candidate state and
+            // set it to changed on formUpdate.
+            else {
+              fieldModel.set('state', 'changed');
+            }
+          })
+          .on('keypress.edit', 'input', function (event) {
+            if (event.keyCode === 13) {
+              return false;
+            }
+          });
+
+        // The in-place editor has loaded; change state to 'active'.
+        fieldModel.set('state', 'active');
       });
-    };
-
-    // Unsuccessfully saved; validation errors.
-    formSaveAjax.commands.editFieldFormValidationErrors = function (ajax, response, status) {
-      editorModel.set('validationErrors', response.data);
-      fieldModel.set('state', 'invalid');
-    };
-
-    // The edit_field_form AJAX command is called upon attempting to save
-    // the form; Form API will mark which form items have errors, if any. This
-    // command is invoked only if validation errors exist and then it runs
-    // before editFieldFormValidationErrors().
-    formSaveAjax.commands.editFieldForm = function (ajax, response, status) {
-      Drupal.AjaxCommands.prototype.insert(ajax, {
-        data: response.data,
-        selector: '#' + $formContainer.attr('id') + ' form'
-      });
-    };
-
-    // Click the form's submit button; the scoped AJAX commands above will
-    // handle the server's response.
-    $submit.trigger('click.edit');
-  },
-
-  /**
-   * {@inheritdoc}
-   */
-  showValidationErrors: function () {
-    this.$formContainer
-      .find('.edit-form')
-      .addClass('edit-validation-error')
-      .find('form')
-      .prepend(this.model.get('validationErrors'));
-  }
-});
+    },
+
+    /**
+     * Removes the form for this field and detaches behaviors and event handlers.
+     */
+    removeForm: function () {
+      if (this.$formContainer === null) {
+        return;
+      }
+
+      delete this.formSaveAjax;
+      // Allow form widgets to detach properly.
+      Drupal.detachBehaviors(this.$formContainer.get(0), null, 'unload');
+      this.$formContainer
+        .off('change.edit', ':input')
+        .off('keypress.edit', 'input')
+        .remove();
+      this.$formContainer = null;
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    save: function () {
+      var $formContainer = this.$formContainer;
+      var $submit = $formContainer.find('.edit-form-submit');
+      var editorModel = this.model;
+      var fieldModel = this.fieldModel;
+
+      function cleanUpAjax() {
+        Drupal.edit.util.form.unajaxifySaving(formSaveAjax);
+        formSaveAjax = null;
+      }
+
+      // Create an AJAX object for the form associated with the field.
+      var formSaveAjax = Drupal.edit.util.form.ajaxifySaving({
+        nocssjs: false,
+        other_view_modes: fieldModel.findOtherViewModes()
+      }, $submit);
+
+      // Successfully saved.
+      formSaveAjax.commands.editFieldFormSaved = function (ajax, response, status) {
+        cleanUpAjax();
+        // First, transition the state to 'saved'.
+        fieldModel.set('state', 'saved');
+        // Second, set the 'htmlForOtherViewModes' attribute, so that when this
+        // field is rerendered, the change can be propagated to other instances of
+        // this field, which may be displayed in different view modes.
+        fieldModel.set('htmlForOtherViewModes', response.other_view_modes);
+        // Finally, set the 'html' attribute on the field model. This will cause
+        // the field to be rerendered.
+        _.defer(function () {
+          fieldModel.set('html', response.data);
+        });
+      };
+
+      // Unsuccessfully saved; validation errors.
+      formSaveAjax.commands.editFieldFormValidationErrors = function (ajax, response, status) {
+        editorModel.set('validationErrors', response.data);
+        fieldModel.set('state', 'invalid');
+      };
+
+      // The edit_field_form AJAX command is called upon attempting to save
+      // the form; Form API will mark which form items have errors, if any. This
+      // command is invoked only if validation errors exist and then it runs
+      // before editFieldFormValidationErrors().
+      formSaveAjax.commands.editFieldForm = function (ajax, response, status) {
+        Drupal.AjaxCommands.prototype.insert(ajax, {
+          data: response.data,
+          selector: '#' + $formContainer.attr('id') + ' form'
+        });
+      };
+
+      // Click the form's submit button; the scoped AJAX commands above will
+      // handle the server's response.
+      $submit.trigger('click.edit');
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    showValidationErrors: function () {
+      this.$formContainer
+        .find('.edit-form')
+        .addClass('edit-validation-error')
+        .find('form')
+        .prepend(this.model.get('validationErrors'));
+    }
+  });
 
 })(jQuery, Drupal);
diff --git a/core/modules/edit/js/editors/plainTextEditor.js b/core/modules/edit/js/editors/plainTextEditor.js
index 9f1dc47..a3f5f00 100644
--- a/core/modules/edit/js/editors/plainTextEditor.js
+++ b/core/modules/edit/js/editors/plainTextEditor.js
@@ -5,111 +5,111 @@
 
 (function ($, _, Drupal) {
 
-"use strict";
+  "use strict";
 
-Drupal.edit.editors.plain_text = Drupal.edit.EditorView.extend({
+  Drupal.edit.editors.plain_text = Drupal.edit.EditorView.extend({
 
-  // Stores the textual DOM element that is being in-place edited.
-  $textElement: null,
+    // Stores the textual DOM element that is being in-place edited.
+    $textElement: null,
 
-  /**
-   * {@inheritdoc}
-   */
-  initialize: function (options) {
-    Drupal.edit.EditorView.prototype.initialize.call(this, options);
+    /**
+     * {@inheritdoc}
+     */
+    initialize: function (options) {
+      Drupal.edit.EditorView.prototype.initialize.call(this, options);
 
-    var editorModel = this.model;
-    var fieldModel = this.fieldModel;
+      var editorModel = this.model;
+      var fieldModel = this.fieldModel;
 
-    // Store the original value of this field. Necessary for reverting changes.
-    var $textElement;
-    var $fieldItems = this.$el.find('.field-item');
-    if ($fieldItems.length) {
-      $textElement = this.$textElement = $fieldItems.eq(0);
-    }
-    else {
-      $textElement = this.$textElement = this.$el;
-    }
-    editorModel.set('originalValue', $.trim(this.$textElement.text()));
-
-    // Sets the state to 'changed' whenever the value changes
-    var previousText = editorModel.get('originalValue');
-    $textElement.on('keyup paste', function (event) {
-      var currentText = $.trim($textElement.text());
-      if (previousText !== currentText) {
-        previousText = currentText;
-        editorModel.set('currentValue', currentText);
-        fieldModel.set('state', 'changed');
+      // Store the original value of this field. Necessary for reverting changes.
+      var $textElement;
+      var $fieldItems = this.$el.find('.field-item');
+      if ($fieldItems.length) {
+        $textElement = this.$textElement = $fieldItems.eq(0);
       }
-    });
-  },
-
-  /**
-   * {@inheritdoc}
-   */
-  getEditedElement: function () {
-    return this.$textElement;
-  },
+      else {
+        $textElement = this.$textElement = this.$el;
+      }
+      editorModel.set('originalValue', $.trim(this.$textElement.text()));
 
-  /**
-   * {@inheritdoc}
-   */
-  stateChange: function (fieldModel, state, options) {
-    var from = fieldModel.previous('state');
-    var to = state;
-    switch (to) {
-      case 'inactive':
-        break;
-      case 'candidate':
-        if (from !== 'inactive') {
-          this.$textElement.removeAttr('contenteditable');
-        }
-        if (from === 'invalid') {
-          this.removeValidationErrors();
+      // Sets the state to 'changed' whenever the value changes
+      var previousText = editorModel.get('originalValue');
+      $textElement.on('keyup paste', function (event) {
+        var currentText = $.trim($textElement.text());
+        if (previousText !== currentText) {
+          previousText = currentText;
+          editorModel.set('currentValue', currentText);
+          fieldModel.set('state', 'changed');
         }
-        break;
-      case 'highlighted':
-        break;
-      case 'activating':
-        // Defer updating the field model until the current state change has
-        // propagated, to not trigger a nested state change event.
-        _.defer(function () {
-          fieldModel.set('state', 'active');
-        });
-        break;
-      case 'active':
-        this.$textElement.attr('contenteditable', 'true');
-        break;
-      case 'changed':
-        break;
-      case 'saving':
-        if (from === 'invalid') {
-          this.removeValidationErrors();
-        }
-        this.save(options);
-        break;
-      case 'saved':
-        break;
-      case 'invalid':
-        this.showValidationErrors();
-        break;
-    }
-  },
+      });
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    getEditedElement: function () {
+      return this.$textElement;
+    },
 
-  /**
-   * {@inheritdoc}
-   */
-  getEditUISettings: function () {
-    return { padding: true, unifiedToolbar: false, fullWidthToolbar: false, popup: false };
-  },
+    /**
+     * {@inheritdoc}
+     */
+    stateChange: function (fieldModel, state, options) {
+      var from = fieldModel.previous('state');
+      var to = state;
+      switch (to) {
+        case 'inactive':
+          break;
+        case 'candidate':
+          if (from !== 'inactive') {
+            this.$textElement.removeAttr('contenteditable');
+          }
+          if (from === 'invalid') {
+            this.removeValidationErrors();
+          }
+          break;
+        case 'highlighted':
+          break;
+        case 'activating':
+          // Defer updating the field model until the current state change has
+          // propagated, to not trigger a nested state change event.
+          _.defer(function () {
+            fieldModel.set('state', 'active');
+          });
+          break;
+        case 'active':
+          this.$textElement.attr('contenteditable', 'true');
+          break;
+        case 'changed':
+          break;
+        case 'saving':
+          if (from === 'invalid') {
+            this.removeValidationErrors();
+          }
+          this.save(options);
+          break;
+        case 'saved':
+          break;
+        case 'invalid':
+          this.showValidationErrors();
+          break;
+      }
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    getEditUISettings: function () {
+      return { padding: true, unifiedToolbar: false, fullWidthToolbar: false, popup: false };
+    },
 
-  /**
-   * {@inheritdoc}
-   */
-  revert: function () {
-    this.$textElement.html(this.model.get('originalValue'));
-  }
+    /**
+     * {@inheritdoc}
+     */
+    revert: function () {
+      this.$textElement.html(this.model.get('originalValue'));
+    }
 
-});
+  });
 
 })(jQuery, _, Drupal);
diff --git a/core/modules/edit/js/models/AppModel.js b/core/modules/edit/js/models/AppModel.js
index 91af8d0..f1e01c9 100644
--- a/core/modules/edit/js/models/AppModel.js
+++ b/core/modules/edit/js/models/AppModel.js
@@ -7,22 +7,22 @@
 
 (function (Backbone, Drupal) {
 
-"use strict";
+  "use strict";
 
-Drupal.edit.AppModel = Backbone.Model.extend({
+  Drupal.edit.AppModel = Backbone.Model.extend({
 
-  defaults: {
-    // The currently state = 'highlighted' Drupal.edit.FieldModel, if any.
-    // @see Drupal.edit.FieldModel.states
-    highlightedField: null,
-    // The currently state = 'active' Drupal.edit.FieldModel, if any.
-    // @see Drupal.edit.FieldModel.states
-    activeField: null,
-    // Reference to a Drupal.dialog instance if a state change requires
-    // confirmation.
-    activeModal: null
-  }
+    defaults: {
+      // The currently state = 'highlighted' Drupal.edit.FieldModel, if any.
+      // @see Drupal.edit.FieldModel.states
+      highlightedField: null,
+      // The currently state = 'active' Drupal.edit.FieldModel, if any.
+      // @see Drupal.edit.FieldModel.states
+      activeField: null,
+      // Reference to a Drupal.dialog instance if a state change requires
+      // confirmation.
+      activeModal: null
+    }
 
-});
+  });
 
 }(Backbone, Drupal));
diff --git a/core/modules/edit/js/models/BaseModel.js b/core/modules/edit/js/models/BaseModel.js
index d7870b6..1f27484 100644
--- a/core/modules/edit/js/models/BaseModel.js
+++ b/core/modules/edit/js/models/BaseModel.js
@@ -5,37 +5,37 @@
 
 (function (Backbone) {
 
-"use strict";
+  "use strict";
 
-Drupal.edit.BaseModel = Backbone.Model.extend({
+  Drupal.edit.BaseModel = Backbone.Model.extend({
 
-  /**
-   * {@inheritdoc}
-   */
-  initialize: function (options) {
-    this.__initialized = true;
-    return Backbone.Model.prototype.initialize.call(this, options);
-  },
+    /**
+     * {@inheritdoc}
+     */
+    initialize: function (options) {
+      this.__initialized = true;
+      return Backbone.Model.prototype.initialize.call(this, options);
+    },
 
-  /**
-   * {@inheritdoc}
-   */
-  set: function (key, val, options) {
-    if (this.__initialized) {
-      // Deal with both the "key", value and {key:value}-style arguments.
-      if (typeof key === 'object') {
-        key.validate = true;
-      }
-      else {
-        if (!options) {
-          options = {};
+    /**
+     * {@inheritdoc}
+     */
+    set: function (key, val, options) {
+      if (this.__initialized) {
+        // Deal with both the "key", value and {key:value}-style arguments.
+        if (typeof key === 'object') {
+          key.validate = true;
+        }
+        else {
+          if (!options) {
+            options = {};
+          }
+          options.validate = true;
         }
-        options.validate = true;
       }
+      return Backbone.Model.prototype.set.call(this, key, val, options);
     }
-    return Backbone.Model.prototype.set.call(this, key, val, options);
-  }
 
-});
+  });
 
 }(Backbone));
diff --git a/core/modules/edit/js/models/EditorModel.js b/core/modules/edit/js/models/EditorModel.js
index fe18681..8cc4530 100644
--- a/core/modules/edit/js/models/EditorModel.js
+++ b/core/modules/edit/js/models/EditorModel.js
@@ -7,21 +7,21 @@
 
 (function (Backbone, Drupal) {
 
-"use strict";
+  "use strict";
 
-Drupal.edit.EditorModel = Backbone.Model.extend({
+  Drupal.edit.EditorModel = Backbone.Model.extend({
 
-  defaults: {
-    // Not the full HTML representation of this field, but the "actual"
-    // original value of the field, stored by the used in-place editor, and
-    // in a representation that can be chosen by the in-place editor.
-    originalValue: null,
-    // Analogous to originalValue, but the current value.
-    currentValue: null,
-    // Stores any validation errors to be rendered.
-    validationErrors: null
-  }
+    defaults: {
+      // Not the full HTML representation of this field, but the "actual"
+      // original value of the field, stored by the used in-place editor, and
+      // in a representation that can be chosen by the in-place editor.
+      originalValue: null,
+      // Analogous to originalValue, but the current value.
+      currentValue: null,
+      // Stores any validation errors to be rendered.
+      validationErrors: null
+    }
 
-});
+  });
 
 }(Backbone, Drupal));
diff --git a/core/modules/edit/js/models/EntityModel.js b/core/modules/edit/js/models/EntityModel.js
index 2e592e3..593fc78 100644
--- a/core/modules/edit/js/models/EntityModel.js
+++ b/core/modules/edit/js/models/EntityModel.js
@@ -5,608 +5,608 @@
 
 (function (_, $, Backbone, Drupal) {
 
-"use strict";
-
-Drupal.edit.EntityModel = Drupal.edit.BaseModel.extend({
-
-  defaults: {
-    // The DOM element that represents this entity. It may seem bizarre to
-    // have a DOM element in a Backbone Model, but we need to be able to map
-    // entities in the DOM to EntityModels in memory.
-    el: null,
-    // An entity ID, of the form "<entity type>/<entity ID>", e.g. "node/1".
-    entityID: null,
-    // An entity instance ID. The first intance of a specific entity (i.e. with
-    // a given entity ID) is assigned 0, the second 1, and so on.
-    entityInstanceID: null,
-    // The unique ID of this entity instance on the page, of the form "<entity
-    // type>/<entity ID>[entity instance ID]", e.g. "node/1[0]".
-    id: null,
-    // The label of the entity.
-    label: null,
-    // A Drupal.edit.FieldCollection for all fields of this entity.
-    fields: null,
-
-    // The attributes below are stateful. The ones above will never change
-    // during the life of a EntityModel instance.
-
-    // Indicates whether this instance of this entity is currently being
-    // edited in-place.
-    isActive: false,
-    // Whether one or more fields have already been stored in TempStore.
-    inTempStore: false,
-    // Whether one or more fields have already been stored in TempStore *or*
-    // the field that's currently being edited is in the 'changed' or a later
-    // state. In other words, this boolean indicates whether a "Save" button is
-    // necessary or not.
-    isDirty: false,
-    // Whether the request to the server has been made to commit this entity.
-    // Used to prevent multiple such requests.
-    isCommitting: false,
-    // The current processing state of an entity.
-    state: 'closed',
-    // The IDs of the fields whose new values have been stored in TempStore. We
-    // must store this on the EntityModel as well (even though it already is on
-    // the FieldModel) because when a field is rerendered, its FieldModel is
-    // destroyed and this allows us to transition it back to the proper state.
-    fieldsInTempStore: [],
-    // A flag the tells the application that this EntityModel must be reloaded
-    // in order to restore the original values to its fields in the client.
-    reload: false
-  },
-
-  /**
-   * @inheritdoc
-   */
-  initialize: function () {
-    this.set('fields', new Drupal.edit.FieldCollection());
-
-    // Respond to entity state changes.
-    this.listenTo(this, 'change:state', this.stateChange);
-
-    // The state of the entity is largely dependent on the state of its
-    // fields.
-    this.listenTo(this.get('fields'), 'change:state', this.fieldStateChange);
-
-    // Call Drupal.edit.BaseModel's initialize() method.
-    Drupal.edit.BaseModel.prototype.initialize.call(this);
-  },
-
-  /**
-   * Updates FieldModels' states when an EntityModel change occurs.
-   *
-   * @param Drupal.edit.EntityModel entityModel
-   * @param String state
-   *   The state of the associated entity. One of Drupal.edit.EntityModel.states.
-   * @param Object options
-   */
-  stateChange: function (entityModel, state, options) {
-    var to = state;
-    switch (to) {
-      case 'closed':
-        this.set({
-          'isActive': false,
-          'inTempStore': false,
-          'isDirty': false
-        });
-        break;
+  "use strict";
+
+  Drupal.edit.EntityModel = Drupal.edit.BaseModel.extend({
+
+    defaults: {
+      // The DOM element that represents this entity. It may seem bizarre to
+      // have a DOM element in a Backbone Model, but we need to be able to map
+      // entities in the DOM to EntityModels in memory.
+      el: null,
+      // An entity ID, of the form "<entity type>/<entity ID>", e.g. "node/1".
+      entityID: null,
+      // An entity instance ID. The first intance of a specific entity (i.e. with
+      // a given entity ID) is assigned 0, the second 1, and so on.
+      entityInstanceID: null,
+      // The unique ID of this entity instance on the page, of the form "<entity
+      // type>/<entity ID>[entity instance ID]", e.g. "node/1[0]".
+      id: null,
+      // The label of the entity.
+      label: null,
+      // A Drupal.edit.FieldCollection for all fields of this entity.
+      fields: null,
+
+      // The attributes below are stateful. The ones above will never change
+      // during the life of a EntityModel instance.
+
+      // Indicates whether this instance of this entity is currently being
+      // edited in-place.
+      isActive: false,
+      // Whether one or more fields have already been stored in TempStore.
+      inTempStore: false,
+      // Whether one or more fields have already been stored in TempStore *or*
+      // the field that's currently being edited is in the 'changed' or a later
+      // state. In other words, this boolean indicates whether a "Save" button is
+      // necessary or not.
+      isDirty: false,
+      // Whether the request to the server has been made to commit this entity.
+      // Used to prevent multiple such requests.
+      isCommitting: false,
+      // The current processing state of an entity.
+      state: 'closed',
+      // The IDs of the fields whose new values have been stored in TempStore. We
+      // must store this on the EntityModel as well (even though it already is on
+      // the FieldModel) because when a field is rerendered, its FieldModel is
+      // destroyed and this allows us to transition it back to the proper state.
+      fieldsInTempStore: [],
+      // A flag the tells the application that this EntityModel must be reloaded
+      // in order to restore the original values to its fields in the client.
+      reload: false
+    },
+
+    /**
+     * @inheritdoc
+     */
+    initialize: function () {
+      this.set('fields', new Drupal.edit.FieldCollection());
+
+      // Respond to entity state changes.
+      this.listenTo(this, 'change:state', this.stateChange);
+
+      // The state of the entity is largely dependent on the state of its
+      // fields.
+      this.listenTo(this.get('fields'), 'change:state', this.fieldStateChange);
+
+      // Call Drupal.edit.BaseModel's initialize() method.
+      Drupal.edit.BaseModel.prototype.initialize.call(this);
+    },
+
+    /**
+     * Updates FieldModels' states when an EntityModel change occurs.
+     *
+     * @param Drupal.edit.EntityModel entityModel
+     * @param String state
+     *   The state of the associated entity. One of Drupal.edit.EntityModel.states.
+     * @param Object options
+     */
+    stateChange: function (entityModel, state, options) {
+      var to = state;
+      switch (to) {
+        case 'closed':
+          this.set({
+            'isActive': false,
+            'inTempStore': false,
+            'isDirty': false
+          });
+          break;
 
-      case 'launching':
-        break;
+        case 'launching':
+          break;
 
-      case 'opening':
-        // Set the fields to candidate state.
-        entityModel.get('fields').each(function (fieldModel) {
-          fieldModel.set('state', 'candidate', options);
-        });
-        break;
-
-      case 'opened':
-        // The entity is now ready for editing!
-        this.set('isActive', true);
-        break;
-
-      case 'committing':
-        // The user indicated they want to save the entity.
-        var fields = this.get('fields');
-        // For fields that are in an active state, transition them to candidate.
-        fields.chain()
-          .filter(function (fieldModel) {
-            return _.intersection([fieldModel.get('state')], ['active']).length;
-          })
-          .each(function (fieldModel) {
-            fieldModel.set('state', 'candidate');
-          });
-        // For fields that are in a changed state, field values must first be
-        // stored in TempStore.
-        fields.chain()
-          .filter(function (fieldModel) {
-            return _.intersection([fieldModel.get('state')], Drupal.edit.app.changedFieldStates).length;
-          })
-          .each(function (fieldModel) {
-            fieldModel.set('state', 'saving');
+        case 'opening':
+          // Set the fields to candidate state.
+          entityModel.get('fields').each(function (fieldModel) {
+            fieldModel.set('state', 'candidate', options);
           });
-        break;
-
-      case 'deactivating':
-        var changedFields = this.get('fields')
-          .filter(function (fieldModel) {
-            return _.intersection([fieldModel.get('state')], ['changed', 'invalid']).length;
+          break;
+
+        case 'opened':
+          // The entity is now ready for editing!
+          this.set('isActive', true);
+          break;
+
+        case 'committing':
+          // The user indicated they want to save the entity.
+          var fields = this.get('fields');
+          // For fields that are in an active state, transition them to candidate.
+          fields.chain()
+            .filter(function (fieldModel) {
+              return _.intersection([fieldModel.get('state')], ['active']).length;
+            })
+            .each(function (fieldModel) {
+              fieldModel.set('state', 'candidate');
+            });
+          // For fields that are in a changed state, field values must first be
+          // stored in TempStore.
+          fields.chain()
+            .filter(function (fieldModel) {
+              return _.intersection([fieldModel.get('state')], Drupal.edit.app.changedFieldStates).length;
+            })
+            .each(function (fieldModel) {
+              fieldModel.set('state', 'saving');
+            });
+          break;
+
+        case 'deactivating':
+          var changedFields = this.get('fields')
+            .filter(function (fieldModel) {
+              return _.intersection([fieldModel.get('state')], ['changed', 'invalid']).length;
+            });
+          // If the entity contains unconfirmed or unsaved changes, return the
+          // entity to an opened state and ask the user if they would like to save
+          // the changes or discard the changes.
+          //   1. One of the fields is in a changed state. The changed field might
+          //   just be a change in the client or it might have been saved to
+          //   tempstore.
+          //   2. The saved flag is empty and the confirmed flag is empty. If the
+          //   entity has been saved to the server, the fields changed in the
+          //   client are irrelevant. If the changes are confirmed, then proceed
+          //   to set the fields to candidate state.
+          if ((changedFields.length || this.get('fieldsInTempStore').length) && (!options.saved && !options.confirmed)) {
+            // Cancel deactivation until the user confirms save or discard.
+            this.set('state', 'opened', {confirming: true});
+            // An action in reaction to state change must be deferred.
+            _.defer(function () {
+              Drupal.edit.app.confirmEntityDeactivation(entityModel);
+            });
+          }
+          else {
+            var invalidFields = this.get('fields')
+              .filter(function (fieldModel) {
+                return _.intersection([fieldModel.get('state')], ['invalid']).length;
+              });
+            // Indicate if this EntityModel needs to be reloaded in order to
+            // restore the original values of its fields.
+            entityModel.set('reload', (this.get('fieldsInTempStore').length || invalidFields.length));
+            // Set all fields to the 'candidate' state. A changed field may have to
+            // go through confirmation first.
+            entityModel.get('fields').each(function (fieldModel) {
+              // If the field is already in the candidate state, trigger a change
+              // event so that the entityModel can move to the next state in
+              // deactivation.
+              if (_.intersection([fieldModel.get('state')], ['candidate', 'highlighted']).length) {
+                fieldModel.trigger('change:state', fieldModel, fieldModel.get('state'), options);
+              }
+              else {
+                fieldModel.set('state', 'candidate', options);
+              }
+            });
+          }
+          break;
+
+        case 'closing':
+          // Set all fields to the 'inactive' state.
+          options.reason = 'stop';
+          this.get('fields').each(function (fieldModel) {
+            fieldModel.set({
+              'inTempStore': false,
+              'state': 'inactive'
+            }, options);
           });
-        // If the entity contains unconfirmed or unsaved changes, return the
-        // entity to an opened state and ask the user if they would like to save
-        // the changes or discard the changes.
-        //   1. One of the fields is in a changed state. The changed field might
-        //   just be a change in the client or it might have been saved to
-        //   tempstore.
-        //   2. The saved flag is empty and the confirmed flag is empty. If the
-        //   entity has been saved to the server, the fields changed in the
-        //   client are irrelevant. If the changes are confirmed, then proceed
-        //   to set the fields to candidate state.
-        if ((changedFields.length || this.get('fieldsInTempStore').length) && (!options.saved && !options.confirmed)) {
-          // Cancel deactivation until the user confirms save or discard.
-          this.set('state', 'opened', {confirming: true});
-          // An action in reaction to state change must be deferred.
+          break;
+      }
+    },
+
+    /**
+     * Updates a Field and Entity model's "inTempStore" when appropriate.
+     *
+     * Helper function.
+     *
+     * @param Drupal.edit.EntityModel entityModel
+     *   The model of the entity for which a field's state attribute has changed.
+     * @param Drupal.edit.FieldModel fieldModel
+     *   The model of the field whose state attribute has changed.
+     *
+     * @see fieldStateChange()
+     */
+    _updateInTempStoreAttributes: function (entityModel, fieldModel) {
+      var current = fieldModel.get('state');
+      var previous = fieldModel.previous('state');
+      var fieldsInTempStore = entityModel.get('fieldsInTempStore');
+      // If the fieldModel changed to the 'saved' state: remember that this
+      // field was saved to TempStore.
+      if (current === 'saved') {
+        // Mark the entity as saved in TempStore, so that we can pass the
+        // proper "reset TempStore" boolean value when communicating with the
+        // server.
+        entityModel.set('inTempStore', true);
+        // Mark the field as saved in TempStore, so that visual indicators
+        // signifying just that may be rendered.
+        fieldModel.set('inTempStore', true);
+        // Remember that this field is in TempStore, restore when rerendered.
+        fieldsInTempStore.push(fieldModel.get('fieldID'));
+        fieldsInTempStore = _.uniq(fieldsInTempStore);
+        entityModel.set('fieldsInTempStore', fieldsInTempStore);
+      }
+      // If the fieldModel changed to the 'candidate' state from the
+      // 'inactive' state, then this is a field for this entity that got
+      // rerendered. Restore its previous 'inTempStore' attribute value.
+      else if (current === 'candidate' && previous === 'inactive') {
+        fieldModel.set('inTempStore', _.intersection([fieldModel.get('fieldID')], fieldsInTempStore).length > 0);
+      }
+    },
+
+    /**
+     * Reacts to state changes in this entity's fields.
+     *
+     * @param Drupal.edit.FieldModel fieldModel
+     *   The model of the field whose state attribute changed.
+     * @param String state
+     *   The state of the associated field. One of Drupal.edit.FieldModel.states.
+     */
+    fieldStateChange: function (fieldModel, state) {
+      var entityModel = this;
+      var fieldState = state;
+      // Switch on the entityModel state.
+      // The EntityModel responds to FieldModel state changes as a function of its
+      // state. For example, a field switching back to 'candidate' state when its
+      // entity is in the 'opened' state has no effect on the entity. But that
+      // same switch back to 'candidate' state of a field when the entity is in
+      // the 'committing' state might allow the entity to proceed with the commit
+      // flow.
+      switch (this.get('state')) {
+        case 'closed':
+        case 'launching':
+          // It should be impossible to reach these: fields can't change state
+          // while the entity is closed or still launching.
+          break;
+
+        case 'opening':
+          // We must change the entity to the 'opened' state, but it must first be
+          // confirmed that all of its fieldModels have transitioned to the
+          // 'candidate' state.
+          // We do this here, because this is called every time a fieldModel
+          // changes state, hence each time this is called, we get closer to the
+          // goal of having all fieldModels in the 'candidate' state.
+          // A state change in reaction to another state change must be deferred.
           _.defer(function () {
-            Drupal.edit.app.confirmEntityDeactivation(entityModel);
-          });
-        }
-        else {
-          var invalidFields = this.get('fields')
-          .filter(function (fieldModel) {
-            return _.intersection([fieldModel.get('state')], ['invalid']).length;
+            entityModel.set('state', 'opened', {
+              'accept-field-states': Drupal.edit.app.readyFieldStates
+            });
           });
-          // Indicate if this EntityModel needs to be reloaded in order to
-          // restore the original values of its fields.
-          entityModel.set('reload', (this.get('fieldsInTempStore').length || invalidFields.length));
-          // Set all fields to the 'candidate' state. A changed field may have to
-          // go through confirmation first.
-          entityModel.get('fields').each(function (fieldModel) {
-            // If the field is already in the candidate state, trigger a change
-            // event so that the entityModel can move to the next state in
-            // deactivation.
-            if (_.intersection([fieldModel.get('state')], ['candidate', 'highlighted']).length) {
-              fieldModel.trigger('change:state', fieldModel, fieldModel.get('state'), options);
-            }
-            else {
-              fieldModel.set('state', 'candidate', options);
-            }
-          });
-        }
-        break;
-
-      case 'closing':
-        // Set all fields to the 'inactive' state.
-        options.reason = 'stop';
-        this.get('fields').each(function (fieldModel) {
-          fieldModel.set({
-            'inTempStore': false,
-            'state': 'inactive'
-          }, options);
-        });
-        break;
-    }
-  },
-
-  /**
-   * Updates a Field and Entity model's "inTempStore" when appropriate.
-   *
-   * Helper function.
-   *
-   * @param Drupal.edit.EntityModel entityModel
-   *   The model of the entity for which a field's state attribute has changed.
-   * @param Drupal.edit.FieldModel fieldModel
-   *   The model of the field whose state attribute has changed.
-   *
-   * @see fieldStateChange()
-   */
-  _updateInTempStoreAttributes: function (entityModel, fieldModel) {
-    var current = fieldModel.get('state');
-    var previous = fieldModel.previous('state');
-    var fieldsInTempStore = entityModel.get('fieldsInTempStore');
-    // If the fieldModel changed to the 'saved' state: remember that this
-    // field was saved to TempStore.
-    if (current === 'saved') {
-      // Mark the entity as saved in TempStore, so that we can pass the
-      // proper "reset TempStore" boolean value when communicating with the
-      // server.
-      entityModel.set('inTempStore', true);
-      // Mark the field as saved in TempStore, so that visual indicators
-      // signifying just that may be rendered.
-      fieldModel.set('inTempStore', true);
-      // Remember that this field is in TempStore, restore when rerendered.
-      fieldsInTempStore.push(fieldModel.get('fieldID'));
-      fieldsInTempStore = _.uniq(fieldsInTempStore);
-      entityModel.set('fieldsInTempStore', fieldsInTempStore);
-    }
-    // If the fieldModel changed to the 'candidate' state from the
-    // 'inactive' state, then this is a field for this entity that got
-    // rerendered. Restore its previous 'inTempStore' attribute value.
-    else if (current === 'candidate' && previous === 'inactive') {
-      fieldModel.set('inTempStore', _.intersection([fieldModel.get('fieldID')], fieldsInTempStore).length > 0);
-    }
-  },
-
-  /**
-   * Reacts to state changes in this entity's fields.
-   *
-   * @param Drupal.edit.FieldModel fieldModel
-   *   The model of the field whose state attribute changed.
-   * @param String state
-   *   The state of the associated field. One of Drupal.edit.FieldModel.states.
-   */
-  fieldStateChange: function (fieldModel, state) {
-    var entityModel = this;
-    var fieldState = state;
-    // Switch on the entityModel state.
-    // The EntityModel responds to FieldModel state changes as a function of its
-    // state. For example, a field switching back to 'candidate' state when its
-    // entity is in the 'opened' state has no effect on the entity. But that
-    // same switch back to 'candidate' state of a field when the entity is in
-    // the 'committing' state might allow the entity to proceed with the commit
-    // flow.
-    switch (this.get('state')) {
-      case 'closed':
-      case 'launching':
-        // It should be impossible to reach these: fields can't change state
-        // while the entity is closed or still launching.
-        break;
-
-      case 'opening':
-        // We must change the entity to the 'opened' state, but it must first be
-        // confirmed that all of its fieldModels have transitioned to the
-        // 'candidate' state.
-        // We do this here, because this is called every time a fieldModel
-        // changes state, hence each time this is called, we get closer to the
-        // goal of having all fieldModels in the 'candidate' state.
-        // A state change in reaction to another state change must be deferred.
-        _.defer(function () {
-          entityModel.set('state', 'opened', {
+          break;
+
+        case 'opened':
+          // Set the isDirty attribute when appropriate so that it is known when
+          // to display the "Save" button in the entity toolbar.
+          // Note that once a field has been changed, there's no way to discard
+          // that change, hence it will have to be saved into TempStore, or the
+          // in-place editing of this field will have to be stopped completely.
+          // In other words: once any field enters the 'changed' field, then for
+          // the remainder of the in-place editing session, the entity is by
+          // definition dirty.
+          if (fieldState === 'changed') {
+            entityModel.set('isDirty', true);
+          }
+          else {
+            this._updateInTempStoreAttributes(entityModel, fieldModel);
+          }
+          break;
+
+        case 'committing':
+          // If the field save returned a validation error, set the state of the
+          // entity back to 'opened'.
+          if (fieldState === 'invalid') {
+            // A state change in reaction to another state change must be deferred.
+            _.defer(function () {
+              entityModel.set('state', 'opened', { reason: 'invalid' });
+            });
+          }
+          else {
+            this._updateInTempStoreAttributes(entityModel, fieldModel);
+          }
+
+          // Attempt to save the entity. If the entity's fields are not yet all in
+          // a ready state, the save will not be processed.
+          var options = {
             'accept-field-states': Drupal.edit.app.readyFieldStates
+          };
+          if (entityModel.set('isCommitting', true, options)) {
+            entityModel.save({
+              success: function () {
+                entityModel.set({
+                  'state': 'deactivating',
+                  'isCommitting': false
+                }, {'saved': true});
+              },
+              error: function () {
+                // Reset the "isCommitting" mutex.
+                entityModel.set('isCommitting', false);
+                // Change the state back to "opened", to allow the user to hit the
+                // "Save" button again.
+                entityModel.set('state', 'opened', { reason: 'networkerror' });
+                // Show a modal to inform the user of the network error.
+                var message = Drupal.t('Your changes to <q>@entity-title</q> could not be saved, either due to a website problem or a network connection problem.<br>Please try again.', { '@entity-title': entityModel.get('label') });
+                Drupal.edit.util.networkErrorModal(Drupal.t('Sorry!'), message);
+              }
+            });
+          }
+          break;
+
+        case 'deactivating':
+          // When setting the entity to 'closing', require that all fieldModels
+          // are in either the 'candidate' or 'highlighted' state.
+          // A state change in reaction to another state change must be deferred.
+          _.defer(function () {
+            entityModel.set('state', 'closing', {
+              'accept-field-states': Drupal.edit.app.readyFieldStates
+            });
           });
-        });
-        break;
-
-      case 'opened':
-        // Set the isDirty attribute when appropriate so that it is known when
-        // to display the "Save" button in the entity toolbar.
-        // Note that once a field has been changed, there's no way to discard
-        // that change, hence it will have to be saved into TempStore, or the
-        // in-place editing of this field will have to be stopped completely.
-        // In other words: once any field enters the 'changed' field, then for
-        // the remainder of the in-place editing session, the entity is by
-        // definition dirty.
-        if (fieldState === 'changed') {
-          entityModel.set('isDirty', true);
-        }
-        else {
-          this._updateInTempStoreAttributes(entityModel, fieldModel);
-        }
-        break;
+          break;
 
-      case 'committing':
-        // If the field save returned a validation error, set the state of the
-        // entity back to 'opened'.
-        if (fieldState === 'invalid') {
+        case 'closing':
+          // When setting the entity to 'closed', require that all fieldModels are
+          // in the 'inactive' state.
           // A state change in reaction to another state change must be deferred.
-          _.defer(function() {
-            entityModel.set('state', 'opened', { reason: 'invalid' });
+          _.defer(function () {
+            entityModel.set('state', 'closed', {
+              'accept-field-states': ['inactive']
+            });
           });
+          break;
+      }
+    },
+
+    /**
+     * Fires an AJAX request to the REST save URL for an entity.
+     *
+     * @param options
+     *   An object of options that contains:
+     *     - success: (optional) A function to invoke if the entity is success-
+     *     fully saved.
+     */
+    save: function (options) {
+      var entityModel = this;
+
+      // @todo Simplify this once https://drupal.org/node/1533366 lands.
+      // @see https://drupal.org/node/2029999.
+      var id = 'edit-save-entity';
+      // Create a temporary element to be able to use Drupal.ajax.
+      var $el = $('#edit-entity-toolbar').find('.action-save'); // This is the span element inside the button.
+      // Create a Drupal.ajax instance to save the entity.
+      var entitySaverAjax = new Drupal.ajax(id, $el, {
+        url: Drupal.url('edit/entity/' + entityModel.get('entityID')),
+        event: 'edit-save.edit',
+        progress: { type: 'none' },
+        error: function () {
+          $el.off('edit-save.edit');
+          // Let the Drupal.edit.EntityModel Backbone model's error() method
+          // handle errors.
+          options.error.call(entityModel);
+        }
+      });
+      // Entity saved successfully.
+      entitySaverAjax.commands.editEntitySaved = function (ajax, response, status) {
+        // Clean up.
+        $(ajax.element).off('edit-save.edit');
+        // All fields have been moved from TempStore to permanent storage, update
+        // the "inTempStore" attribute on FieldModels, on the EntityModel and
+        // clear EntityModel's "fieldInTempStore" attribute.
+        entityModel.get('fields').each(function (fieldModel) {
+          fieldModel.set('inTempStore', false);
+        });
+        entityModel.set('inTempStore', false);
+        entityModel.set('fieldsInTempStore', []);
+
+        // Invoke the optional success callback.
+        if (options.success) {
+          options.success.call(entityModel);
         }
-        else {
-          this._updateInTempStoreAttributes(entityModel, fieldModel);
+      };
+      // Trigger the AJAX request, which will will return the editEntitySaved AJAX
+      // command to which we then react.
+      $el.trigger('edit-save.edit');
+    },
+
+    /**
+     * {@inheritdoc}
+     *
+     * @param Object attrs
+     *   The attributes changes in the save or set call.
+     * @param Object options
+     *   An object with the following option:
+     *     - String reason (optional): a string that conveys a particular reason
+     *       to allow for an exceptional state change.
+     *     - Array accept-field-states (optional) An array of strings that
+     *     represent field states that the entities must be in to validate. For
+     *     example, if accept-field-states is ['candidate', 'highlighted'], then
+     *     all the fields of the entity must be in either of these two states
+     *     for the save or set call to validate and proceed.
+     */
+    validate: function (attrs, options) {
+      var acceptedFieldStates = options['accept-field-states'] || [];
+
+      // Validate state change.
+      var currentState = this.get('state');
+      var nextState = attrs.state;
+      if (currentState !== nextState) {
+        // Ensure it's a valid state.
+        if (_.indexOf(this.constructor.states, nextState) === -1) {
+          return '"' + nextState + '" is an invalid state';
         }
 
-        // Attempt to save the entity. If the entity's fields are not yet all in
-        // a ready state, the save will not be processed.
-        var options = {
-          'accept-field-states': Drupal.edit.app.readyFieldStates
-        };
-        if (entityModel.set('isCommitting', true, options)) {
-          entityModel.save({
-            success: function () {
-              entityModel.set({
-                'state': 'deactivating',
-                'isCommitting' : false
-              }, {'saved': true});
-            },
-            error: function () {
-              // Reset the "isCommitting" mutex.
-              entityModel.set('isCommitting', false);
-              // Change the state back to "opened", to allow the user to hit the
-              // "Save" button again.
-              entityModel.set('state', 'opened', { reason: 'networkerror' });
-              // Show a modal to inform the user of the network error.
-              var message = Drupal.t('Your changes to <q>@entity-title</q> could not be saved, either due to a website problem or a network connection problem.<br>Please try again.', { '@entity-title' : entityModel.get('label') });
-              Drupal.edit.util.networkErrorModal(Drupal.t('Sorry!'), message);
-            }
-          });
+        // Ensure it's a state change that is allowed.
+        // Check if the acceptStateChange function accepts it.
+        if (!this._acceptStateChange(currentState, nextState, options)) {
+          return 'state change not accepted';
+        }
+        // If that function accepts it, then ensure all fields are also in an
+        // acceptable state.
+        else if (!this._fieldsHaveAcceptableStates(acceptedFieldStates)) {
+          return 'state change not accepted because fields are not in acceptable state';
         }
-        break;
-
-      case 'deactivating':
-        // When setting the entity to 'closing', require that all fieldModels
-        // are in either the 'candidate' or 'highlighted' state.
-        // A state change in reaction to another state change must be deferred.
-        _.defer(function() {
-          entityModel.set('state', 'closing', {
-            'accept-field-states': Drupal.edit.app.readyFieldStates
-          });
-        });
-        break;
-
-      case 'closing':
-        // When setting the entity to 'closed', require that all fieldModels are
-        // in the 'inactive' state.
-        // A state change in reaction to another state change must be deferred.
-        _.defer(function() {
-          entityModel.set('state', 'closed', {
-            'accept-field-states': ['inactive']
-          });
-        });
-        break;
-    }
-  },
-
-  /**
-   * Fires an AJAX request to the REST save URL for an entity.
-   *
-   * @param options
-   *   An object of options that contains:
-   *     - success: (optional) A function to invoke if the entity is success-
-   *     fully saved.
-   */
-  save: function (options) {
-    var entityModel = this;
-
-    // @todo Simplify this once https://drupal.org/node/1533366 lands.
-    // @see https://drupal.org/node/2029999.
-    var id = 'edit-save-entity';
-    // Create a temporary element to be able to use Drupal.ajax.
-    var $el = $('#edit-entity-toolbar').find('.action-save'); // This is the span element inside the button.
-    // Create a Drupal.ajax instance to save the entity.
-    var entitySaverAjax = new Drupal.ajax(id, $el, {
-      url: Drupal.url('edit/entity/' + entityModel.get('entityID')),
-      event: 'edit-save.edit',
-      progress: { type: 'none' },
-      error: function () {
-        $el.off('edit-save.edit');
-        // Let the Drupal.edit.EntityModel Backbone model's error() method
-        // handle errors.
-        options.error.call(entityModel);
       }
-    });
-    // Entity saved successfully.
-    entitySaverAjax.commands.editEntitySaved = function(ajax, response, status) {
-      // Clean up.
-      $(ajax.element).off('edit-save.edit');
-      // All fields have been moved from TempStore to permanent storage, update
-      // the "inTempStore" attribute on FieldModels, on the EntityModel and
-      // clear EntityModel's "fieldInTempStore" attribute.
-      entityModel.get('fields').each(function (fieldModel) {
-        fieldModel.set('inTempStore', false);
-      });
-      entityModel.set('inTempStore', false);
-      entityModel.set('fieldsInTempStore', []);
 
-      // Invoke the optional success callback.
-      if (options.success) {
-        options.success.call(entityModel);
+      // Validate setting isCommitting = true.
+      var currentIsCommitting = this.get('isCommitting');
+      var nextIsCommitting = attrs.isCommitting;
+      if (currentIsCommitting === false && nextIsCommitting === true) {
+        if (!this._fieldsHaveAcceptableStates(acceptedFieldStates)) {
+          return 'isCommitting change not accepted because fields are not in acceptable state';
+        }
       }
-    };
-    // Trigger the AJAX request, which will will return the editEntitySaved AJAX
-    // command to which we then react.
-    $el.trigger('edit-save.edit');
-  },
-
-  /**
-   * {@inheritdoc}
-   *
-   * @param Object attrs
-   *   The attributes changes in the save or set call.
-   * @param Object options
-   *   An object with the following option:
-   *     - String reason (optional): a string that conveys a particular reason
-   *       to allow for an exceptional state change.
-   *     - Array accept-field-states (optional) An array of strings that
-   *     represent field states that the entities must be in to validate. For
-   *     example, if accept-field-states is ['candidate', 'highlighted'], then
-   *     all the fields of the entity must be in either of these two states
-   *     for the save or set call to validate and proceed.
-   */
-  validate: function (attrs, options) {
-    var acceptedFieldStates = options['accept-field-states'] || [];
-
-    // Validate state change.
-    var currentState = this.get('state');
-    var nextState = attrs.state;
-    if (currentState !== nextState) {
-      // Ensure it's a valid state.
-      if (_.indexOf(this.constructor.states, nextState) === -1) {
-        return '"' + nextState + '" is an invalid state';
+      else if (currentIsCommitting === true && nextIsCommitting === true) {
+        return "isCommiting is a mutex, hence only changes are allowed";
       }
+    },
 
-      // Ensure it's a state change that is allowed.
-      // Check if the acceptStateChange function accepts it.
-      if (!this._acceptStateChange(currentState, nextState, options)) {
-        return 'state change not accepted';
-      }
-      // If that function accepts it, then ensure all fields are also in an
-      // acceptable state.
-      else if (!this._fieldsHaveAcceptableStates(acceptedFieldStates)) {
-        return 'state change not accepted because fields are not in acceptable state';
-      }
-    }
+    // Like @see AppView.acceptEditorStateChange()
+    _acceptStateChange: function (from, to, context) {
+      var accept = true;
 
-    // Validate setting isCommitting = true.
-    var currentIsCommitting = this.get('isCommitting');
-    var nextIsCommitting = attrs.isCommitting;
-    if (currentIsCommitting === false && nextIsCommitting === true) {
-      if (!this._fieldsHaveAcceptableStates(acceptedFieldStates)) {
-        return 'isCommitting change not accepted because fields are not in acceptable state';
-      }
-    }
-    else if (currentIsCommitting === true && nextIsCommitting === true) {
-      return "isCommiting is a mutex, hence only changes are allowed";
-    }
-  },
-
-  // Like @see AppView.acceptEditorStateChange()
-  _acceptStateChange: function (from, to, context) {
-    var accept = true;
-
-    // In general, enforce the states sequence. Disallow going back from a
-    // "later" state to an "earlier" state, except in explicitly allowed
-    // cases.
-    if (!this.constructor.followsStateSequence(from, to)) {
-      accept = false;
-
-      // Allow: closing -> closed.
-      // Necessary to stop editing an entity.
-      if (from === 'closing' && to === 'closed') {
-        accept = true;
-      }
-      // Allow: committing -> opened.
-      // Necessary to be able to correct an invalid field, or to hit the "Save"
-      // button again after a server/network error.
-      else if (from === 'committing' && to === 'opened' && context.reason && (context.reason === 'invalid' || context.reason === 'networkerror')) {
-        accept = true;
-      }
-      // Allow: deactivating -> opened.
-      // Necessary to be able to confirm changes with the user.
-      else if (from === 'deactivating' && to === 'opened' && context.confirming) {
-        accept = true;
+      // In general, enforce the states sequence. Disallow going back from a
+      // "later" state to an "earlier" state, except in explicitly allowed
+      // cases.
+      if (!this.constructor.followsStateSequence(from, to)) {
+        accept = false;
+
+        // Allow: closing -> closed.
+        // Necessary to stop editing an entity.
+        if (from === 'closing' && to === 'closed') {
+          accept = true;
+        }
+        // Allow: committing -> opened.
+        // Necessary to be able to correct an invalid field, or to hit the "Save"
+        // button again after a server/network error.
+        else if (from === 'committing' && to === 'opened' && context.reason && (context.reason === 'invalid' || context.reason === 'networkerror')) {
+          accept = true;
+        }
+        // Allow: deactivating -> opened.
+        // Necessary to be able to confirm changes with the user.
+        else if (from === 'deactivating' && to === 'opened' && context.confirming) {
+          accept = true;
+        }
+        // Allow: opened -> deactivating.
+        // Necessary to be able to stop editing.
+        else if (from === 'opened' && to === 'deactivating' && context.confirmed) {
+          accept = true;
+        }
       }
-      // Allow: opened -> deactivating.
-      // Necessary to be able to stop editing.
-      else if (from === 'opened' && to === 'deactivating' && context.confirmed) {
-        accept = true;
+
+      return accept;
+    },
+
+    /**
+     * @param Array acceptedFieldStates
+     *   @see validate()
+     * @return Boolean
+     */
+    _fieldsHaveAcceptableStates: function (acceptedFieldStates) {
+      var accept = true;
+
+      // If no acceptable field states are provided, assume all field states are
+      // acceptable. We want to let validation pass as a default and only
+      // check validity on calls to set that explicitly request it.
+      if (acceptedFieldStates.length > 0) {
+        var fieldStates = this.get('fields').pluck('state') || [];
+        // If not all fields are in one of the accepted field states, then we
+        // still can't allow this state change.
+        if (_.difference(fieldStates, acceptedFieldStates).length) {
+          accept = false;
+        }
       }
+
+      return accept;
+    },
+
+    /**
+     * @inheritdoc
+     */
+    destroy: function (options) {
+      Drupal.edit.BaseModel.prototype.destroy.call(this, options);
+
+      this.stopListening();
+
+      // Destroy all fields of this entity.
+      this.get('fields').each(function (fieldModel) {
+        fieldModel.destroy();
+      });
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    sync: function () {
+      // We don't use REST updates to sync.
+      return;
     }
 
-    return accept;
-  },
-
-  /**
-   * @param Array acceptedFieldStates
-   *   @see validate()
-   * @return Boolean
-   */
-  _fieldsHaveAcceptableStates: function (acceptedFieldStates) {
-    var accept = true;
-
-    // If no acceptable field states are provided, assume all field states are
-    // acceptable. We want to let validation pass as a default and only
-    // check validity on calls to set that explicitly request it.
-    if (acceptedFieldStates.length > 0) {
-      var fieldStates = this.get('fields').pluck('state') || [];
-      // If not all fields are in one of the accepted field states, then we
-      // still can't allow this state change.
-      if (_.difference(fieldStates, acceptedFieldStates).length) {
-        accept = false;
-      }
+  }, {
+
+    /**
+     * A list (sequence) of all possible states an entity can be in during
+     * in-place editing.
+     */
+    states: [
+      // Initial state, like field's 'inactive' OR the user has just finished
+      // in-place editing this entity.
+      // - Trigger: none (initial) or EntityModel (finished).
+      // - Expected behavior: (when not initial state): tear down
+      //   EntityToolbarView, in-place editors and related views.
+      'closed',
+      // User has activated in-place editing of this entity.
+      // - Trigger: user.
+      // - Expected behavior: the EntityToolbarView is gets set up, in-place
+      //   editors (EditorViews) and related views for this entity's fields are
+      //   set up. Upon completion of those, the state is changed to 'opening'.
+      'launching',
+      // Launching has finished.
+      // - Trigger: application.
+      // - Guarantees: in-place editors ready for use, all entity and field views
+      //   have been set up, all fields are in the 'inactive' state.
+      // - Expected behavior: all fields are changed to the 'candidate' state and
+      //   once this is completed, the entity state will be changed to 'opened'.
+      'opening',
+      // Opening has finished.
+      // - Trigger: EntityModel.
+      // - Guarantees: see 'opening', all fields are in the 'candidate' state.
+      // - Expected behavior: the user is able to actually use in-place editing.
+      'opened',
+      // User has clicked the 'Save' button (and has thus changed at least one
+      // field).
+      // - Trigger: user.
+      // - Guarantees: see 'opened', plus: either a changed field is in TempStore,
+      //   or the user has just modified a field without activating (switching to)
+      //   another field.
+      // - Expected behavior: 1) if any of the fields are not yet in TempStore,
+      //   save them to TempStore, 2) if then any of the fields has the 'invalid'
+      //   state, then change the entity state back to 'opened', otherwise: save
+      //   the entity by committing it from TempStore into permanent storage.
+      'committing',
+      // User has clicked the 'Close' button, or has clicked the 'Save' button and
+      // that was successfully completed.
+      // - Trigger: user or EntityModel.
+      // - Guarantees: when having clicked 'Close' hardly any: fields may be in a
+      //   variety of states; when having clicked 'Save': all fields are in the
+      //   'candidate' state.
+      // - Expected behavior: transition all fields to the 'candidate' state,
+      //   possibly requiring confirmation in the case of having clicked 'Close'.
+      'deactivating',
+      // Deactivation has been completed.
+      // - Trigger: EntityModel.
+      // - Guarantees: all fields are in the 'candidate' state.
+      // - Expected behavior: change all fields to the 'inactive' state.
+      'closing'
+    ],
+
+    /**
+     * Indicates whether the 'from' state comes before the 'to' state.
+     *
+     * @param String from
+     *   One of Drupal.edit.EntityModel.states.
+     * @param String to
+     *   One of Drupal.edit.EntityModel.states.
+     * @return Boolean
+     */
+    followsStateSequence: function (from, to) {
+      return _.indexOf(this.states, from) < _.indexOf(this.states, to);
     }
 
-    return accept;
-  },
-
-  /**
-   * @inheritdoc
-   */
-  destroy: function (options) {
-    Drupal.edit.BaseModel.prototype.destroy.call(this, options);
-
-    this.stopListening();
-
-    // Destroy all fields of this entity.
-    this.get('fields').each(function (fieldModel) {
-      fieldModel.destroy();
-    });
-  },
-
-  /**
-   * {@inheritdoc}
-   */
-  sync: function () {
-    // We don't use REST updates to sync.
-    return;
-  }
-
-}, {
-
-  /**
-   * A list (sequence) of all possible states an entity can be in during
-   * in-place editing.
-   */
-  states: [
-    // Initial state, like field's 'inactive' OR the user has just finished
-    // in-place editing this entity.
-    // - Trigger: none (initial) or EntityModel (finished).
-    // - Expected behavior: (when not initial state): tear down
-    //   EntityToolbarView, in-place editors and related views.
-    'closed',
-    // User has activated in-place editing of this entity.
-    // - Trigger: user.
-    // - Expected behavior: the EntityToolbarView is gets set up, in-place
-    //   editors (EditorViews) and related views for this entity's fields are
-    //   set up. Upon completion of those, the state is changed to 'opening'.
-    'launching',
-    // Launching has finished.
-    // - Trigger: application.
-    // - Guarantees: in-place editors ready for use, all entity and field views
-    //   have been set up, all fields are in the 'inactive' state.
-    // - Expected behavior: all fields are changed to the 'candidate' state and
-    //   once this is completed, the entity state will be changed to 'opened'.
-    'opening',
-    // Opening has finished.
-    // - Trigger: EntityModel.
-    // - Guarantees: see 'opening', all fields are in the 'candidate' state.
-    // - Expected behavior: the user is able to actually use in-place editing.
-    'opened',
-    // User has clicked the 'Save' button (and has thus changed at least one
-    // field).
-    // - Trigger: user.
-    // - Guarantees: see 'opened', plus: either a changed field is in TempStore,
-    //   or the user has just modified a field without activating (switching to)
-    //   another field.
-    // - Expected behavior: 1) if any of the fields are not yet in TempStore,
-    //   save them to TempStore, 2) if then any of the fields has the 'invalid'
-    //   state, then change the entity state back to 'opened', otherwise: save
-    //   the entity by committing it from TempStore into permanent storage.
-    'committing',
-    // User has clicked the 'Close' button, or has clicked the 'Save' button and
-    // that was successfully completed.
-    // - Trigger: user or EntityModel.
-    // - Guarantees: when having clicked 'Close' hardly any: fields may be in a
-    //   variety of states; when having clicked 'Save': all fields are in the
-    //   'candidate' state.
-    // - Expected behavior: transition all fields to the 'candidate' state,
-    //   possibly requiring confirmation in the case of having clicked 'Close'.
-    'deactivating',
-    // Deactivation has been completed.
-    // - Trigger: EntityModel.
-    // - Guarantees: all fields are in the 'candidate' state.
-    // - Expected behavior: change all fields to the 'inactive' state.
-    'closing'
-  ],
-
-  /**
-   * Indicates whether the 'from' state comes before the 'to' state.
-   *
-   * @param String from
-   *   One of Drupal.edit.EntityModel.states.
-   * @param String to
-   *   One of Drupal.edit.EntityModel.states.
-   * @return Boolean
-   */
-  followsStateSequence: function (from, to) {
-    return _.indexOf(this.states, from) < _.indexOf(this.states, to);
-  }
-
-});
-
-Drupal.edit.EntityCollection = Backbone.Collection.extend({
-  model: Drupal.edit.EntityModel
-});
+  });
+
+  Drupal.edit.EntityCollection = Backbone.Collection.extend({
+    model: Drupal.edit.EntityModel
+  });
 
 }(_, jQuery, Backbone, Drupal));
diff --git a/core/modules/edit/js/models/FieldModel.js b/core/modules/edit/js/models/FieldModel.js
index ea537a0..0b91b0f 100644
--- a/core/modules/edit/js/models/FieldModel.js
+++ b/core/modules/edit/js/models/FieldModel.js
@@ -5,256 +5,256 @@
 
 (function (_, Backbone, Drupal) {
 
-"use strict";
+  "use strict";
 
-/**
- * State of an in-place editable field in the DOM.
- */
-Drupal.edit.FieldModel = Drupal.edit.BaseModel.extend({
+  /**
+   * State of an in-place editable field in the DOM.
+   */
+  Drupal.edit.FieldModel = Drupal.edit.BaseModel.extend({
 
-  defaults: {
-    // The DOM element that represents this field. It may seem bizarre to have
-    // a DOM element in a Backbone Model, but we need to be able to map fields
-    // in the DOM to FieldModels in memory.
-    el: null,
-    // A field ID, of the form
-    // "<entity type>/<id>/<field name>/<language>/<view mode>", e.g.
-    // "node/1/field_tags/und/full".
-    fieldID: null,
-    // The unique ID of this field within its entity instance on the page, of
-    // the form "<entity type>/<id>/<field name>/<language>/<view mode>[entity instance ID]",
-    // e.g. "node/1/field_tags/und/full[0]".
-    id: null,
-    // A Drupal.edit.EntityModel. Its "fields" attribute, which is a
-    // FieldCollection, is automatically updated to include this FieldModel.
-    entity: null,
-    // This field's metadata as returned by the EditController::metadata().
-    metadata: null,
-    // Callback function for validating changes between states. Receives the
-    // previous state, new state, context, and a callback
-    acceptStateChange: null,
-    // A logical field ID, of the form
-    // "<entity type>/<id>/<field name>/<language>", i.e. the fieldID without
-    // the view mode, to be able to identify other instances of the same field
-    // on the page but rendered in a different view mode. e.g. "node/1/field_tags/und".
-    logicalFieldID: null,
+    defaults: {
+      // The DOM element that represents this field. It may seem bizarre to have
+      // a DOM element in a Backbone Model, but we need to be able to map fields
+      // in the DOM to FieldModels in memory.
+      el: null,
+      // A field ID, of the form
+      // "<entity type>/<id>/<field name>/<language>/<view mode>", e.g.
+      // "node/1/field_tags/und/full".
+      fieldID: null,
+      // The unique ID of this field within its entity instance on the page, of
+      // the form "<entity type>/<id>/<field name>/<language>/<view mode>[entity instance ID]",
+      // e.g. "node/1/field_tags/und/full[0]".
+      id: null,
+      // A Drupal.edit.EntityModel. Its "fields" attribute, which is a
+      // FieldCollection, is automatically updated to include this FieldModel.
+      entity: null,
+      // This field's metadata as returned by the EditController::metadata().
+      metadata: null,
+      // Callback function for validating changes between states. Receives the
+      // previous state, new state, context, and a callback
+      acceptStateChange: null,
+      // A logical field ID, of the form
+      // "<entity type>/<id>/<field name>/<language>", i.e. the fieldID without
+      // the view mode, to be able to identify other instances of the same field
+      // on the page but rendered in a different view mode. e.g. "node/1/field_tags/und".
+      logicalFieldID: null,
 
-    // The attributes below are stateful. The ones above will never change
-    // during the life of a FieldModel instance.
+      // The attributes below are stateful. The ones above will never change
+      // during the life of a FieldModel instance.
 
-    // In-place editing state of this field. Defaults to the initial state.
-    // Possible values: @see Drupal.edit.FieldModel.states.
-    state: 'inactive',
-    // The field is currently in the 'changed' state or one of the following
-    // states in which the field is still changed.
-    isChanged: false,
-    // Is tracked by the EntityModel, is mirrored here solely for decorative
-    // purposes: so that FieldDecorationView.renderChanged() can react to it.
-    inTempStore: false,
-    // The full HTML representation of this field (with the element that has
-    // the data-edit-field-id as the outer element). Used to propagate changes
-    // from this field instance to other instances of the same field.
-    html: null,
-    // An object containing the full HTML representations (values) of other view
-    // modes (keys) of this field, for other instances of this field displayed
-    // in a different view mode.
-    htmlForOtherViewModes: null
-  },
+      // In-place editing state of this field. Defaults to the initial state.
+      // Possible values: @see Drupal.edit.FieldModel.states.
+      state: 'inactive',
+      // The field is currently in the 'changed' state or one of the following
+      // states in which the field is still changed.
+      isChanged: false,
+      // Is tracked by the EntityModel, is mirrored here solely for decorative
+      // purposes: so that FieldDecorationView.renderChanged() can react to it.
+      inTempStore: false,
+      // The full HTML representation of this field (with the element that has
+      // the data-edit-field-id as the outer element). Used to propagate changes
+      // from this field instance to other instances of the same field.
+      html: null,
+      // An object containing the full HTML representations (values) of other view
+      // modes (keys) of this field, for other instances of this field displayed
+      // in a different view mode.
+      htmlForOtherViewModes: null
+    },
 
-  /**
-   * {@inheritdoc}
-   */
-  initialize: function (options) {
-    // Store the original full HTML representation of this field.
-    this.set('html', options.el.outerHTML);
+    /**
+     * {@inheritdoc}
+     */
+    initialize: function (options) {
+      // Store the original full HTML representation of this field.
+      this.set('html', options.el.outerHTML);
 
-    // Enlist field automatically in the associated entity's field collection.
-    this.get('entity').get('fields').add(this);
+      // Enlist field automatically in the associated entity's field collection.
+      this.get('entity').get('fields').add(this);
 
-    // Automatically generate the logical field ID.
-    this.set('logicalFieldID', this.get('fieldID').split('/').slice(0, 4).join('/'));
+      // Automatically generate the logical field ID.
+      this.set('logicalFieldID', this.get('fieldID').split('/').slice(0, 4).join('/'));
 
-    // Call Drupal.edit.BaseModel's initialize() method.
-    Drupal.edit.BaseModel.prototype.initialize.call(this, options);
-  },
+      // Call Drupal.edit.BaseModel's initialize() method.
+      Drupal.edit.BaseModel.prototype.initialize.call(this, options);
+    },
 
-  /**
-   * {@inheritdoc}
-   */
-  destroy: function (options) {
-    if (this.get('state') !== 'inactive') {
-      throw new Error("FieldModel cannot be destroyed if it is not inactive state.");
-    }
-    Drupal.edit.BaseModel.prototype.destroy.call(this, options);
-  },
+    /**
+     * {@inheritdoc}
+     */
+    destroy: function (options) {
+      if (this.get('state') !== 'inactive') {
+        throw new Error("FieldModel cannot be destroyed if it is not inactive state.");
+      }
+      Drupal.edit.BaseModel.prototype.destroy.call(this, options);
+    },
 
-  /**
-   * {@inheritdoc}
-   */
-  sync: function () {
-    // We don't use REST updates to sync.
-    return;
-  },
+    /**
+     * {@inheritdoc}
+     */
+    sync: function () {
+      // We don't use REST updates to sync.
+      return;
+    },
 
-  /**
-   * {@inheritdoc}
-   */
-  validate: function (attrs, options) {
-    var current = this.get('state');
-    var next = attrs.state;
-    if (current !== next) {
-      // Ensure it's a valid state.
-      if (_.indexOf(this.constructor.states, next) === -1) {
-        return '"' + next + '" is an invalid state';
-      }
-      // Check if the acceptStateChange callback accepts it.
-      if (!this.get('acceptStateChange')(current, next, options, this)) {
-        return 'state change not accepted';
+    /**
+     * {@inheritdoc}
+     */
+    validate: function (attrs, options) {
+      var current = this.get('state');
+      var next = attrs.state;
+      if (current !== next) {
+        // Ensure it's a valid state.
+        if (_.indexOf(this.constructor.states, next) === -1) {
+          return '"' + next + '" is an invalid state';
+        }
+        // Check if the acceptStateChange callback accepts it.
+        if (!this.get('acceptStateChange')(current, next, options, this)) {
+          return 'state change not accepted';
+        }
       }
-    }
-  },
+    },
 
-  /**
-   * Extracts the entity ID from this field's ID.
-   *
-   * @return String
-   *   An entity ID: a string of the format `<entity type>/<id>`.
-   */
-  getEntityID: function () {
-    return this.get('fieldID').split('/').slice(0, 2).join('/');
-  },
+    /**
+     * Extracts the entity ID from this field's ID.
+     *
+     * @return String
+     *   An entity ID: a string of the format `<entity type>/<id>`.
+     */
+    getEntityID: function () {
+      return this.get('fieldID').split('/').slice(0, 2).join('/');
+    },
 
-  /**
-   * Extracts the view mode ID from this field's ID.
-   *
-   * @return String
-   *   A view mode ID.
-   */
-  getViewMode: function () {
-    return this.get('fieldID').split('/').pop();
-  },
+    /**
+     * Extracts the view mode ID from this field's ID.
+     *
+     * @return String
+     *   A view mode ID.
+     */
+    getViewMode: function () {
+      return this.get('fieldID').split('/').pop();
+    },
 
-  /**
-   * Find other instances of this field with different view modes.
-   *
-   * @return Array
-   *   An array containing view mode IDs.
-   */
-  findOtherViewModes: function () {
-    var currentField = this;
-    var otherViewModes = [];
-    Drupal.edit.collections.fields
-      // Find all instances of fields that display the same logical field (same
-      // entity, same field, just a different instance and maybe a different
-      // view mode).
-      .where({ logicalFieldID: currentField.get('logicalFieldID') })
-      .forEach(function (field) {
-        // Ignore the current field.
-        if (field === currentField) {
-          return;
-        }
-        // Also ignore other fields with the same view mode.
-        else if (field.get('fieldID') === currentField.get('fieldID')) {
-          return;
-        }
-        else {
-          otherViewModes.push(field.getViewMode());
-        }
-      });
-    return otherViewModes;
-  }
+    /**
+     * Find other instances of this field with different view modes.
+     *
+     * @return Array
+     *   An array containing view mode IDs.
+     */
+    findOtherViewModes: function () {
+      var currentField = this;
+      var otherViewModes = [];
+      Drupal.edit.collections.fields
+        // Find all instances of fields that display the same logical field (same
+        // entity, same field, just a different instance and maybe a different
+        // view mode).
+        .where({ logicalFieldID: currentField.get('logicalFieldID') })
+        .forEach(function (field) {
+          // Ignore the current field.
+          if (field === currentField) {
+            return;
+          }
+          // Also ignore other fields with the same view mode.
+          else if (field.get('fieldID') === currentField.get('fieldID')) {
+            return;
+          }
+          else {
+            otherViewModes.push(field.getViewMode());
+          }
+        });
+      return otherViewModes;
+    }
 
-}, {
+  }, {
 
-  /**
-   * A list (sequence) of all possible states a field can be in during in-place
-   * editing.
-   */
-  states: [
-    // The field associated with this FieldModel is linked to an EntityModel;
-    // the user can choose to start in-place editing that entity (and
-    // consequently this field). No in-place editor (EditorView) is associated
-    // with this field, because this field is not being in-place edited.
-    // This is both the initial (not yet in-place editing) and the end state (
-    // finished in-place editing).
-    'inactive',
-    // The user is in-place editing this entity, and this field is a candidate
-    // for in-place editing. In-place editor should not
-    // - Trigger: user.
-    // - Guarantees: entity is ready, in-place editor (EditorView) is associated
-    //   with the field.
-    // - Expected behavior: visual indicators around the field indicate it is
-    //   available for in-place editing, no in-place editor presented yet.
-    'candidate',
-    // User is highlighting this field.
-    // - Trigger: user.
-    // - Guarantees: see 'candidate'.
-    // - Expected behavior: visual indicators to convey highlighting, in-place
-    //   editing toolbar shows field's label.
-    'highlighted',
-    // User has activated the in-place editing of this field; in-place editor is
-    // activating.
-    // - Trigger: user.
-    // - Guarantees: see 'candidate'.
-    // - Expected behavior: loading indicator, in-place editor is loading remote
-    //   data (e.g. retrieve form from back-end). Upon retrieval of remote data,
-    //   the in-place editor transitions the field's state to 'active'.
-    'activating',
-    // In-place editor has finished loading remote data; ready for use.
-    // - Trigger: in-place editor.
-    // - Guarantees: see 'candidate'.
-    // - Expected behavior: in-place editor for the field is ready for use.
-    'active',
-    // User has modified values in the in-place editor.
-    // - Trigger: user.
-    // - Guarantees: see 'candidate', plus in-place editor is ready for use.
-    // - Expected behavior: visual indicator of change.
-    'changed',
-    // User is saving changed field data in in-place editor to TempStore. The
-    // save mechanism of the in-place editor is called.
-    // - Trigger: user.
-    // - Guarantees: see 'candidate' and 'active'.
-    // - Expected behavior: saving indicator, in-place editor is saving field
-    //   data into TempStore. Upon successful saving (without validation
-    //   errors), the in-place editor transitions the field's state to 'saved',
-    //   but to 'invalid' upon failed saving (with validation errors).
-    'saving',
-    // In-place editor has successfully saved the changed field.
-    // - Trigger: in-place editor.
-    // - Guarantees: see 'candidate' and 'active'.
-    // - Expected behavior: transition back to 'candidate' state because the
-    //   deed is done. Then: 1) transition to 'inactive' to allow the field to
-    //   be rerendered, 2) destroy the FieldModel (which also destroys attached
-    //   views like the EditorView), 3) replace the existing field HTML with the
-    //   existing HTML and 4) attach behaviors again so that the field becomes
-    //   available again for in-place editing.
-    'saved',
-    // In-place editor has failed to saved the changed field: there were
-    // validation errors.
-    // - Trigger: in-place editor.
-    // - Guarantees: see 'candidate' and 'active'.
-    // - Expected behavior: remain in 'invalid' state, let the user make more
-    //   changes so that he can save it again, without validation errors.
-    'invalid'
-  ],
+    /**
+     * A list (sequence) of all possible states a field can be in during in-place
+     * editing.
+     */
+    states: [
+      // The field associated with this FieldModel is linked to an EntityModel;
+      // the user can choose to start in-place editing that entity (and
+      // consequently this field). No in-place editor (EditorView) is associated
+      // with this field, because this field is not being in-place edited.
+      // This is both the initial (not yet in-place editing) and the end state (
+      // finished in-place editing).
+      'inactive',
+      // The user is in-place editing this entity, and this field is a candidate
+      // for in-place editing. In-place editor should not
+      // - Trigger: user.
+      // - Guarantees: entity is ready, in-place editor (EditorView) is associated
+      //   with the field.
+      // - Expected behavior: visual indicators around the field indicate it is
+      //   available for in-place editing, no in-place editor presented yet.
+      'candidate',
+      // User is highlighting this field.
+      // - Trigger: user.
+      // - Guarantees: see 'candidate'.
+      // - Expected behavior: visual indicators to convey highlighting, in-place
+      //   editing toolbar shows field's label.
+      'highlighted',
+      // User has activated the in-place editing of this field; in-place editor is
+      // activating.
+      // - Trigger: user.
+      // - Guarantees: see 'candidate'.
+      // - Expected behavior: loading indicator, in-place editor is loading remote
+      //   data (e.g. retrieve form from back-end). Upon retrieval of remote data,
+      //   the in-place editor transitions the field's state to 'active'.
+      'activating',
+      // In-place editor has finished loading remote data; ready for use.
+      // - Trigger: in-place editor.
+      // - Guarantees: see 'candidate'.
+      // - Expected behavior: in-place editor for the field is ready for use.
+      'active',
+      // User has modified values in the in-place editor.
+      // - Trigger: user.
+      // - Guarantees: see 'candidate', plus in-place editor is ready for use.
+      // - Expected behavior: visual indicator of change.
+      'changed',
+      // User is saving changed field data in in-place editor to TempStore. The
+      // save mechanism of the in-place editor is called.
+      // - Trigger: user.
+      // - Guarantees: see 'candidate' and 'active'.
+      // - Expected behavior: saving indicator, in-place editor is saving field
+      //   data into TempStore. Upon successful saving (without validation
+      //   errors), the in-place editor transitions the field's state to 'saved',
+      //   but to 'invalid' upon failed saving (with validation errors).
+      'saving',
+      // In-place editor has successfully saved the changed field.
+      // - Trigger: in-place editor.
+      // - Guarantees: see 'candidate' and 'active'.
+      // - Expected behavior: transition back to 'candidate' state because the
+      //   deed is done. Then: 1) transition to 'inactive' to allow the field to
+      //   be rerendered, 2) destroy the FieldModel (which also destroys attached
+      //   views like the EditorView), 3) replace the existing field HTML with the
+      //   existing HTML and 4) attach behaviors again so that the field becomes
+      //   available again for in-place editing.
+      'saved',
+      // In-place editor has failed to saved the changed field: there were
+      // validation errors.
+      // - Trigger: in-place editor.
+      // - Guarantees: see 'candidate' and 'active'.
+      // - Expected behavior: remain in 'invalid' state, let the user make more
+      //   changes so that he can save it again, without validation errors.
+      'invalid'
+    ],
 
-  /**
-   * Indicates whether the 'from' state comes before the 'to' state.
-   *
-   * @param String from
-   *   One of Drupal.edit.FieldModel.states.
-   * @param String to
-   *   One of Drupal.edit.FieldModel.states.
-   * @return Boolean
-   */
-  followsStateSequence: function (from, to) {
-    return _.indexOf(this.states, from) < _.indexOf(this.states, to);
-  }
+    /**
+     * Indicates whether the 'from' state comes before the 'to' state.
+     *
+     * @param String from
+     *   One of Drupal.edit.FieldModel.states.
+     * @param String to
+     *   One of Drupal.edit.FieldModel.states.
+     * @return Boolean
+     */
+    followsStateSequence: function (from, to) {
+      return _.indexOf(this.states, from) < _.indexOf(this.states, to);
+    }
 
-});
+  });
 
-Drupal.edit.FieldCollection = Backbone.Collection.extend({
-  model: Drupal.edit.FieldModel
-});
+  Drupal.edit.FieldCollection = Backbone.Collection.extend({
+    model: Drupal.edit.FieldModel
+  });
 
 }(_, Backbone, Drupal));
diff --git a/core/modules/edit/js/theme.js b/core/modules/edit/js/theme.js
index fdd0fbb..9915b94 100644
--- a/core/modules/edit/js/theme.js
+++ b/core/modules/edit/js/theme.js
@@ -5,164 +5,164 @@
 
 (function ($, Drupal) {
 
-"use strict";
+  "use strict";
 
-/**
- * Theme function for a "backstage" for the Edit module.
- *
- * @param Object settings
- *   An object with the following keys:
- *   - String id: the id to apply to the backstage.
- * @return String
- *   The corresponding HTML.
- */
-Drupal.theme.editBackstage = function (settings) {
-  var html = '';
-  html += '<div id="' + settings.id + '" />';
-  return html;
-};
+  /**
+   * Theme function for a "backstage" for the Edit module.
+   *
+   * @param Object settings
+   *   An object with the following keys:
+   *   - String id: the id to apply to the backstage.
+   * @return String
+   *   The corresponding HTML.
+   */
+  Drupal.theme.editBackstage = function (settings) {
+    var html = '';
+    html += '<div id="' + settings.id + '" />';
+    return html;
+  };
 
-/**
- * Theme function for a toolbar container of the Edit module.
- *
- * @param Object settings
- *   An object with the following keys:
- *   - String id: the id to apply to the toolbar container.
- * @return String
- *   The corresponding HTML.
- */
-Drupal.theme.editEntityToolbar = function (settings) {
-  var html = '';
-  html += '<div id="' + settings.id + '" class="edit edit-toolbar-container clearfix">';
-  html += '<i class="edit-toolbar-pointer"></i>';
-  html += '<div class="edit-toolbar-content">';
-  html += '<div class="edit-toolbar edit-toolbar-entity clearfix icon icon-pencil">';
-  html += '<div class="edit-toolbar-label" />';
-  html += '</div>';
-  html += '<div class="edit-toolbar edit-toolbar-field clearfix" />';
-  html += '</div><div class="edit-toolbar-lining"></div></div>';
-  return html;
-};
+  /**
+   * Theme function for a toolbar container of the Edit module.
+   *
+   * @param Object settings
+   *   An object with the following keys:
+   *   - String id: the id to apply to the toolbar container.
+   * @return String
+   *   The corresponding HTML.
+   */
+  Drupal.theme.editEntityToolbar = function (settings) {
+    var html = '';
+    html += '<div id="' + settings.id + '" class="edit edit-toolbar-container clearfix">';
+    html += '<i class="edit-toolbar-pointer"></i>';
+    html += '<div class="edit-toolbar-content">';
+    html += '<div class="edit-toolbar edit-toolbar-entity clearfix icon icon-pencil">';
+    html += '<div class="edit-toolbar-label" />';
+    html += '</div>';
+    html += '<div class="edit-toolbar edit-toolbar-field clearfix" />';
+    html += '</div><div class="edit-toolbar-lining"></div></div>';
+    return html;
+  };
 
-/**
- * Theme function for a toolbar container of the Edit module.
- *
- * @param Object settings
- *   An object with the following keys:
- *   - String entityLabel: The title of the active entity.
- *   - String fieldLabel: The label of the highlighted or active field.
- * @return String
- *   The corresponding HTML.
- */
-Drupal.theme.editEntityToolbarLabel = function (settings) {
-  return '<span class="field">' + settings.fieldLabel + '</span>' + settings.entityLabel;
-};
+  /**
+   * Theme function for a toolbar container of the Edit module.
+   *
+   * @param Object settings
+   *   An object with the following keys:
+   *   - String entityLabel: The title of the active entity.
+   *   - String fieldLabel: The label of the highlighted or active field.
+   * @return String
+   *   The corresponding HTML.
+   */
+  Drupal.theme.editEntityToolbarLabel = function (settings) {
+    return '<span class="field">' + settings.fieldLabel + '</span>' + settings.entityLabel;
+  };
 
-/**
- * Element that defines a containing box of the placement of the entity toolbar.
- *
- * @return String
- *   The corresponding HTML.
- */
-Drupal.theme.editEntityToolbarFence = function () {
-  return '<div id="edit-toolbar-fence" />';
-};
+  /**
+   * Element that defines a containing box of the placement of the entity toolbar.
+   *
+   * @return String
+   *   The corresponding HTML.
+   */
+  Drupal.theme.editEntityToolbarFence = function () {
+    return '<div id="edit-toolbar-fence" />';
+  };
 
-/**
- * Theme function for a toolbar container of the Edit module.
- *
- * @param settings
- *   An object with the following keys:
- *   - id: the id to apply to the toolbar container.
- * @return
- *   The corresponding HTML.
- */
-Drupal.theme.editFieldToolbar = function (settings) {
-  return '<div id="' + settings.id + '" />';
-};
+  /**
+   * Theme function for a toolbar container of the Edit module.
+   *
+   * @param settings
+   *   An object with the following keys:
+   *   - id: the id to apply to the toolbar container.
+   * @return
+   *   The corresponding HTML.
+   */
+  Drupal.theme.editFieldToolbar = function (settings) {
+    return '<div id="' + settings.id + '" />';
+  };
 
-/**
- * Theme function for a toolbar toolgroup of the Edit module.
- *
- * @param Object settings
- *   An object with the following keys:
- *   - String id: (optional) the id of the toolgroup
- *   - String classes: the class of the toolgroup.
- *   - Array buttons: @see Drupal.theme.prototype.editButtons().
- * @return String
- *   The corresponding HTML.
- */
-Drupal.theme.editToolgroup = function (settings) {
-  // Classes.
-  var classes = (settings.classes || []);
-  classes.unshift('edit-toolgroup');
-  var html = '';
-  html += '<div class="' + classes.join(' ') + '"';
-  if (settings.id) {
-    html += ' id="' + settings.id + '"';
-  }
-  html += '>';
-  html += Drupal.theme('editButtons', { buttons: settings.buttons });
-  html += '</div>';
-  return html;
-};
-
-/**
- * Theme function for buttons of the Edit module.
- *
- * Can be used for the buttons both in the toolbar toolgroups and in the modal.
- *
- * @param Object settings
- *   An object with the following keys:
- *   - buttons: an array of objects with the following keys:
- *     - String type: the type of the button (defaults to 'button')
- *     - Array classes: the classes of the button.
- *     - String label: the label of the button.
- * @return String
- *   The corresponding HTML.
- */
-Drupal.theme.editButtons = function (settings) {
-  var html = '';
-  for (var i = 0; i < settings.buttons.length; i++) {
-    var button = settings.buttons[i];
-    if (!button.hasOwnProperty('type')) {
-      button.type = 'button';
+  /**
+   * Theme function for a toolbar toolgroup of the Edit module.
+   *
+   * @param Object settings
+   *   An object with the following keys:
+   *   - String id: (optional) the id of the toolgroup
+   *   - String classes: the class of the toolgroup.
+   *   - Array buttons: @see Drupal.theme.prototype.editButtons().
+   * @return String
+   *   The corresponding HTML.
+   */
+  Drupal.theme.editToolgroup = function (settings) {
+    // Classes.
+    var classes = (settings.classes || []);
+    classes.unshift('edit-toolgroup');
+    var html = '';
+    html += '<div class="' + classes.join(' ') + '"';
+    if (settings.id) {
+      html += ' id="' + settings.id + '"';
     }
-    // Attributes.
-    var attributes = [];
-    var attrMap  = settings.buttons[i].attributes || {};
-    for (var attr in attrMap) {
-      if (attrMap.hasOwnProperty(attr)) {
-        attributes.push(attr + ((attrMap[attr]) ? '="' + attrMap[attr] + '"' : '' ));
+    html += '>';
+    html += Drupal.theme('editButtons', { buttons: settings.buttons });
+    html += '</div>';
+    return html;
+  };
+
+  /**
+   * Theme function for buttons of the Edit module.
+   *
+   * Can be used for the buttons both in the toolbar toolgroups and in the modal.
+   *
+   * @param Object settings
+   *   An object with the following keys:
+   *   - buttons: an array of objects with the following keys:
+   *     - String type: the type of the button (defaults to 'button')
+   *     - Array classes: the classes of the button.
+   *     - String label: the label of the button.
+   * @return String
+   *   The corresponding HTML.
+   */
+  Drupal.theme.editButtons = function (settings) {
+    var html = '';
+    for (var i = 0; i < settings.buttons.length; i++) {
+      var button = settings.buttons[i];
+      if (!button.hasOwnProperty('type')) {
+        button.type = 'button';
+      }
+      // Attributes.
+      var attributes = [];
+      var attrMap = settings.buttons[i].attributes || {};
+      for (var attr in attrMap) {
+        if (attrMap.hasOwnProperty(attr)) {
+          attributes.push(attr + ((attrMap[attr]) ? '="' + attrMap[attr] + '"' : '' ));
+        }
       }
+      html += '<button type="' + button.type + '" class="' + button.classes + '"' + ' ' + attributes.join(' ') + '>';
+      html += button.label;
+      html += '</button>';
     }
-    html += '<button type="' + button.type + '" class="' + button.classes + '"'  + ' ' + attributes.join(' ') + '>';
-    html += button.label;
-    html += '</button>';
-  }
-  return html;
-};
+    return html;
+  };
 
-/**
- * Theme function for a form container of the Edit module.
- *
- * @param Object settings
- *   An object with the following keys:
- *   - String id: the id to apply to the toolbar container.
- *   - String loadingMsg: The message to show while loading.
- * @return String
- *   The corresponding HTML.
- */
-Drupal.theme.editFormContainer = function (settings) {
-  var html = '';
-  html += '<div id="' + settings.id + '" class="edit-form-container">';
-  html += '  <div class="edit-form">';
-  html += '    <div class="placeholder">';
-  html +=        settings.loadingMsg;
-  html += '    </div>';
-  html += '  </div>';
-  html += '</div>';
-  return html;
-};
+  /**
+   * Theme function for a form container of the Edit module.
+   *
+   * @param Object settings
+   *   An object with the following keys:
+   *   - String id: the id to apply to the toolbar container.
+   *   - String loadingMsg: The message to show while loading.
+   * @return String
+   *   The corresponding HTML.
+   */
+  Drupal.theme.editFormContainer = function (settings) {
+    var html = '';
+    html += '<div id="' + settings.id + '" class="edit-form-container">';
+    html += '  <div class="edit-form">';
+    html += '    <div class="placeholder">';
+    html += settings.loadingMsg;
+    html += '    </div>';
+    html += '  </div>';
+    html += '</div>';
+    return html;
+  };
 
 })(jQuery, Drupal);
diff --git a/core/modules/edit/js/util.js b/core/modules/edit/js/util.js
index da7656e..6883d7e 100644
--- a/core/modules/edit/js/util.js
+++ b/core/modules/edit/js/util.js
@@ -5,172 +5,172 @@
 
 (function ($, Drupal) {
 
-"use strict";
+  "use strict";
 
-Drupal.edit.util = Drupal.edit.util || {};
+  Drupal.edit.util = Drupal.edit.util || {};
 
-Drupal.edit.util.constants = {};
-Drupal.edit.util.constants.transitionEnd = "transitionEnd.edit webkitTransitionEnd.edit transitionend.edit msTransitionEnd.edit oTransitionEnd.edit";
-
-/**
- * Converts a field id into a formatted url path.
- *
- * @param String id
- *   The id of an editable field. For example, 'node/1/body/und/full'.
- * @param String urlFormat
- *   The Controller route for field processing. For example,
- *   '/edit/form/%21entity_type/%21id/%21field_name/%21langcode/%21view_mode'.
- */
-Drupal.edit.util.buildUrl = function (id, urlFormat) {
-  var parts = id.split('/');
-  return Drupal.formatString(decodeURIComponent(urlFormat), {
-    '!entity_type': parts[0],
-    '!id'         : parts[1],
-    '!field_name' : parts[2],
-    '!langcode'   : parts[3],
-    '!view_mode'  : parts[4]
-  });
-};
-
-/**
- * Shows a network error modal dialog.
- *
- * @param String title
- *   The title to use in the modal dialog.
- * @param String message
- *   The message to use in the modal dialog.
- */
-Drupal.edit.util.networkErrorModal = function (title, message) {
-  var $message = $('<div>' + message + '</div>');
-  var networkErrorModal = Drupal.dialog($message.get(0), {
-    title: title,
-    dialogClass: 'edit-network-error',
-    buttons: [
-      {
-        text: Drupal.t('OK'),
-        click: function() {
-          networkErrorModal.close();
-        }
-      }
-    ],
-    create: function () {
-      $(this).parent().find('.ui-dialog-titlebar-close').remove();
-    },
-    close: function (event) {
-      // Automatically destroy the DOM element that was used for the dialog.
-      $(event.target).remove();
-    }
-  });
-  networkErrorModal.showModal();
-};
-
-Drupal.edit.util.form = {
+  Drupal.edit.util.constants = {};
+  Drupal.edit.util.constants.transitionEnd = "transitionEnd.edit webkitTransitionEnd.edit transitionend.edit msTransitionEnd.edit oTransitionEnd.edit";
 
   /**
-   * Loads a form, calls a callback to insert.
-   *
-   * Leverages Drupal.ajax' ability to have scoped (per-instance) command
-   * implementations to be able to call a callback.
+   * Converts a field id into a formatted url path.
    *
-   * @param Object options
-   *   An object with the following keys:
-   *    - jQuery $el: (required) DOM element necessary for Drupal.ajax to
-   *      perform AJAX commands.
-   *    - String fieldID: (required) the field ID that uniquely identifies the
-   *      field for which this form will be loaded.
-   *    - Boolean nocssjs: (required) boolean indicating whether no CSS and JS
-   *      should be returned (necessary when the form is invisible to the user).
-   *    - Boolean reset: (required) boolean indicating whether the data stored
-   *      for this field's entity in TempStore should be used or reset.
-   * @param Function callback
-   *   A callback function that will receive the form to be inserted, as well as
-   *   the ajax object, necessary if the callback wants to perform other AJAX
-   *   commands.
+   * @param String id
+   *   The id of an editable field. For example, 'node/1/body/und/full'.
+   * @param String urlFormat
+   *   The Controller route for field processing. For example,
+   *   '/edit/form/%21entity_type/%21id/%21field_name/%21langcode/%21view_mode'.
    */
-  load: function (options, callback) {
-    var $el = options.$el;
-    var fieldID = options.fieldID;
-
-    // Create a Drupal.ajax instance to load the form.
-    var formLoaderAjax = new Drupal.ajax(fieldID, $el, {
-      url: Drupal.edit.util.buildUrl(fieldID, Drupal.url('edit/form/!entity_type/!id/!field_name/!langcode/!view_mode')),
-      event: 'edit-internal.edit',
-      submit: {
-        nocssjs : options.nocssjs,
-        reset : options.reset
-      },
-      progress: { type : null }, // No progress indicator.
-      error: function (xhr, url) {
-        $el.off('edit-internal.edit');
-
-        // Show a modal to inform the user of the network error.
-        var fieldLabel = Drupal.edit.metadata.get(fieldID, 'label');
-        var message = Drupal.t('Could not load the form for <q>@field-label</q>, either due to a website problem or a network connection problem.<br>Please try again.', { '@field-label' : fieldLabel });
-        Drupal.edit.util.networkErrorModal(Drupal.t('Sorry!'), message);
-
-        // Change the state back to "candidate", to allow the user to start
-        // in-place editing of the field again.
-        var fieldModel = Drupal.edit.app.model.get('activeField');
-        fieldModel.set('state', 'candidate');
-      }
+  Drupal.edit.util.buildUrl = function (id, urlFormat) {
+    var parts = id.split('/');
+    return Drupal.formatString(decodeURIComponent(urlFormat), {
+      '!entity_type': parts[0],
+      '!id': parts[1],
+      '!field_name': parts[2],
+      '!langcode': parts[3],
+      '!view_mode': parts[4]
     });
-    // Implement a scoped editFieldForm AJAX command: calls the callback.
-    formLoaderAjax.commands.editFieldForm = function (ajax, response, status) {
-      callback(response.data, ajax);
-      $el.off('edit-internal.edit');
-      formLoaderAjax = null;
-    };
-    // This will ensure our scoped editFieldForm AJAX command gets called.
-    $el.trigger('edit-internal.edit');
-  },
+  };
 
   /**
-   * Creates a Drupal.ajax instance that is used to save a form.
+   * Shows a network error modal dialog.
    *
-   * @param Object options
-   *   An object with the following keys:
-   *    - nocssjs: (required) boolean indicating whether no CSS and JS should be
-   *      returned (necessary when the form is invisible to the user).
-   *    - other_view_modes: (required) array containing view mode IDs (of other
-   *      instances of this field on the page).
-   * @return Drupal.ajax
-   *   A Drupal.ajax instance.
+   * @param String title
+   *   The title to use in the modal dialog.
+   * @param String message
+   *   The message to use in the modal dialog.
    */
-  ajaxifySaving: function (options, $submit) {
-    // Re-wire the form to handle submit.
-    var settings = {
-      url: $submit.closest('form').attr('action'),
-      setClick: true,
-      event: 'click.edit',
-      progress: { type: null },
-      submit: {
-        nocssjs : options.nocssjs,
-        other_view_modes : options.other_view_modes
-      },
-      // Reimplement the success handler to ensure Drupal.attachBehaviors() does
-      // not get called on the form.
-      success: function (response, status) {
-        for (var i in response) {
-          if (response.hasOwnProperty(i) && response[i].command && this.commands[response[i].command]) {
-            this.commands[response[i].command](this, response[i], status);
+  Drupal.edit.util.networkErrorModal = function (title, message) {
+    var $message = $('<div>' + message + '</div>');
+    var networkErrorModal = Drupal.dialog($message.get(0), {
+      title: title,
+      dialogClass: 'edit-network-error',
+      buttons: [
+        {
+          text: Drupal.t('OK'),
+          click: function () {
+            networkErrorModal.close();
           }
         }
+      ],
+      create: function () {
+        $(this).parent().find('.ui-dialog-titlebar-close').remove();
+      },
+      close: function (event) {
+        // Automatically destroy the DOM element that was used for the dialog.
+        $(event.target).remove();
       }
-    };
+    });
+    networkErrorModal.showModal();
+  };
+
+  Drupal.edit.util.form = {
+
+    /**
+     * Loads a form, calls a callback to insert.
+     *
+     * Leverages Drupal.ajax' ability to have scoped (per-instance) command
+     * implementations to be able to call a callback.
+     *
+     * @param Object options
+     *   An object with the following keys:
+     *    - jQuery $el: (required) DOM element necessary for Drupal.ajax to
+     *      perform AJAX commands.
+     *    - String fieldID: (required) the field ID that uniquely identifies the
+     *      field for which this form will be loaded.
+     *    - Boolean nocssjs: (required) boolean indicating whether no CSS and JS
+     *      should be returned (necessary when the form is invisible to the user).
+     *    - Boolean reset: (required) boolean indicating whether the data stored
+     *      for this field's entity in TempStore should be used or reset.
+     * @param Function callback
+     *   A callback function that will receive the form to be inserted, as well as
+     *   the ajax object, necessary if the callback wants to perform other AJAX
+     *   commands.
+     */
+    load: function (options, callback) {
+      var $el = options.$el;
+      var fieldID = options.fieldID;
+
+      // Create a Drupal.ajax instance to load the form.
+      var formLoaderAjax = new Drupal.ajax(fieldID, $el, {
+        url: Drupal.edit.util.buildUrl(fieldID, Drupal.url('edit/form/!entity_type/!id/!field_name/!langcode/!view_mode')),
+        event: 'edit-internal.edit',
+        submit: {
+          nocssjs: options.nocssjs,
+          reset: options.reset
+        },
+        progress: { type: null }, // No progress indicator.
+        error: function (xhr, url) {
+          $el.off('edit-internal.edit');
+
+          // Show a modal to inform the user of the network error.
+          var fieldLabel = Drupal.edit.metadata.get(fieldID, 'label');
+          var message = Drupal.t('Could not load the form for <q>@field-label</q>, either due to a website problem or a network connection problem.<br>Please try again.', { '@field-label': fieldLabel });
+          Drupal.edit.util.networkErrorModal(Drupal.t('Sorry!'), message);
+
+          // Change the state back to "candidate", to allow the user to start
+          // in-place editing of the field again.
+          var fieldModel = Drupal.edit.app.model.get('activeField');
+          fieldModel.set('state', 'candidate');
+        }
+      });
+      // Implement a scoped editFieldForm AJAX command: calls the callback.
+      formLoaderAjax.commands.editFieldForm = function (ajax, response, status) {
+        callback(response.data, ajax);
+        $el.off('edit-internal.edit');
+        formLoaderAjax = null;
+      };
+      // This will ensure our scoped editFieldForm AJAX command gets called.
+      $el.trigger('edit-internal.edit');
+    },
 
-    return new Drupal.ajax($submit.attr('id'), $submit[0], settings);
-  },
+    /**
+     * Creates a Drupal.ajax instance that is used to save a form.
+     *
+     * @param Object options
+     *   An object with the following keys:
+     *    - nocssjs: (required) boolean indicating whether no CSS and JS should be
+     *      returned (necessary when the form is invisible to the user).
+     *    - other_view_modes: (required) array containing view mode IDs (of other
+     *      instances of this field on the page).
+     * @return Drupal.ajax
+     *   A Drupal.ajax instance.
+     */
+    ajaxifySaving: function (options, $submit) {
+      // Re-wire the form to handle submit.
+      var settings = {
+        url: $submit.closest('form').attr('action'),
+        setClick: true,
+        event: 'click.edit',
+        progress: { type: null },
+        submit: {
+          nocssjs: options.nocssjs,
+          other_view_modes: options.other_view_modes
+        },
+        // Reimplement the success handler to ensure Drupal.attachBehaviors() does
+        // not get called on the form.
+        success: function (response, status) {
+          for (var i in response) {
+            if (response.hasOwnProperty(i) && response[i].command && this.commands[response[i].command]) {
+              this.commands[response[i].command](this, response[i], status);
+            }
+          }
+        }
+      };
 
-  /**
-   * Cleans up the Drupal.ajax instance that is used to save the form.
-   *
-   * @param Drupal.ajax ajax
-   *   A Drupal.ajax that was returned by Drupal.edit.form.ajaxifySaving().
-   */
-  unajaxifySaving: function (ajax) {
-    $(ajax.element).off('click.edit');
-  }
+      return new Drupal.ajax($submit.attr('id'), $submit[0], settings);
+    },
+
+    /**
+     * Cleans up the Drupal.ajax instance that is used to save the form.
+     *
+     * @param Drupal.ajax ajax
+     *   A Drupal.ajax that was returned by Drupal.edit.form.ajaxifySaving().
+     */
+    unajaxifySaving: function (ajax) {
+      $(ajax.element).off('click.edit');
+    }
 
-};
+  };
 
 })(jQuery, Drupal);
diff --git a/core/modules/edit/js/views/AppView.js b/core/modules/edit/js/views/AppView.js
index 599ba2b..73ab7f8 100644
--- a/core/modules/edit/js/views/AppView.js
+++ b/core/modules/edit/js/views/AppView.js
@@ -7,577 +7,577 @@
 
 (function ($, _, Backbone, Drupal) {
 
-"use strict";
-
-// Indicates whether the page should be reloaded after in-place editing has
-// shut down. A page reload is necessary to re-instate the original HTML of the
-// edited fields if in-place editing has been canceled and one or more of the
-// entity's fields were saved to TempStore: one of them may have been changed to
-// the empty value and hence may have been rerendered as the empty string, which
-// makes it impossible for Edit to know where to restore the original HTML.
-var reload = false;
-
-Drupal.edit.AppView = Backbone.View.extend({
-
-  /**
-   * {@inheritdoc}
-   *
-   * @param Object options
-   *   An object with the following keys:
-   *   - Drupal.edit.AppModel model: the application state model
-   *   - Drupal.edit.EntityCollection entitiesCollection: all on-page entities
-   *   - Drupal.edit.FieldCollection fieldsCollection: all on-page fields
-   */
-  initialize: function (options) {
-    // AppView's configuration for handling states.
-    // @see Drupal.edit.FieldModel.states
-    this.activeFieldStates = ['activating', 'active'];
-    this.singleFieldStates = ['highlighted', 'activating', 'active'];
-    this.changedFieldStates = ['changed', 'saving', 'saved', 'invalid'];
-    this.readyFieldStates = ['candidate', 'highlighted'];
-
-    this.listenTo(options.entitiesCollection, {
+  "use strict";
+
+  // Indicates whether the page should be reloaded after in-place editing has
+  // shut down. A page reload is necessary to re-instate the original HTML of the
+  // edited fields if in-place editing has been canceled and one or more of the
+  // entity's fields were saved to TempStore: one of them may have been changed to
+  // the empty value and hence may have been rerendered as the empty string, which
+  // makes it impossible for Edit to know where to restore the original HTML.
+  var reload = false;
+
+  Drupal.edit.AppView = Backbone.View.extend({
+
+    /**
+     * {@inheritdoc}
+     *
+     * @param Object options
+     *   An object with the following keys:
+     *   - Drupal.edit.AppModel model: the application state model
+     *   - Drupal.edit.EntityCollection entitiesCollection: all on-page entities
+     *   - Drupal.edit.FieldCollection fieldsCollection: all on-page fields
+     */
+    initialize: function (options) {
+      // AppView's configuration for handling states.
+      // @see Drupal.edit.FieldModel.states
+      this.activeFieldStates = ['activating', 'active'];
+      this.singleFieldStates = ['highlighted', 'activating', 'active'];
+      this.changedFieldStates = ['changed', 'saving', 'saved', 'invalid'];
+      this.readyFieldStates = ['candidate', 'highlighted'];
+
+      this.listenTo(options.entitiesCollection, {
+        // Track app state.
+        'change:state': this.appStateChange,
+        'change:isActive': this.enforceSingleActiveEntity
+      });
+
       // Track app state.
-      'change:state': this.appStateChange,
-      'change:isActive': this.enforceSingleActiveEntity
-    });
-
-    // Track app state.
-    this.listenTo(options.fieldsCollection, 'change:state', this.editorStateChange);
-    // Respond to field model HTML representation change events.
-    this.listenTo(options.fieldsCollection, 'change:html', this.renderUpdatedField);
-    this.listenTo(options.fieldsCollection, 'change:html', this.propagateUpdatedField);
-    // Respond to addition.
-    this.listenTo(options.fieldsCollection, 'add', this.rerenderedFieldToCandidate);
-    // Respond to destruction.
-    this.listenTo(options.fieldsCollection, 'destroy', this.teardownEditor);
-  },
-
-  /**
-   * Handles setup/teardown and state changes when the active entity changes.
-   *
-   * @param Drupal.edit.EntityModel entityModel
-   *   An instance of the EntityModel class.
-   * @param String state
-   *   The state of the associated field. One of Drupal.edit.EntityModel.states.
-   */
-  appStateChange: function (entityModel, state) {
-    var app = this;
-    var entityToolbarView;
-    switch (state) {
-      case 'launching':
-        reload = false;
-        // First, create an entity toolbar view.
-        entityToolbarView = new Drupal.edit.EntityToolbarView({
-          model: entityModel,
-          appModel: this.model
-        });
-        entityModel.toolbarView = entityToolbarView;
-        // Second, set up in-place editors.
-        // They must be notified of state changes, hence this must happen while
-        // the associated fields are still in the 'inactive' state.
-        entityModel.get('fields').each(function (fieldModel) {
-          app.setupEditor(fieldModel);
-        });
-        // Third, transition the entity to the 'opening' state, which will
-        // transition all fields from 'inactive' to 'candidate'.
-        _.defer(function () {
-          entityModel.set('state', 'opening');
-        });
-        break;
-      case 'closed':
-        entityToolbarView = entityModel.toolbarView;
-        // First, tear down the in-place editors.
-        entityModel.get('fields').each(function (fieldModel) {
-          app.teardownEditor(fieldModel);
-        });
-        // Second, tear down the entity toolbar view.
-        if (entityToolbarView) {
-          entityToolbarView.remove();
-          delete entityModel.toolbarView;
-        }
-        // A page reload may be necessary to re-instate the original HTML of the
-        // edited fields.
-        if (reload) {
+      this.listenTo(options.fieldsCollection, 'change:state', this.editorStateChange);
+      // Respond to field model HTML representation change events.
+      this.listenTo(options.fieldsCollection, 'change:html', this.renderUpdatedField);
+      this.listenTo(options.fieldsCollection, 'change:html', this.propagateUpdatedField);
+      // Respond to addition.
+      this.listenTo(options.fieldsCollection, 'add', this.rerenderedFieldToCandidate);
+      // Respond to destruction.
+      this.listenTo(options.fieldsCollection, 'destroy', this.teardownEditor);
+    },
+
+    /**
+     * Handles setup/teardown and state changes when the active entity changes.
+     *
+     * @param Drupal.edit.EntityModel entityModel
+     *   An instance of the EntityModel class.
+     * @param String state
+     *   The state of the associated field. One of Drupal.edit.EntityModel.states.
+     */
+    appStateChange: function (entityModel, state) {
+      var app = this;
+      var entityToolbarView;
+      switch (state) {
+        case 'launching':
           reload = false;
-          location.reload();
-        }
-        break;
-    }
-  },
-
-  /**
-   * Accepts or reject editor (Editor) state changes.
-   *
-   * This is what ensures that the app is in control of what happens.
-   *
-   * @param String from
-   *   The previous state.
-   * @param String to
-   *   The new state.
-   * @param null|Object context
-   *   The context that is trying to trigger the state change.
-   * @param Drupal.edit.FieldModel fieldModel
-   *   The fieldModel to which this change applies.
-   */
-  acceptEditorStateChange: function (from, to, context, fieldModel) {
-    var accept = true;
-
-    // If the app is in view mode, then reject all state changes except for
-    // those to 'inactive'.
-    if (context && (context.reason === 'stop' || context.reason === 'rerender')) {
-      if (from === 'candidate' && to === 'inactive') {
-        accept = true;
+          // First, create an entity toolbar view.
+          entityToolbarView = new Drupal.edit.EntityToolbarView({
+            model: entityModel,
+            appModel: this.model
+          });
+          entityModel.toolbarView = entityToolbarView;
+          // Second, set up in-place editors.
+          // They must be notified of state changes, hence this must happen while
+          // the associated fields are still in the 'inactive' state.
+          entityModel.get('fields').each(function (fieldModel) {
+            app.setupEditor(fieldModel);
+          });
+          // Third, transition the entity to the 'opening' state, which will
+          // transition all fields from 'inactive' to 'candidate'.
+          _.defer(function () {
+            entityModel.set('state', 'opening');
+          });
+          break;
+        case 'closed':
+          entityToolbarView = entityModel.toolbarView;
+          // First, tear down the in-place editors.
+          entityModel.get('fields').each(function (fieldModel) {
+            app.teardownEditor(fieldModel);
+          });
+          // Second, tear down the entity toolbar view.
+          if (entityToolbarView) {
+            entityToolbarView.remove();
+            delete entityModel.toolbarView;
+          }
+          // A page reload may be necessary to re-instate the original HTML of the
+          // edited fields.
+          if (reload) {
+            reload = false;
+            location.reload();
+          }
+          break;
       }
-    }
-    // Handling of edit mode state changes is more granular.
-    else {
-      // In general, enforce the states sequence. Disallow going back from a
-      // "later" state to an "earlier" state, except in explicitly allowed
-      // cases.
-      if (!Drupal.edit.FieldModel.followsStateSequence(from, to)) {
-        accept = false;
-        // Allow: activating/active -> candidate.
-        // Necessary to stop editing a field.
-        if (_.indexOf(this.activeFieldStates, from) !== -1 && to === 'candidate') {
-          accept = true;
-        }
-        // Allow: changed/invalid -> candidate.
-        // Necessary to stop editing a field when it is changed or invalid.
-        else if ((from === 'changed' || from === 'invalid') && to === 'candidate') {
-          accept = true;
-        }
-        // Allow: highlighted -> candidate.
-        // Necessary to stop highlighting a field.
-        else if (from === 'highlighted' && to === 'candidate') {
-          accept = true;
-        }
-        // Allow: saved -> candidate.
-        // Necessary when successfully saved a field.
-        else if (from === 'saved' && to === 'candidate') {
-          accept = true;
-        }
-        // Allow: invalid -> saving.
-        // Necessary to be able to save a corrected, invalid field.
-        else if (from === 'invalid' && to === 'saving') {
-          accept = true;
-        }
-        // Allow: invalid -> activating.
-        // Necessary to be able to correct a field that turned out to be invalid
-        // after the user already had moved on to the next field (which we
-        // explicitly allow to have a fluent UX).
-        else if (from === 'invalid' && to === 'activating') {
+    },
+
+    /**
+     * Accepts or reject editor (Editor) state changes.
+     *
+     * This is what ensures that the app is in control of what happens.
+     *
+     * @param String from
+     *   The previous state.
+     * @param String to
+     *   The new state.
+     * @param null|Object context
+     *   The context that is trying to trigger the state change.
+     * @param Drupal.edit.FieldModel fieldModel
+     *   The fieldModel to which this change applies.
+     */
+    acceptEditorStateChange: function (from, to, context, fieldModel) {
+      var accept = true;
+
+      // If the app is in view mode, then reject all state changes except for
+      // those to 'inactive'.
+      if (context && (context.reason === 'stop' || context.reason === 'rerender')) {
+        if (from === 'candidate' && to === 'inactive') {
           accept = true;
         }
       }
+      // Handling of edit mode state changes is more granular.
+      else {
+        // In general, enforce the states sequence. Disallow going back from a
+        // "later" state to an "earlier" state, except in explicitly allowed
+        // cases.
+        if (!Drupal.edit.FieldModel.followsStateSequence(from, to)) {
+          accept = false;
+          // Allow: activating/active -> candidate.
+          // Necessary to stop editing a field.
+          if (_.indexOf(this.activeFieldStates, from) !== -1 && to === 'candidate') {
+            accept = true;
+          }
+          // Allow: changed/invalid -> candidate.
+          // Necessary to stop editing a field when it is changed or invalid.
+          else if ((from === 'changed' || from === 'invalid') && to === 'candidate') {
+            accept = true;
+          }
+          // Allow: highlighted -> candidate.
+          // Necessary to stop highlighting a field.
+          else if (from === 'highlighted' && to === 'candidate') {
+            accept = true;
+          }
+          // Allow: saved -> candidate.
+          // Necessary when successfully saved a field.
+          else if (from === 'saved' && to === 'candidate') {
+            accept = true;
+          }
+          // Allow: invalid -> saving.
+          // Necessary to be able to save a corrected, invalid field.
+          else if (from === 'invalid' && to === 'saving') {
+            accept = true;
+          }
+          // Allow: invalid -> activating.
+          // Necessary to be able to correct a field that turned out to be invalid
+          // after the user already had moved on to the next field (which we
+          // explicitly allow to have a fluent UX).
+          else if (from === 'invalid' && to === 'activating') {
+            accept = true;
+          }
+        }
 
-      // If it's not against the general principle, then here are more
-      // disallowed cases to check.
-      if (accept) {
-        var activeField, activeFieldState;
-        // Ensure only one field (editor) at a time is active … but allow a user
-        // to hop from one field to the next, even if we still have to start
-        // saving the field that is currently active: assume it will be valid,
-        // to allow for a fluent UX. (If it turns out to be invalid, this block
-        // of code also handles that.)
-        if ((this.readyFieldStates.indexOf(from) !== -1 || from === 'invalid') && this.activeFieldStates.indexOf(to) !== -1) {
-          activeField = this.model.get('activeField');
-          if (activeField && activeField !== fieldModel) {
-            activeFieldState = activeField.get('state');
-            // Allow the state change. If the state of the active field is:
-            // - 'activating' or 'active': change it to 'candidate'
-            // - 'changed' or 'invalid': change it to 'saving'
-            // - 'saving'or 'saved': don't do anything.
-            if (this.activeFieldStates.indexOf(activeFieldState) !== -1) {
-              activeField.set('state', 'candidate');
+        // If it's not against the general principle, then here are more
+        // disallowed cases to check.
+        if (accept) {
+          var activeField, activeFieldState;
+          // Ensure only one field (editor) at a time is active … but allow a user
+          // to hop from one field to the next, even if we still have to start
+          // saving the field that is currently active: assume it will be valid,
+          // to allow for a fluent UX. (If it turns out to be invalid, this block
+          // of code also handles that.)
+          if ((this.readyFieldStates.indexOf(from) !== -1 || from === 'invalid') && this.activeFieldStates.indexOf(to) !== -1) {
+            activeField = this.model.get('activeField');
+            if (activeField && activeField !== fieldModel) {
+              activeFieldState = activeField.get('state');
+              // Allow the state change. If the state of the active field is:
+              // - 'activating' or 'active': change it to 'candidate'
+              // - 'changed' or 'invalid': change it to 'saving'
+              // - 'saving'or 'saved': don't do anything.
+              if (this.activeFieldStates.indexOf(activeFieldState) !== -1) {
+                activeField.set('state', 'candidate');
+              }
+              else if (activeFieldState === 'changed' || activeFieldState === 'invalid') {
+                activeField.set('state', 'saving');
+              }
+
+              // If the field that's being activated is in fact already in the
+              // invalid state (which can only happen because above we allowed the
+              // user to move on to another field to allow for a fluent UX; we
+              // assumed it would be saved successfully), then we shouldn't allow
+              // the field to enter the 'activating' state, instead, we simply
+              // change the active editor. All guarantees and assumptions for this
+              // field still hold!
+              if (from === 'invalid') {
+                this.model.set('activeField', fieldModel);
+                accept = false;
+              }
+              else {
+                // Do not reject: the field is either in the 'candidate' or
+                // 'highlighted' state and we allow it to enter the 'activating'
+                // state!
+              }
             }
-            else if (activeFieldState === 'changed' || activeFieldState === 'invalid') {
-              activeField.set('state', 'saving');
+          }
+          // Reject going from activating/active to candidate because of a
+          // mouseleave.
+          else if (_.indexOf(this.activeFieldStates, from) !== -1 && to === 'candidate') {
+            if (context && context.reason === 'mouseleave') {
+              accept = false;
             }
-
-            // If the field that's being activated is in fact already in the
-            // invalid state (which can only happen because above we allowed the
-            // user to move on to another field to allow for a fluent UX; we
-            // assumed it would be saved successfully), then we shouldn't allow
-            // the field to enter the 'activating' state, instead, we simply
-            // change the active editor. All guarantees and assumptions for this
-            // field still hold!
-            if (from === 'invalid') {
-              this.model.set('activeField', fieldModel);
+          }
+          // When attempting to stop editing a changed/invalid property, ask for
+          // confirmation.
+          else if ((from === 'changed' || from === 'invalid') && to === 'candidate') {
+            if (context && context.reason === 'mouseleave') {
               accept = false;
             }
             else {
-              // Do not reject: the field is either in the 'candidate' or
-              // 'highlighted' state and we allow it to enter the 'activating'
-              // state!
-            }
-          }
-        }
-        // Reject going from activating/active to candidate because of a
-        // mouseleave.
-        else if (_.indexOf(this.activeFieldStates, from) !== -1 && to === 'candidate') {
-          if (context && context.reason === 'mouseleave') {
-            accept = false;
-          }
-        }
-        // When attempting to stop editing a changed/invalid property, ask for
-        // confirmation.
-        else if ((from === 'changed' || from === 'invalid') && to === 'candidate') {
-          if (context && context.reason === 'mouseleave') {
-            accept = false;
-          }
-          else {
-            // Check whether the transition has been confirmed?
-            if (context && context.confirmed) {
-              accept = true;
+              // Check whether the transition has been confirmed?
+              if (context && context.confirmed) {
+                accept = true;
+              }
             }
           }
         }
       }
-    }
 
-    return accept;
-  },
-
-  /**
-   * Sets up the in-place editor for the given field.
-   *
-   * Must happen before the fieldModel's state is changed to 'candidate'.
-   *
-   * @param Drupal.edit.FieldModel fieldModel
-   *   The field for which an in-place editor must be set up.
-   */
-  setupEditor: function (fieldModel) {
-    // Get the corresponding entity toolbar.
-    var entityModel = fieldModel.get('entity');
-    var entityToolbarView = entityModel.toolbarView;
-    // Get the field toolbar DOM root from the entity toolbar.
-    var fieldToolbarRoot = entityToolbarView.getToolbarRoot();
-    // Create in-place editor.
-    var editorName = fieldModel.get('metadata').editor;
-    var editorModel = new Drupal.edit.EditorModel();
-    var editorView = new Drupal.edit.editors[editorName]({
-      el: $(fieldModel.get('el')),
-      model: editorModel,
-      fieldModel: fieldModel
-    });
-
-    // Create in-place editor's toolbar for this field — stored inside the
-    // entity toolbar, the entity toolbar will position itself appropriately
-    // above (or below) the edited element.
-    var toolbarView = new Drupal.edit.FieldToolbarView({
-      el: fieldToolbarRoot,
-      model: fieldModel,
-      $editedElement: $(editorView.getEditedElement()),
-      editorView: editorView,
-      entityModel: entityModel
-    });
-
-    // Create decoration for edited element: padding if necessary, sets classes
-    // on the element to style it according to the current state.
-    var decorationView = new Drupal.edit.FieldDecorationView({
-      el: $(editorView.getEditedElement()),
-      model: fieldModel,
-      editorView: editorView
-    });
-
-    // Track these three views in FieldModel so that we can tear them down
-    // correctly.
-    fieldModel.editorView = editorView;
-    fieldModel.toolbarView = toolbarView;
-    fieldModel.decorationView = decorationView;
-  },
-
-  /**
-   * Tears down the in-place editor for the given field.
-   *
-   * Must happen after the fieldModel's state is changed to 'inactive'.
-   *
-   * @param Drupal.edit.FieldModel fieldModel
-   *   The field for which an in-place editor must be torn down.
-   */
-  teardownEditor: function (fieldModel) {
-    // Early-return if this field was not yet decorated.
-    if (fieldModel.editorView === undefined) {
-      return;
-    }
+      return accept;
+    },
+
+    /**
+     * Sets up the in-place editor for the given field.
+     *
+     * Must happen before the fieldModel's state is changed to 'candidate'.
+     *
+     * @param Drupal.edit.FieldModel fieldModel
+     *   The field for which an in-place editor must be set up.
+     */
+    setupEditor: function (fieldModel) {
+      // Get the corresponding entity toolbar.
+      var entityModel = fieldModel.get('entity');
+      var entityToolbarView = entityModel.toolbarView;
+      // Get the field toolbar DOM root from the entity toolbar.
+      var fieldToolbarRoot = entityToolbarView.getToolbarRoot();
+      // Create in-place editor.
+      var editorName = fieldModel.get('metadata').editor;
+      var editorModel = new Drupal.edit.EditorModel();
+      var editorView = new Drupal.edit.editors[editorName]({
+        el: $(fieldModel.get('el')),
+        model: editorModel,
+        fieldModel: fieldModel
+      });
+
+      // Create in-place editor's toolbar for this field — stored inside the
+      // entity toolbar, the entity toolbar will position itself appropriately
+      // above (or below) the edited element.
+      var toolbarView = new Drupal.edit.FieldToolbarView({
+        el: fieldToolbarRoot,
+        model: fieldModel,
+        $editedElement: $(editorView.getEditedElement()),
+        editorView: editorView,
+        entityModel: entityModel
+      });
 
-    // Unbind event handlers; remove toolbar element; delete toolbar view.
-    fieldModel.toolbarView.remove();
-    delete fieldModel.toolbarView;
-
-    // Unbind event handlers; delete decoration view. Don't remove the element
-    // because that would remove the field itself.
-    fieldModel.decorationView.remove();
-    delete fieldModel.decorationView;
-
-    // Unbind event handlers; delete editor view. Don't remove the element
-    // because that would remove the field itself.
-    fieldModel.editorView.remove();
-    delete fieldModel.editorView;
-  },
-
-  /**
-   * Asks the user to confirm whether he wants to stop editing via a modal.
-   *
-   * @see acceptEditorStateChange()
-   */
-  confirmEntityDeactivation: function (entityModel) {
-    var that = this;
-    var discardDialog;
-
-    function closeDiscardDialog (action) {
-      discardDialog.close(action);
-      // The active modal has been removed.
-      that.model.set('activeModal', null);
-
-      // If the targetState is saving, the field must be saved, then the
-      // entity must be saved.
-      if (action === 'save') {
-        entityModel.set('state', 'committing', {confirmed : true});
+      // Create decoration for edited element: padding if necessary, sets classes
+      // on the element to style it according to the current state.
+      var decorationView = new Drupal.edit.FieldDecorationView({
+        el: $(editorView.getEditedElement()),
+        model: fieldModel,
+        editorView: editorView
+      });
+
+      // Track these three views in FieldModel so that we can tear them down
+      // correctly.
+      fieldModel.editorView = editorView;
+      fieldModel.toolbarView = toolbarView;
+      fieldModel.decorationView = decorationView;
+    },
+
+    /**
+     * Tears down the in-place editor for the given field.
+     *
+     * Must happen after the fieldModel's state is changed to 'inactive'.
+     *
+     * @param Drupal.edit.FieldModel fieldModel
+     *   The field for which an in-place editor must be torn down.
+     */
+    teardownEditor: function (fieldModel) {
+      // Early-return if this field was not yet decorated.
+      if (fieldModel.editorView === undefined) {
+        return;
       }
-      else {
-        entityModel.set('state', 'deactivating', {confirmed : true});
-        // Editing has been canceled and the changes will not be saved. Mark
-        // the page for reload if the entityModel declares that it requires
-        // a reload.
-        if (entityModel.get('reload')) {
-          reload = true;
-          entityModel.set('reload', false);
+
+      // Unbind event handlers; remove toolbar element; delete toolbar view.
+      fieldModel.toolbarView.remove();
+      delete fieldModel.toolbarView;
+
+      // Unbind event handlers; delete decoration view. Don't remove the element
+      // because that would remove the field itself.
+      fieldModel.decorationView.remove();
+      delete fieldModel.decorationView;
+
+      // Unbind event handlers; delete editor view. Don't remove the element
+      // because that would remove the field itself.
+      fieldModel.editorView.remove();
+      delete fieldModel.editorView;
+    },
+
+    /**
+     * Asks the user to confirm whether he wants to stop editing via a modal.
+     *
+     * @see acceptEditorStateChange()
+     */
+    confirmEntityDeactivation: function (entityModel) {
+      var that = this;
+      var discardDialog;
+
+      function closeDiscardDialog(action) {
+        discardDialog.close(action);
+        // The active modal has been removed.
+        that.model.set('activeModal', null);
+
+        // If the targetState is saving, the field must be saved, then the
+        // entity must be saved.
+        if (action === 'save') {
+          entityModel.set('state', 'committing', {confirmed: true});
+        }
+        else {
+          entityModel.set('state', 'deactivating', {confirmed: true});
+          // Editing has been canceled and the changes will not be saved. Mark
+          // the page for reload if the entityModel declares that it requires
+          // a reload.
+          if (entityModel.get('reload')) {
+            reload = true;
+            entityModel.set('reload', false);
+          }
         }
       }
-    }
 
-    // Only instantiate if there isn't a modal instance visible yet.
-    if (!this.model.get('activeModal')) {
-      var $unsavedChanges = $('<div>' + Drupal.t('You have unsaved changes') + '</div>');
-      discardDialog = Drupal.dialog($unsavedChanges.get(0), {
-        title: Drupal.t('Discard changes?'),
-        dialogClass: 'edit-discard-modal',
-        resizable: false,
-        buttons: [
-          {
-            text: Drupal.t('Save'),
-            click: function() {
-              closeDiscardDialog('save');
+      // Only instantiate if there isn't a modal instance visible yet.
+      if (!this.model.get('activeModal')) {
+        var $unsavedChanges = $('<div>' + Drupal.t('You have unsaved changes') + '</div>');
+        discardDialog = Drupal.dialog($unsavedChanges.get(0), {
+          title: Drupal.t('Discard changes?'),
+          dialogClass: 'edit-discard-modal',
+          resizable: false,
+          buttons: [
+            {
+              text: Drupal.t('Save'),
+              click: function () {
+                closeDiscardDialog('save');
+              }
+            },
+            {
+              text: Drupal.t('Discard changes'),
+              click: function () {
+                closeDiscardDialog('discard');
+              }
             }
+          ],
+          // Prevent this modal from being closed without the user making a choice
+          // as per http://stackoverflow.com/a/5438771.
+          closeOnEscape: false,
+          create: function () {
+            $(this).parent().find('.ui-dialog-titlebar-close').remove();
           },
-          {
-            text: Drupal.t('Discard changes'),
-            click: function() {
-              closeDiscardDialog('discard');
-            }
+          beforeClose: false,
+          close: function (event) {
+            // Automatically destroy the DOM element that was used for the dialog.
+            $(event.target).remove();
           }
-        ],
-        // Prevent this modal from being closed without the user making a choice
-        // as per http://stackoverflow.com/a/5438771.
-        closeOnEscape: false,
-        create: function () {
-          $(this).parent().find('.ui-dialog-titlebar-close').remove();
-        },
-        beforeClose: false,
-        close: function (event) {
-          // Automatically destroy the DOM element that was used for the dialog.
-          $(event.target).remove();
-        }
-      });
-      this.model.set('activeModal', discardDialog);
-
-      discardDialog.showModal();
-    }
-  },
-
-  /**
-   * Reacts to field state changes; tracks global state.
-   *
-   * @param Drupal.edit.FieldModel fieldModel
-   * @param String state
-   *   The state of the associated field. One of Drupal.edit.FieldModel.states.
-   */
-  editorStateChange: function (fieldModel, state) {
-    var from = fieldModel.previous('state');
-    var to = state;
-
-    // Keep track of the highlighted field in the global state.
-    if (_.indexOf(this.singleFieldStates, to) !== -1 && this.model.get('highlightedField') !== fieldModel) {
-      this.model.set('highlightedField', fieldModel);
-    }
-    else if (this.model.get('highlightedField') === fieldModel && to === 'candidate') {
-      this.model.set('highlightedField', null);
-    }
+        });
+        this.model.set('activeModal', discardDialog);
 
-    // Keep track of the active field in the global state.
-    if (_.indexOf(this.activeFieldStates, to) !== -1 && this.model.get('activeField') !== fieldModel) {
-      this.model.set('activeField', fieldModel);
-    }
-    else if (this.model.get('activeField') === fieldModel && to === 'candidate') {
-      // Discarded if it transitions from a changed state to 'candidate'.
-      if (from === 'changed' || from === 'invalid') {
-        fieldModel.editorView.revert();
+        discardDialog.showModal();
+      }
+    },
+
+    /**
+     * Reacts to field state changes; tracks global state.
+     *
+     * @param Drupal.edit.FieldModel fieldModel
+     * @param String state
+     *   The state of the associated field. One of Drupal.edit.FieldModel.states.
+     */
+    editorStateChange: function (fieldModel, state) {
+      var from = fieldModel.previous('state');
+      var to = state;
+
+      // Keep track of the highlighted field in the global state.
+      if (_.indexOf(this.singleFieldStates, to) !== -1 && this.model.get('highlightedField') !== fieldModel) {
+        this.model.set('highlightedField', fieldModel);
+      }
+      else if (this.model.get('highlightedField') === fieldModel && to === 'candidate') {
+        this.model.set('highlightedField', null);
       }
-      this.model.set('activeField', null);
-    }
-  },
-
-  /**
-   * Render an updated field (a field whose 'html' attribute changed).
-   *
-   * @param Drupal.edit.FieldModel fieldModel
-   *   The FieldModel whose 'html' attribute changed.
-   * @param String html
-   *   The updated 'html' attribute.
-   * @param Object options
-   *   An object with the following keys:
-   *   - Boolean propagation: whether this change to the 'html' attribute
-   *     occurred because of the propagation of changes to another instance of
-   *     this field.
-   */
-  renderUpdatedField: function (fieldModel, html, options) {
-    // Get data necessary to rerender property before it is unavailable.
-    var $fieldWrapper = $(fieldModel.get('el'));
-    var $context = $fieldWrapper.parent();
-
-    var renderField = function () {
-      // Destroy the field model; this will cause all attached views to be
-      // destroyed too, and removal from all collections in which it exists.
-      fieldModel.destroy();
-
-      // Replace the old content with the new content.
-      $fieldWrapper.replaceWith(html);
-
-      // Attach behaviors again to the modified piece of HTML; this will
-      // create a new field model and call rerenderedFieldToCandidate() with
-      // it.
-      Drupal.attachBehaviors($context.get(0));
-    };
-
-    // When propagating the changes of another instance of this field, this
-    // field is not being actively edited and hence no state changes are
-    // necessary. So: only update the state of this field when the rerendering
-    // of this field happens not because of propagation, but because it is
-    // being edited itself.
-    if (!options.propagation) {
-      // Deferred because renderUpdatedField is reacting to a field model change
-      // event, and we want to make sure that event fully propagates before
-      // making another change to the same model.
-      _.defer(function () {
-        // First set the state to 'candidate', to allow all attached views to
-        // clean up all their "active state"-related changes.
-        fieldModel.set('state', 'candidate');
 
-        // Similarly, the above .set() call's change event must fully propagate
-        // before calling it again.
+      // Keep track of the active field in the global state.
+      if (_.indexOf(this.activeFieldStates, to) !== -1 && this.model.get('activeField') !== fieldModel) {
+        this.model.set('activeField', fieldModel);
+      }
+      else if (this.model.get('activeField') === fieldModel && to === 'candidate') {
+        // Discarded if it transitions from a changed state to 'candidate'.
+        if (from === 'changed' || from === 'invalid') {
+          fieldModel.editorView.revert();
+        }
+        this.model.set('activeField', null);
+      }
+    },
+
+    /**
+     * Render an updated field (a field whose 'html' attribute changed).
+     *
+     * @param Drupal.edit.FieldModel fieldModel
+     *   The FieldModel whose 'html' attribute changed.
+     * @param String html
+     *   The updated 'html' attribute.
+     * @param Object options
+     *   An object with the following keys:
+     *   - Boolean propagation: whether this change to the 'html' attribute
+     *     occurred because of the propagation of changes to another instance of
+     *     this field.
+     */
+    renderUpdatedField: function (fieldModel, html, options) {
+      // Get data necessary to rerender property before it is unavailable.
+      var $fieldWrapper = $(fieldModel.get('el'));
+      var $context = $fieldWrapper.parent();
+
+      var renderField = function () {
+        // Destroy the field model; this will cause all attached views to be
+        // destroyed too, and removal from all collections in which it exists.
+        fieldModel.destroy();
+
+        // Replace the old content with the new content.
+        $fieldWrapper.replaceWith(html);
+
+        // Attach behaviors again to the modified piece of HTML; this will
+        // create a new field model and call rerenderedFieldToCandidate() with
+        // it.
+        Drupal.attachBehaviors($context.get(0));
+      };
+
+      // When propagating the changes of another instance of this field, this
+      // field is not being actively edited and hence no state changes are
+      // necessary. So: only update the state of this field when the rerendering
+      // of this field happens not because of propagation, but because it is
+      // being edited itself.
+      if (!options.propagation) {
+        // Deferred because renderUpdatedField is reacting to a field model change
+        // event, and we want to make sure that event fully propagates before
+        // making another change to the same model.
         _.defer(function () {
-          // Set the field's state to 'inactive', to enable the updating of its
-          // DOM value.
-          fieldModel.set('state', 'inactive', { reason: 'rerender' });
-
-          renderField();
+          // First set the state to 'candidate', to allow all attached views to
+          // clean up all their "active state"-related changes.
+          fieldModel.set('state', 'candidate');
+
+          // Similarly, the above .set() call's change event must fully propagate
+          // before calling it again.
+          _.defer(function () {
+            // Set the field's state to 'inactive', to enable the updating of its
+            // DOM value.
+            fieldModel.set('state', 'inactive', { reason: 'rerender' });
+
+            renderField();
+          });
         });
-      });
-    }
-    else {
-      renderField();
-    }
-  },
-
-  /**
-   * Propagates the changes to an updated field to all instances of that field.
-   *
-   * @param Drupal.edit.FieldModel updatedField
-   *   The FieldModel whose 'html' attribute changed.
-   * @param String html
-   *   The updated 'html' attribute.
-   * @param Object options
-   *   An object with the following keys:
-   *   - Boolean propagation: whether this change to the 'html' attribute
-   *     occurred because of the propagation of changes to another instance of
-   *     this field.
-   *
-   * @see Drupal.edit.AppView.renderUpdatedField()
-   */
-  propagateUpdatedField: function (updatedField, html, options) {
-    // Don't propagate field updates that themselves were caused by propagation.
-    if (options.propagation) {
-      return;
-    }
+      }
+      else {
+        renderField();
+      }
+    },
+
+    /**
+     * Propagates the changes to an updated field to all instances of that field.
+     *
+     * @param Drupal.edit.FieldModel updatedField
+     *   The FieldModel whose 'html' attribute changed.
+     * @param String html
+     *   The updated 'html' attribute.
+     * @param Object options
+     *   An object with the following keys:
+     *   - Boolean propagation: whether this change to the 'html' attribute
+     *     occurred because of the propagation of changes to another instance of
+     *     this field.
+     *
+     * @see Drupal.edit.AppView.renderUpdatedField()
+     */
+    propagateUpdatedField: function (updatedField, html, options) {
+      // Don't propagate field updates that themselves were caused by propagation.
+      if (options.propagation) {
+        return;
+      }
 
-    var htmlForOtherViewModes = updatedField.get('htmlForOtherViewModes');
-    Drupal.edit.collections.fields
-      // Find all instances of fields that display the same logical field (same
-      // entity, same field, just a different instance and maybe a different
-      // view mode).
-      .where({ logicalFieldID: updatedField.get('logicalFieldID') })
-      .forEach(function (field) {
-        // Ignore the field that was already updated.
-        if (field === updatedField) {
-          return;
-        }
-        // If this other instance of the field has the same view mode, we can
-        // update it easily.
-        else if (field.getViewMode() === updatedField.getViewMode()) {
-          field.set('html', updatedField.get('html'));
-        }
-        // If this other instance of the field has a different view mode, and
-        // that is one of the view modes for which a re-rendered version is
-        // available (and that should be the case unless this field was only
-        // added to the page after editing of the updated field began), then use
-        // that view mode's re-rendered version.
-        else {
-          if (field.getViewMode() in htmlForOtherViewModes) {
-            field.set('html', htmlForOtherViewModes[field.getViewMode()], { propagation: true });
+      var htmlForOtherViewModes = updatedField.get('htmlForOtherViewModes');
+      Drupal.edit.collections.fields
+        // Find all instances of fields that display the same logical field (same
+        // entity, same field, just a different instance and maybe a different
+        // view mode).
+        .where({ logicalFieldID: updatedField.get('logicalFieldID') })
+        .forEach(function (field) {
+          // Ignore the field that was already updated.
+          if (field === updatedField) {
+            return;
           }
-        }
-      });
-  },
-
-  /**
-   * If the new in-place editable field is for the entity that's currently
-   * being edited, then transition it to the 'candidate' state.
-   *
-   * This happens when a field was modified, saved and hence rerendered.
-   *
-   * @param Drupal.edit.FieldModel fieldModel
-   *   A field that was just added to the collection of fields.
-   */
-  rerenderedFieldToCandidate: function (fieldModel) {
-    var activeEntity = Drupal.edit.collections.entities.findWhere({isActive: true});
-
-    // Early-return if there is no active entity.
-    if (!activeEntity) {
-      return;
-    }
+          // If this other instance of the field has the same view mode, we can
+          // update it easily.
+          else if (field.getViewMode() === updatedField.getViewMode()) {
+            field.set('html', updatedField.get('html'));
+          }
+          // If this other instance of the field has a different view mode, and
+          // that is one of the view modes for which a re-rendered version is
+          // available (and that should be the case unless this field was only
+          // added to the page after editing of the updated field began), then use
+          // that view mode's re-rendered version.
+          else {
+            if (field.getViewMode() in htmlForOtherViewModes) {
+              field.set('html', htmlForOtherViewModes[field.getViewMode()], { propagation: true });
+            }
+          }
+        });
+    },
+
+    /**
+     * If the new in-place editable field is for the entity that's currently
+     * being edited, then transition it to the 'candidate' state.
+     *
+     * This happens when a field was modified, saved and hence rerendered.
+     *
+     * @param Drupal.edit.FieldModel fieldModel
+     *   A field that was just added to the collection of fields.
+     */
+    rerenderedFieldToCandidate: function (fieldModel) {
+      var activeEntity = Drupal.edit.collections.entities.findWhere({isActive: true});
+
+      // Early-return if there is no active entity.
+      if (!activeEntity) {
+        return;
+      }
 
-    // If the field's entity is the active entity, make it a candidate.
-    if (fieldModel.get('entity') === activeEntity) {
-      this.setupEditor(fieldModel);
-      fieldModel.set('state', 'candidate');
-    }
-  },
-
-  /**
-   * EntityModel Collection change handler, called on change:isActive, enforces
-   * a single active entity.
-   *
-   * @param Drupal.edit.EntityModel
-   *   The entityModel instance whose active state has changed.
-   */
-  enforceSingleActiveEntity: function (changedEntityModel) {
-    // When an entity is deactivated, we don't need to enforce anything.
-    if (changedEntityModel.get('isActive') === false) {
-      return;
-    }
+      // If the field's entity is the active entity, make it a candidate.
+      if (fieldModel.get('entity') === activeEntity) {
+        this.setupEditor(fieldModel);
+        fieldModel.set('state', 'candidate');
+      }
+    },
+
+    /**
+     * EntityModel Collection change handler, called on change:isActive, enforces
+     * a single active entity.
+     *
+     * @param Drupal.edit.EntityModel
+     *   The entityModel instance whose active state has changed.
+     */
+    enforceSingleActiveEntity: function (changedEntityModel) {
+      // When an entity is deactivated, we don't need to enforce anything.
+      if (changedEntityModel.get('isActive') === false) {
+        return;
+      }
 
-    // This entity was activated; deactivate all other entities.
-    changedEntityModel.collection.chain()
-      .filter(function (entityModel) {
-        return entityModel.get('isActive') === true && entityModel !== changedEntityModel;
-      })
-      .each(function (entityModel) {
-        entityModel.set('state', 'deactivating');
-      });
-  }
+      // This entity was activated; deactivate all other entities.
+      changedEntityModel.collection.chain()
+        .filter(function (entityModel) {
+          return entityModel.get('isActive') === true && entityModel !== changedEntityModel;
+        })
+        .each(function (entityModel) {
+          entityModel.set('state', 'deactivating');
+        });
+    }
 
-});
+  });
 
 }(jQuery, _, Backbone, Drupal));
diff --git a/core/modules/edit/js/views/ContextualLinkView.js b/core/modules/edit/js/views/ContextualLinkView.js
index 65a6077..6c21348 100644
--- a/core/modules/edit/js/views/ContextualLinkView.js
+++ b/core/modules/edit/js/views/ContextualLinkView.js
@@ -5,55 +5,55 @@
 
 (function ($, Backbone, Drupal) {
 
-"use strict";
+  "use strict";
 
-Drupal.edit.ContextualLinkView = Backbone.View.extend({
+  Drupal.edit.ContextualLinkView = Backbone.View.extend({
 
-   events: function () {
-    // Prevents delay and simulated mouse events.
-    function touchEndToClick (event) {
-      event.preventDefault();
-      event.target.click();
-    }
-    return {
-      'click a': function (event) {
+    events: function () {
+      // Prevents delay and simulated mouse events.
+      function touchEndToClick(event) {
         event.preventDefault();
-        this.model.set('state', 'launching');
-      },
-      'touchEnd a': touchEndToClick
-    };
-  },
-
-  /**
-   * {@inheritdoc}
-   *
-   * @param Object options
-   *   An object with the following keys:
-   *   - Drupal.edit.EntityModel model: the associated entity's model
-   *   - Drupal.edit.AppModel appModel: the application state model
-   *   - strings: the strings for the "Quick edit" link
-   */
-  initialize: function (options) {
-    // Insert the text of the quick edit toggle.
-    this.$el.find('a').text(options.strings.quickEdit);
-    // Initial render.
-    this.render();
-    // Re-render whenever this entity's isActive attribute changes.
-    this.listenTo(this.model, 'change:isActive', this.render);
-  },
-
-  /**
-   * {@inheritdoc}
-   */
-  render: function (entityModel, isActive) {
-    this.$el.find('a').attr('aria-pressed', isActive);
-
-    // Hides the contextual links if an in-place editor is active.
-    this.$el.closest('.contextual').toggle(!isActive);
-
-    return this;
-  }
-
-});
+        event.target.click();
+      }
+      return {
+        'click a': function (event) {
+          event.preventDefault();
+          this.model.set('state', 'launching');
+        },
+        'touchEnd a': touchEndToClick
+      };
+    },
+
+    /**
+     * {@inheritdoc}
+     *
+     * @param Object options
+     *   An object with the following keys:
+     *   - Drupal.edit.EntityModel model: the associated entity's model
+     *   - Drupal.edit.AppModel appModel: the application state model
+     *   - strings: the strings for the "Quick edit" link
+     */
+    initialize: function (options) {
+      // Insert the text of the quick edit toggle.
+      this.$el.find('a').text(options.strings.quickEdit);
+      // Initial render.
+      this.render();
+      // Re-render whenever this entity's isActive attribute changes.
+      this.listenTo(this.model, 'change:isActive', this.render);
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    render: function (entityModel, isActive) {
+      this.$el.find('a').attr('aria-pressed', isActive);
+
+      // Hides the contextual links if an in-place editor is active.
+      this.$el.closest('.contextual').toggle(!isActive);
+
+      return this;
+    }
+
+  });
 
 })(jQuery, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/EditorView.js b/core/modules/edit/js/views/EditorView.js
index a00432d..5108fcf 100644
--- a/core/modules/edit/js/views/EditorView.js
+++ b/core/modules/edit/js/views/EditorView.js
@@ -5,288 +5,288 @@
 
 (function ($, Backbone, Drupal) {
 
-"use strict";
-
-/**
- * A base implementation that outlines the structure for in-place editors.
- *
- * Specific in-place editor implementations should subclass (extend) this View
- * and override whichever method they deem necessary to override.
- *
- * Look at Drupal.edit.editors.form and Drupal.edit.editors.plain_text for
- * examples.
- *
- * @see Drupal.edit.EditorModel
- */
-Drupal.edit.EditorView = Backbone.View.extend({
+  "use strict";
 
   /**
-   * {@inheritdoc}
+   * A base implementation that outlines the structure for in-place editors.
    *
-   * Typically you would want to override this method to set the originalValue
-   * attribute in the FieldModel to such a value that your in-place editor can
-   * revert to the original value when necessary.
+   * Specific in-place editor implementations should subclass (extend) this View
+   * and override whichever method they deem necessary to override.
    *
-   * If you override this method, you should call this method (the parent
-   * class' initialize()) first, like this:
-   *   Drupal.edit.EditorView.prototype.initialize.call(this, options);
+   * Look at Drupal.edit.editors.form and Drupal.edit.editors.plain_text for
+   * examples.
    *
-   * For an example, @see Drupal.edit.editors.plain_text.
-   *
-   * @param Object options
-   *   An object with the following keys:
-   *   - Drupal.edit.EditorModel model: the in-place editor state model
-   *   - Drupal.edit.FieldModel fieldModel: the field model
+   * @see Drupal.edit.EditorModel
    */
-  initialize: function (options) {
-    this.fieldModel = options.fieldModel;
-    this.listenTo(this.fieldModel, 'change:state', this.stateChange);
-  },
+  Drupal.edit.EditorView = Backbone.View.extend({
 
-  /**
-   * {@inheritdoc}
-   */
-  remove: function () {
-    // The el property is the field, which should not be removed. Remove the
-    // pointer to it, then call Backbone.View.prototype.remove().
-    this.setElement();
-    Backbone.View.prototype.remove.call(this);
-  },
+    /**
+     * {@inheritdoc}
+     *
+     * Typically you would want to override this method to set the originalValue
+     * attribute in the FieldModel to such a value that your in-place editor can
+     * revert to the original value when necessary.
+     *
+     * If you override this method, you should call this method (the parent
+     * class' initialize()) first, like this:
+     *   Drupal.edit.EditorView.prototype.initialize.call(this, options);
+     *
+     * For an example, @see Drupal.edit.editors.plain_text.
+     *
+     * @param Object options
+     *   An object with the following keys:
+     *   - Drupal.edit.EditorModel model: the in-place editor state model
+     *   - Drupal.edit.FieldModel fieldModel: the field model
+     */
+    initialize: function (options) {
+      this.fieldModel = options.fieldModel;
+      this.listenTo(this.fieldModel, 'change:state', this.stateChange);
+    },
 
-  /**
-   * Returns the edited element.
-   *
-   * For some single cardinality fields, it may be necessary or useful to
-   * not in-place edit (and hence decorate) the DOM element with the
-   * data-edit-field-id attribute (which is the field's wrapper), but a specific
-   * element within the field's wrapper.
-   * e.g. using a WYSIWYG editor on a body field should happen on the DOM
-   * element containing the text itself, not on the field wrapper.
-   *
-   * For example, @see Drupal.edit.editors.plain_text.
-   *
-   * @return jQuery
-   *   A jQuery-wrapped DOM element.
-   */
-  getEditedElement: function () {
-    return this.$el;
-  },
+    /**
+     * {@inheritdoc}
+     */
+    remove: function () {
+      // The el property is the field, which should not be removed. Remove the
+      // pointer to it, then call Backbone.View.prototype.remove().
+      this.setElement();
+      Backbone.View.prototype.remove.call(this);
+    },
 
-  /**
-   * Returns 3 Edit UI settings that depend on the in-place editor:
-   *  - Boolean padding: indicates whether padding should be applied to the
-   *    edited element, to guarantee legibility of text.
-   *  - Boolean unifiedToolbar: provides the in-place editor with the ability to
-   *    insert its own toolbar UI into Edit's tightly integrated toolbar.
-   *  - Boolean fullWidthToolbar: indicates whether Edit's tightly integrated
-   *    toolbar should consume the full width of the element, rather than being
-   *    just long enough to accommodate a label.
-   */
-  getEditUISettings: function () {
-    return { padding: false, unifiedToolbar: false, fullWidthToolbar: false, popup: false };
-  },
-
-  /**
-   * Determines the actions to take given a change of state.
-   *
-   * @param Drupal.edit.FieldModel fieldModel
-   * @param String state
-   *   The state of the associated field. One of Drupal.edit.FieldModel.states.
-   */
-  stateChange: function (fieldModel, state) {
-    var from = fieldModel.previous('state');
-    var to = state;
-    switch (to) {
-      case 'inactive':
-        // An in-place editor view will not yet exist in this state, hence
-        // this will never be reached. Listed for sake of completeness.
-        break;
-      case 'candidate':
-        // Nothing to do for the typical in-place editor: it should not be
-        // visible yet.
-
-        // Except when we come from the 'invalid' state, then we clean up.
-        if (from === 'invalid') {
-          this.removeValidationErrors();
-        }
-        break;
-      case 'highlighted':
-        // Nothing to do for the typical in-place editor: it should not be
-        // visible yet.
-        break;
-      case 'activating':
-        // The user has indicated he wants to do in-place editing: if
-        // something needs to be loaded (CSS/JavaScript/server data/…), then
-        // do so at this stage, and once the in-place editor is ready,
-        // set the 'active' state.
-        // A "loading" indicator will be shown in the UI for as long as the
-        // field remains in this state.
-        var loadDependencies = function (callback) {
-          // Do the loading here.
-          callback();
-        };
-        loadDependencies(function () {
-          fieldModel.set('state', 'active');
-        });
-        break;
-      case 'active':
-        // The user can now actually use the in-place editor.
-        break;
-      case 'changed':
-        // Nothing to do for the typical in-place editor. The UI will show an
-        // indicator that the field has changed.
-        break;
-      case 'saving':
-        // When the user has indicated he wants to save his changes to this
-        // field, this state will be entered.
-        // If the previous saving attempt resulted in validation errors, the
-        // previous state will be 'invalid'. Clean up those validation errors
-        // while the user is saving.
-        if (from === 'invalid') {
-          this.removeValidationErrors();
-        }
-        this.save();
-        break;
-      case 'saved':
-        // Nothing to do for the typical in-place editor. Immediately after
-        // being saved, a field will go to the 'candidate' state, where it
-        // should no longer be visible (after all, the field will then again
-        // just be a *candidate* to be in-place edited).
-        break;
-      case 'invalid':
-        // The modified field value was attempted to be saved, but there were
-        // validation errors.
-        this.showValidationErrors();
-        break;
-    }
-  },
+    /**
+     * Returns the edited element.
+     *
+     * For some single cardinality fields, it may be necessary or useful to
+     * not in-place edit (and hence decorate) the DOM element with the
+     * data-edit-field-id attribute (which is the field's wrapper), but a specific
+     * element within the field's wrapper.
+     * e.g. using a WYSIWYG editor on a body field should happen on the DOM
+     * element containing the text itself, not on the field wrapper.
+     *
+     * For example, @see Drupal.edit.editors.plain_text.
+     *
+     * @return jQuery
+     *   A jQuery-wrapped DOM element.
+     */
+    getEditedElement: function () {
+      return this.$el;
+    },
 
-  /**
-   * Reverts the modified value back to the original value (before editing
-   * started).
-   */
-  revert: function () {
-    // A no-op by default; each editor should implement reverting itself.
+    /**
+     * Returns 3 Edit UI settings that depend on the in-place editor:
+     *  - Boolean padding: indicates whether padding should be applied to the
+     *    edited element, to guarantee legibility of text.
+     *  - Boolean unifiedToolbar: provides the in-place editor with the ability to
+     *    insert its own toolbar UI into Edit's tightly integrated toolbar.
+     *  - Boolean fullWidthToolbar: indicates whether Edit's tightly integrated
+     *    toolbar should consume the full width of the element, rather than being
+     *    just long enough to accommodate a label.
+     */
+    getEditUISettings: function () {
+      return { padding: false, unifiedToolbar: false, fullWidthToolbar: false, popup: false };
+    },
 
-    // Note that if the in-place editor does not cause the FieldModel's
-    // element to be modified, then nothing needs to happen.
-  },
+    /**
+     * Determines the actions to take given a change of state.
+     *
+     * @param Drupal.edit.FieldModel fieldModel
+     * @param String state
+     *   The state of the associated field. One of Drupal.edit.FieldModel.states.
+     */
+    stateChange: function (fieldModel, state) {
+      var from = fieldModel.previous('state');
+      var to = state;
+      switch (to) {
+        case 'inactive':
+          // An in-place editor view will not yet exist in this state, hence
+          // this will never be reached. Listed for sake of completeness.
+          break;
+        case 'candidate':
+          // Nothing to do for the typical in-place editor: it should not be
+          // visible yet.
 
-  /**
-   * Saves the modified value in the in-place editor for this field.
-   */
-  save: function () {
-    var fieldModel = this.fieldModel;
-    var editorModel = this.model;
-    var backstageId = 'edit_backstage-' + this.fieldModel.id.replace(/[\/\[\]\_\s]/g, '-');
+          // Except when we come from the 'invalid' state, then we clean up.
+          if (from === 'invalid') {
+            this.removeValidationErrors();
+          }
+          break;
+        case 'highlighted':
+          // Nothing to do for the typical in-place editor: it should not be
+          // visible yet.
+          break;
+        case 'activating':
+          // The user has indicated he wants to do in-place editing: if
+          // something needs to be loaded (CSS/JavaScript/server data/…), then
+          // do so at this stage, and once the in-place editor is ready,
+          // set the 'active' state.
+          // A "loading" indicator will be shown in the UI for as long as the
+          // field remains in this state.
+          var loadDependencies = function (callback) {
+            // Do the loading here.
+            callback();
+          };
+          loadDependencies(function () {
+            fieldModel.set('state', 'active');
+          });
+          break;
+        case 'active':
+          // The user can now actually use the in-place editor.
+          break;
+        case 'changed':
+          // Nothing to do for the typical in-place editor. The UI will show an
+          // indicator that the field has changed.
+          break;
+        case 'saving':
+          // When the user has indicated he wants to save his changes to this
+          // field, this state will be entered.
+          // If the previous saving attempt resulted in validation errors, the
+          // previous state will be 'invalid'. Clean up those validation errors
+          // while the user is saving.
+          if (from === 'invalid') {
+            this.removeValidationErrors();
+          }
+          this.save();
+          break;
+        case 'saved':
+          // Nothing to do for the typical in-place editor. Immediately after
+          // being saved, a field will go to the 'candidate' state, where it
+          // should no longer be visible (after all, the field will then again
+          // just be a *candidate* to be in-place edited).
+          break;
+        case 'invalid':
+          // The modified field value was attempted to be saved, but there were
+          // validation errors.
+          this.showValidationErrors();
+          break;
+      }
+    },
 
-    function fillAndSubmitForm (value) {
-      var $form = $('#' + backstageId).find('form');
-      // Fill in the value in any <input> that isn't hidden or a submit
-      // button.
-      $form.find(':input[type!="hidden"][type!="submit"]:not(select)')
-        // Don't mess with the node summary.
-        .not('[name$="\\[summary\\]"]').val(value);
-      // Submit the form.
-      $form.find('.edit-form-submit').trigger('click.edit');
-    }
+    /**
+     * Reverts the modified value back to the original value (before editing
+     * started).
+     */
+    revert: function () {
+      // A no-op by default; each editor should implement reverting itself.
 
-    var formOptions = {
-      fieldID: this.fieldModel.get('fieldID'),
-      $el: this.$el,
-      nocssjs: true,
-      other_view_modes: fieldModel.findOtherViewModes(),
-      // Reset an existing entry for this entity in the TempStore (if any) when
-      // saving the field. Logically speaking, this should happen in a separate
-      // request because this is an entity-level operation, not a field-level
-      // operation. But that would require an additional request, that might not
-      // even be necessary: it is only when a user saves a first changed field
-      // for an entity that this needs to happen: precisely now!
-      reset: !this.fieldModel.get('entity').get('inTempStore')
-    };
+      // Note that if the in-place editor does not cause the FieldModel's
+      // element to be modified, then nothing needs to happen.
+    },
 
-    var self = this;
-    Drupal.edit.util.form.load(formOptions, function (form, ajax) {
-      // Create a backstage area for storing forms that are hidden from view
-      // (hence "backstage" — since the editing doesn't happen in the form, it
-      // happens "directly" in the content, the form is only used for saving).
-      var $backstage = $(Drupal.theme('editBackstage', { id: backstageId })).appendTo('body');
-      // Hidden forms are stuffed into the backstage container for this field.
-      var $form = $(form).appendTo($backstage);
-      // Disable the browser's HTML5 validation; we only care about server-
-      // side validation. (Not disabling this will actually cause problems
-      // because browsers don't like to set HTML5 validation errors on hidden
-      // forms.)
-      $form.prop('novalidate', true);
-      var $submit = $form.find('.edit-form-submit');
-      self.formSaveAjax = Drupal.edit.util.form.ajaxifySaving(formOptions, $submit);
+    /**
+     * Saves the modified value in the in-place editor for this field.
+     */
+    save: function () {
+      var fieldModel = this.fieldModel;
+      var editorModel = this.model;
+      var backstageId = 'edit_backstage-' + this.fieldModel.id.replace(/[\/\[\]\_\s]/g, '-');
 
-      function removeHiddenForm () {
-        Drupal.edit.util.form.unajaxifySaving(self.formSaveAjax);
-        delete self.formSaveAjax;
-        $backstage.remove();
+      function fillAndSubmitForm(value) {
+        var $form = $('#' + backstageId).find('form');
+        // Fill in the value in any <input> that isn't hidden or a submit
+        // button.
+        $form.find(':input[type!="hidden"][type!="submit"]:not(select)')
+          // Don't mess with the node summary.
+          .not('[name$="\\[summary\\]"]').val(value);
+        // Submit the form.
+        $form.find('.edit-form-submit').trigger('click.edit');
       }
 
-      // Successfully saved.
-      self.formSaveAjax.commands.editFieldFormSaved = function (ajax, response, status) {
-        removeHiddenForm();
-        // First, transition the state to 'saved'.
-        fieldModel.set('state', 'saved');
-        // Second, set the 'htmlForOtherViewModes' attribute, so that when this
-        // field is rerendered, the change can be propagated to other instances of
-        // this field, which may be displayed in different view modes.
-        fieldModel.set('htmlForOtherViewModes', response.other_view_modes);
-        // Finally, set the 'html' attribute on the field model. This will cause
-        // the field to be rerendered.
-        fieldModel.set('html', response.data);
+      var formOptions = {
+        fieldID: this.fieldModel.get('fieldID'),
+        $el: this.$el,
+        nocssjs: true,
+        other_view_modes: fieldModel.findOtherViewModes(),
+        // Reset an existing entry for this entity in the TempStore (if any) when
+        // saving the field. Logically speaking, this should happen in a separate
+        // request because this is an entity-level operation, not a field-level
+        // operation. But that would require an additional request, that might not
+        // even be necessary: it is only when a user saves a first changed field
+        // for an entity that this needs to happen: precisely now!
+        reset: !this.fieldModel.get('entity').get('inTempStore')
       };
 
-      // Unsuccessfully saved; validation errors.
-      self.formSaveAjax.commands.editFieldFormValidationErrors = function (ajax, response, status) {
-        removeHiddenForm();
-        editorModel.set('validationErrors', response.data);
-        fieldModel.set('state', 'invalid');
-      };
+      var self = this;
+      Drupal.edit.util.form.load(formOptions, function (form, ajax) {
+        // Create a backstage area for storing forms that are hidden from view
+        // (hence "backstage" — since the editing doesn't happen in the form, it
+        // happens "directly" in the content, the form is only used for saving).
+        var $backstage = $(Drupal.theme('editBackstage', { id: backstageId })).appendTo('body');
+        // Hidden forms are stuffed into the backstage container for this field.
+        var $form = $(form).appendTo($backstage);
+        // Disable the browser's HTML5 validation; we only care about server-
+        // side validation. (Not disabling this will actually cause problems
+        // because browsers don't like to set HTML5 validation errors on hidden
+        // forms.)
+        $form.prop('novalidate', true);
+        var $submit = $form.find('.edit-form-submit');
+        self.formSaveAjax = Drupal.edit.util.form.ajaxifySaving(formOptions, $submit);
 
-      // The editFieldForm AJAX command is only called upon loading the form
-      // for the first time, and when there are validation errors in the form;
-      // Form API then marks which form items have errors. This is useful for
-      // the form-based in-place editor, but pointless for any other: the form
-      // itself won't be visible at all anyway! So, we just ignore it.
-      self.formSaveAjax.commands.editFieldForm = function () {};
+        function removeHiddenForm() {
+          Drupal.edit.util.form.unajaxifySaving(self.formSaveAjax);
+          delete self.formSaveAjax;
+          $backstage.remove();
+        }
 
-      fillAndSubmitForm(editorModel.get('currentValue'));
-    });
-  },
+        // Successfully saved.
+        self.formSaveAjax.commands.editFieldFormSaved = function (ajax, response, status) {
+          removeHiddenForm();
+          // First, transition the state to 'saved'.
+          fieldModel.set('state', 'saved');
+          // Second, set the 'htmlForOtherViewModes' attribute, so that when this
+          // field is rerendered, the change can be propagated to other instances of
+          // this field, which may be displayed in different view modes.
+          fieldModel.set('htmlForOtherViewModes', response.other_view_modes);
+          // Finally, set the 'html' attribute on the field model. This will cause
+          // the field to be rerendered.
+          fieldModel.set('html', response.data);
+        };
 
-  /**
-   * Shows validation error messages.
-   *
-   * Should be called when the state is changed to 'invalid'.
-   */
-  showValidationErrors: function () {
-    var $errors = $('<div class="edit-validation-errors"></div>')
-      .append(this.model.get('validationErrors'));
-    this.getEditedElement()
-      .addClass('edit-validation-error')
-      .after($errors);
-  },
+        // Unsuccessfully saved; validation errors.
+        self.formSaveAjax.commands.editFieldFormValidationErrors = function (ajax, response, status) {
+          removeHiddenForm();
+          editorModel.set('validationErrors', response.data);
+          fieldModel.set('state', 'invalid');
+        };
 
-  /**
-   * Cleans up validation error messages.
-   *
-   * Should be called when the state is changed to 'candidate' or 'saving'. In
-   * the case of the latter: the user has modified the value in the in-place
-   * editor again to attempt to save again. In the case of the latter: the
-   * invalid value was discarded.
-   */
-  removeValidationErrors: function () {
-    this.getEditedElement()
-      .removeClass('edit-validation-error')
-      .next('.edit-validation-errors')
-      .remove();
-  }
+        // The editFieldForm AJAX command is only called upon loading the form
+        // for the first time, and when there are validation errors in the form;
+        // Form API then marks which form items have errors. This is useful for
+        // the form-based in-place editor, but pointless for any other: the form
+        // itself won't be visible at all anyway! So, we just ignore it.
+        self.formSaveAjax.commands.editFieldForm = function () {};
+
+        fillAndSubmitForm(editorModel.get('currentValue'));
+      });
+    },
+
+    /**
+     * Shows validation error messages.
+     *
+     * Should be called when the state is changed to 'invalid'.
+     */
+    showValidationErrors: function () {
+      var $errors = $('<div class="edit-validation-errors"></div>')
+        .append(this.model.get('validationErrors'));
+      this.getEditedElement()
+        .addClass('edit-validation-error')
+        .after($errors);
+    },
+
+    /**
+     * Cleans up validation error messages.
+     *
+     * Should be called when the state is changed to 'candidate' or 'saving'. In
+     * the case of the latter: the user has modified the value in the in-place
+     * editor again to attempt to save again. In the case of the latter: the
+     * invalid value was discarded.
+     */
+    removeValidationErrors: function () {
+      this.getEditedElement()
+        .removeClass('edit-validation-error')
+        .next('.edit-validation-errors')
+        .remove();
+    }
 
-});
+  });
 
 }(jQuery, Backbone, Drupal));
diff --git a/core/modules/edit/js/views/EntityDecorationView.js b/core/modules/edit/js/views/EntityDecorationView.js
index ad107f9..309718f 100644
--- a/core/modules/edit/js/views/EntityDecorationView.js
+++ b/core/modules/edit/js/views/EntityDecorationView.js
@@ -5,34 +5,34 @@
 
 (function (Drupal, $, Backbone) {
 
-"use strict";
-
-Drupal.edit.EntityDecorationView = Backbone.View.extend({
-
-  /**
-   * {@inheritdoc}
-   *
-   * Associated with the DOM root node of an editable entity.
-   */
-  initialize: function () {
-    this.listenTo(this.model, 'change', this.render);
-  },
-
-  /**
-   * {@inheritdoc}
-   */
-  render: function () {
-    this.$el.toggleClass('edit-entity-active', this.model.get('isActive'));
-  },
-
-  /**
-   * {@inheritdoc}
-   */
-  remove: function () {
-    this.setElement(null);
-    Backbone.View.prototype.remove.call(this);
-  }
-
-});
+  "use strict";
+
+  Drupal.edit.EntityDecorationView = Backbone.View.extend({
+
+    /**
+     * {@inheritdoc}
+     *
+     * Associated with the DOM root node of an editable entity.
+     */
+    initialize: function () {
+      this.listenTo(this.model, 'change', this.render);
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    render: function () {
+      this.$el.toggleClass('edit-entity-active', this.model.get('isActive'));
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    remove: function () {
+      this.setElement(null);
+      Backbone.View.prototype.remove.call(this);
+    }
+
+  });
 
 }(Drupal, jQuery, Backbone));
diff --git a/core/modules/edit/js/views/EntityToolbarView.js b/core/modules/edit/js/views/EntityToolbarView.js
index 889335a..064c6cd 100644
--- a/core/modules/edit/js/views/EntityToolbarView.js
+++ b/core/modules/edit/js/views/EntityToolbarView.js
@@ -5,470 +5,470 @@
 
 (function ($, _, Backbone, Drupal, debounce) {
 
-"use strict";
-
-Drupal.edit.EntityToolbarView = Backbone.View.extend({
-
-  _fieldToolbarRoot: null,
-
-  events: function () {
-    var map = {
-      'click button.action-save': 'onClickSave',
-      'click button.action-cancel': 'onClickCancel',
-      'mouseenter': 'onMouseenter'
-    };
-    return map;
-  },
-
-  /**
-   * {@inheritdoc}
-   */
-  initialize: function (options) {
-    var that = this;
-    this.appModel = options.appModel;
-    this.$entity = $(this.model.get('el'));
-
-    // Rerender whenever the entity state changes.
-    this.listenTo(this.model, 'change:isActive change:isDirty change:state', this.render);
-    // Also rerender whenever a different field is highlighted or activated.
-    this.listenTo(this.appModel, 'change:highlightedField change:activeField', this.render);
-    // Rerender when a field of the entity changes state.
-    this.listenTo(this.model.get('fields'), 'change:state', this.fieldStateChange);
-
-    // Reposition the entity toolbar as the viewport and the position within the
-    // viewport changes.
-    $(window).on('resize.edit scroll.edit', debounce($.proxy(this.windowChangeHandler, this), 150));
-
-    // Adjust the fence placement within which the entity toolbar may be
-    // positioned.
-    $(document).on('drupalViewportOffsetChange.edit', function (event, offsets) {
-      if (that.$fence) {
-        that.$fence.css(offsets);
-      }
-    });
-
-    // Set the entity toolbar DOM element as the el for this view.
-    var $toolbar = this.buildToolbarEl();
-    this.setElement($toolbar);
-    this._fieldToolbarRoot = $toolbar.find('.edit-toolbar-field').get(0);
-
-    // Initial render.
-    this.render();
-  },
-
-  /**
-   * {@inheritdoc}
-   */
-  render: function () {
-    if (this.model.get('isActive')) {
-      // If the toolbar container doesn't exist, create it.
-      var $body = $('body');
-      if ($body.children('#edit-entity-toolbar').length === 0) {
-        $body.append(this.$el);
-      }
-      // The fence will define a area on the screen that the entity toolbar
-      // will be position within.
-      if ($body.children('#edit-toolbar-fence').length === 0) {
-        this.$fence = $(Drupal.theme('editEntityToolbarFence'))
-          .css(Drupal.displace())
-          .appendTo($body);
-      }
-      // Adds the entity title to the toolbar.
-      this.label();
+  "use strict";
 
-      // Show the save and cancel buttons.
-      this.show('ops');
-      // If render is being called and the toolbar is already visible, just
-      // reposition it.
-      this.position();
-    }
+  Drupal.edit.EntityToolbarView = Backbone.View.extend({
 
-    // The save button text and state varies with the state of the entity model.
-    var $button = this.$el.find('.edit-button.action-save');
-    var isDirty = this.model.get('isDirty');
-    // Adjust the save button according to the state of the model.
-    switch (this.model.get('state')) {
-      // Quick editing is active, but no field is being edited.
-      case 'opened':
-        // The saving throbber is not managed by AJAX system. The
-        // EntityToolbarView manages this visual element.
-        $button
-          .removeClass('action-saving icon-throbber icon-end')
-          .text(Drupal.t('Save'))
-          .removeAttr('disabled')
-          .attr('aria-hidden', !isDirty);
-        break;
-      // The changes to the fields of the entity are being committed.
-      case 'committing':
-        $button
-          .addClass('action-saving icon-throbber icon-end')
-          .text(Drupal.t('Saving'))
-          .attr('disabled', 'disabled');
-        break;
-      default:
-        $button.attr('aria-hidden', true);
-        break;
-    }
+    _fieldToolbarRoot: null,
 
-    return this;
-  },
-
-  /**
-   * {@inheritdoc}
-   */
-  remove: function () {
-    // Remove additional DOM elements controlled by this View.
-    this.$fence.remove();
-
-    // Stop listening to additional events.
-    $(window).off('resize.edit scroll.edit');
-    $(document).off('drupalViewportOffsetChange.edit');
-
-    Backbone.View.prototype.remove.call(this);
-  },
-
-  /**
-   * Repositions the entity toolbar on window scroll and resize.
-   *
-   * @param jQuery.Eevent event
-   */
-  windowChangeHandler: function (event) {
-    this.position();
-  },
-
-  /**
-   * Determines the actions to take given a change of state.
-   *
-   * @param Drupal.edit.FieldModel model
-   * @param String state
-   *   The state of the associated field. One of Drupal.edit.FieldModel.states.
-   */
-  fieldStateChange: function (model, state) {
-    switch (state) {
-      case 'active':
-        this.render();
-        break;
-      case 'invalid':
-        this.render();
-        break;
-    }
-  },
-
-  /**
-   * Uses the jQuery.ui.position() method to position the entity toolbar.
-   *
-   * @param jQuery|DOM element
-   *   (optional) The element against which the entity toolbar is positioned.
-   */
-  position: function (element) {
-    clearTimeout(this.timer);
-
-    var that = this;
-    // Vary the edge of the positioning according to the direction of language
-    // in the document.
-    var edge = (document.documentElement.dir === 'rtl') ? 'right' : 'left';
-    // A time unit to wait until the entity toolbar is repositioned.
-    var delay = 0;
-    // Determines what check in the series of checks below should be evaluated
-    var check = 0;
-    // When positioned against an active field that has padding, we should
-    // ignore that padding when positioning the toolbar, to not unnecessarily
-    // move the toolbar horizontally, which feels annoying.
-    var horizontalPadding = 0;
-    var of, activeField, highlightedField;
-    // There are several elements in the page that the entity toolbar might be
-    // positioned against. They are considered below in a priority order.
-    do {
-      switch (check) {
-        case 0:
-          // Position against a specific element.
-          of = element;
-          break;
-        case 1:
-          // Position against a form container.
-          activeField = Drupal.edit.app.model.get('activeField');
-          of = activeField && activeField.editorView && activeField.editorView.$formContainer && activeField.editorView.$formContainer.find('.edit-form');
-          break;
-        case 2:
-          // Position against an active field.
-          of = activeField && activeField.editorView && activeField.editorView.getEditedElement();
-          if (activeField && activeField.editorView && activeField.editorView.getEditUISettings().padding) {
-            horizontalPadding = 5;
-          }
+    events: function () {
+      var map = {
+        'click button.action-save': 'onClickSave',
+        'click button.action-cancel': 'onClickCancel',
+        'mouseenter': 'onMouseenter'
+      };
+      return map;
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    initialize: function (options) {
+      var that = this;
+      this.appModel = options.appModel;
+      this.$entity = $(this.model.get('el'));
+
+      // Rerender whenever the entity state changes.
+      this.listenTo(this.model, 'change:isActive change:isDirty change:state', this.render);
+      // Also rerender whenever a different field is highlighted or activated.
+      this.listenTo(this.appModel, 'change:highlightedField change:activeField', this.render);
+      // Rerender when a field of the entity changes state.
+      this.listenTo(this.model.get('fields'), 'change:state', this.fieldStateChange);
+
+      // Reposition the entity toolbar as the viewport and the position within the
+      // viewport changes.
+      $(window).on('resize.edit scroll.edit', debounce($.proxy(this.windowChangeHandler, this), 150));
+
+      // Adjust the fence placement within which the entity toolbar may be
+      // positioned.
+      $(document).on('drupalViewportOffsetChange.edit', function (event, offsets) {
+        if (that.$fence) {
+          that.$fence.css(offsets);
+        }
+      });
+
+      // Set the entity toolbar DOM element as the el for this view.
+      var $toolbar = this.buildToolbarEl();
+      this.setElement($toolbar);
+      this._fieldToolbarRoot = $toolbar.find('.edit-toolbar-field').get(0);
+
+      // Initial render.
+      this.render();
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    render: function () {
+      if (this.model.get('isActive')) {
+        // If the toolbar container doesn't exist, create it.
+        var $body = $('body');
+        if ($body.children('#edit-entity-toolbar').length === 0) {
+          $body.append(this.$el);
+        }
+        // The fence will define a area on the screen that the entity toolbar
+        // will be position within.
+        if ($body.children('#edit-toolbar-fence').length === 0) {
+          this.$fence = $(Drupal.theme('editEntityToolbarFence'))
+            .css(Drupal.displace())
+            .appendTo($body);
+        }
+        // Adds the entity title to the toolbar.
+        this.label();
+
+        // Show the save and cancel buttons.
+        this.show('ops');
+        // If render is being called and the toolbar is already visible, just
+        // reposition it.
+        this.position();
+      }
+
+      // The save button text and state varies with the state of the entity model.
+      var $button = this.$el.find('.edit-button.action-save');
+      var isDirty = this.model.get('isDirty');
+      // Adjust the save button according to the state of the model.
+      switch (this.model.get('state')) {
+        // Quick editing is active, but no field is being edited.
+        case 'opened':
+          // The saving throbber is not managed by AJAX system. The
+          // EntityToolbarView manages this visual element.
+          $button
+            .removeClass('action-saving icon-throbber icon-end')
+            .text(Drupal.t('Save'))
+            .removeAttr('disabled')
+            .attr('aria-hidden', !isDirty);
           break;
-        case 3:
-          // Position against a highlighted field.
-          highlightedField = Drupal.edit.app.model.get('highlightedField');
-          of = highlightedField && highlightedField.editorView && highlightedField.editorView.getEditedElement();
-          delay = 250;
+        // The changes to the fields of the entity are being committed.
+        case 'committing':
+          $button
+            .addClass('action-saving icon-throbber icon-end')
+            .text(Drupal.t('Saving'))
+            .attr('disabled', 'disabled');
           break;
         default:
-          // Position against the entity, or as a last resort, the body element.
-          of = this.$entity || 'body';
-          delay = 750;
+          $button.attr('aria-hidden', true);
           break;
       }
-      // Prepare to check the next possible element to position against.
-      check++;
-    } while (!of);
+
+      return this;
+    },
 
     /**
-     * Refines the positioning algorithm of jquery.ui.position().
+     * {@inheritdoc}
+     */
+    remove: function () {
+      // Remove additional DOM elements controlled by this View.
+      this.$fence.remove();
+
+      // Stop listening to additional events.
+      $(window).off('resize.edit scroll.edit');
+      $(document).off('drupalViewportOffsetChange.edit');
+
+      Backbone.View.prototype.remove.call(this);
+    },
+
+    /**
+     * Repositions the entity toolbar on window scroll and resize.
      *
-     * Invoked as the 'using' callback of jquery.ui.position() in
-     * positionToolbar().
+     * @param jQuery.Eevent event
+     */
+    windowChangeHandler: function (event) {
+      this.position();
+    },
+
+    /**
+     * Determines the actions to take given a change of state.
      *
-     * @param Object suggested
-     *   A hash of top and left values for the position that should be set. It
-     *   can be forwarded to .css() or .animate().
-     * @param Object info
-     *   The position and dimensions of both the 'my' element and the 'of'
-     *   elements, as well as calculations to their relative position. This
-     *   object contains the following properties:
-     *     - Object element: A hash that contains information about the HTML
-     *     element that will be positioned. Also known as the 'my' element.
-     *     - Object target: A hash that contains information about the HTML
-     *     element that the 'my' element will be positioned against. Also known
-     *     as the 'of' element.
+     * @param Drupal.edit.FieldModel model
+     * @param String state
+     *   The state of the associated field. One of Drupal.edit.FieldModel.states.
      */
-    function refinePosition (view, suggested, info) {
-      // Determine if the pointer should be on the top or bottom.
-      var isBelow = suggested.top > info.target.top;
-      info.element.element.toggleClass('edit-toolbar-pointer-top', isBelow);
-      // Don't position the toolbar past the first or last editable field if
-      // the entity is the target.
-      if (view.$entity[0] === info.target.element[0]) {
-        // Get the first or last field according to whether the toolbar is above
-        // or below the entity.
-        var $field = view.$entity.find('.edit-editable').eq((isBelow) ? -1 : 0);
-        if ($field.length > 0) {
-          suggested.top = (isBelow) ? ($field.offset().top + $field.outerHeight(true)) : $field.offset().top - info.element.element.outerHeight(true);
-        }
+    fieldStateChange: function (model, state) {
+      switch (state) {
+        case 'active':
+          this.render();
+          break;
+        case 'invalid':
+          this.render();
+          break;
       }
-      // Don't let the toolbar go outside the fence.
-      var fenceTop = view.$fence.offset().top;
-      var fenceHeight = view.$fence.height();
-      var toolbarHeight = info.element.element.outerHeight(true);
-      if (suggested.top < fenceTop) {
-        suggested.top = fenceTop;
+    },
+
+    /**
+     * Uses the jQuery.ui.position() method to position the entity toolbar.
+     *
+     * @param jQuery|DOM element
+     *   (optional) The element against which the entity toolbar is positioned.
+     */
+    position: function (element) {
+      clearTimeout(this.timer);
+
+      var that = this;
+      // Vary the edge of the positioning according to the direction of language
+      // in the document.
+      var edge = (document.documentElement.dir === 'rtl') ? 'right' : 'left';
+      // A time unit to wait until the entity toolbar is repositioned.
+      var delay = 0;
+      // Determines what check in the series of checks below should be evaluated
+      var check = 0;
+      // When positioned against an active field that has padding, we should
+      // ignore that padding when positioning the toolbar, to not unnecessarily
+      // move the toolbar horizontally, which feels annoying.
+      var horizontalPadding = 0;
+      var of, activeField, highlightedField;
+      // There are several elements in the page that the entity toolbar might be
+      // positioned against. They are considered below in a priority order.
+      do {
+        switch (check) {
+          case 0:
+            // Position against a specific element.
+            of = element;
+            break;
+          case 1:
+            // Position against a form container.
+            activeField = Drupal.edit.app.model.get('activeField');
+            of = activeField && activeField.editorView && activeField.editorView.$formContainer && activeField.editorView.$formContainer.find('.edit-form');
+            break;
+          case 2:
+            // Position against an active field.
+            of = activeField && activeField.editorView && activeField.editorView.getEditedElement();
+            if (activeField && activeField.editorView && activeField.editorView.getEditUISettings().padding) {
+              horizontalPadding = 5;
+            }
+            break;
+          case 3:
+            // Position against a highlighted field.
+            highlightedField = Drupal.edit.app.model.get('highlightedField');
+            of = highlightedField && highlightedField.editorView && highlightedField.editorView.getEditedElement();
+            delay = 250;
+            break;
+          default:
+            // Position against the entity, or as a last resort, the body element.
+            of = this.$entity || 'body';
+            delay = 750;
+            break;
+        }
+        // Prepare to check the next possible element to position against.
+        check++;
+      } while (!of);
+
+      /**
+       * Refines the positioning algorithm of jquery.ui.position().
+       *
+       * Invoked as the 'using' callback of jquery.ui.position() in
+       * positionToolbar().
+       *
+       * @param Object suggested
+       *   A hash of top and left values for the position that should be set. It
+       *   can be forwarded to .css() or .animate().
+       * @param Object info
+       *   The position and dimensions of both the 'my' element and the 'of'
+       *   elements, as well as calculations to their relative position. This
+       *   object contains the following properties:
+       *     - Object element: A hash that contains information about the HTML
+       *     element that will be positioned. Also known as the 'my' element.
+       *     - Object target: A hash that contains information about the HTML
+       *     element that the 'my' element will be positioned against. Also known
+       *     as the 'of' element.
+       */
+      function refinePosition(view, suggested, info) {
+        // Determine if the pointer should be on the top or bottom.
+        var isBelow = suggested.top > info.target.top;
+        info.element.element.toggleClass('edit-toolbar-pointer-top', isBelow);
+        // Don't position the toolbar past the first or last editable field if
+        // the entity is the target.
+        if (view.$entity[0] === info.target.element[0]) {
+          // Get the first or last field according to whether the toolbar is above
+          // or below the entity.
+          var $field = view.$entity.find('.edit-editable').eq((isBelow) ? -1 : 0);
+          if ($field.length > 0) {
+            suggested.top = (isBelow) ? ($field.offset().top + $field.outerHeight(true)) : $field.offset().top - info.element.element.outerHeight(true);
+          }
+        }
+        // Don't let the toolbar go outside the fence.
+        var fenceTop = view.$fence.offset().top;
+        var fenceHeight = view.$fence.height();
+        var toolbarHeight = info.element.element.outerHeight(true);
+        if (suggested.top < fenceTop) {
+          suggested.top = fenceTop;
+        }
+        else if ((suggested.top + toolbarHeight) > (fenceTop + fenceHeight)) {
+          suggested.top = fenceTop + fenceHeight - toolbarHeight;
+        }
+        // Position the toolbar.
+        info.element.element.css({
+          left: Math.floor(suggested.left),
+          top: Math.floor(suggested.top)
+        });
       }
-      else if ((suggested.top + toolbarHeight) > (fenceTop + fenceHeight)) {
-        suggested.top = fenceTop + fenceHeight - toolbarHeight;
+
+      /**
+       * Calls the jquery.ui.position() method on the $el of this view.
+       */
+      function positionToolbar() {
+        that.$el
+          .position({
+            my: edge + ' bottom',
+            // Move the toolbar 1px towards the start edge of the 'of' element,
+            // plus any horizontal padding that may have been added to the element
+            // that is being added, to prevent unwanted horizontal movement.
+            at: edge + '+' + (1 + horizontalPadding) + ' top',
+            of: of,
+            collision: 'flipfit',
+            using: refinePosition.bind(null, that),
+            within: that.$fence
+          })
+          // Resize the toolbar to match the dimensions of the field, up to a
+          // maximum width that is equal to 90% of the field's width.
+          .css({
+            'max-width': (document.documentElement.clientWidth < 450) ? document.documentElement.clientWidth : 450,
+            // Set a minimum width of 240px for the entity toolbar, or the width
+            // of the client if it is less than 240px, so that the toolbar
+            // never folds up into a squashed and jumbled mess.
+            'min-width': (document.documentElement.clientWidth < 240) ? document.documentElement.clientWidth : 240,
+            'width': '100%'
+          });
       }
-      // Position the toolbar.
-      info.element.element.css({
-        left: Math.floor(suggested.left),
-        top: Math.floor(suggested.top)
-      });
-    }
+
+      // Uses the jQuery.ui.position() method. Use a timeout to move the toolbar
+      // only after the user has focused on an editable for 250ms. This prevents
+      // the toolbar from jumping around the screen.
+      this.timer = setTimeout(function () {
+        // Render the position in the next execution cycle, so that animations on
+        // the field have time to process. This is not strictly speaking, a
+        // guarantee that all animations will be finished, but it's a simple way
+        // to get better positioning without too much additional code.
+        _.defer(positionToolbar);
+      }, delay);
+    },
 
     /**
-     * Calls the jquery.ui.position() method on the $el of this view.
+     * Set the model state to 'saving' when the save button is clicked.
+     *
+     * @param jQuery event
      */
-    function positionToolbar () {
-      that.$el
-        .position({
-          my: edge + ' bottom',
-          // Move the toolbar 1px towards the start edge of the 'of' element,
-          // plus any horizontal padding that may have been added to the element
-          // that is being added, to prevent unwanted horizontal movement.
-          at: edge + '+' + (1 + horizontalPadding) + ' top',
-          of: of,
-          collision: 'flipfit',
-          using: refinePosition.bind(null, that),
-          within: that.$fence
-        })
-        // Resize the toolbar to match the dimensions of the field, up to a
-        // maximum width that is equal to 90% of the field's width.
+    onClickSave: function (event) {
+      event.stopPropagation();
+      event.preventDefault();
+      // Save the model.
+      this.model.set('state', 'committing');
+    },
+
+    /**
+     * Sets the model state to candidate when the cancel button is clicked.
+     *
+     * @param jQuery event
+     */
+    onClickCancel: function (event) {
+      event.preventDefault();
+      this.model.set('state', 'deactivating');
+    },
+
+    /**
+     * Clears the timeout that will eventually reposition the entity toolbar.
+     *
+     * Without this, it may reposition itself, away from the user's cursor!
+     *
+     * @param jQuery event
+     */
+    onMouseenter: function (event) {
+      clearTimeout(this.timer);
+    },
+
+    /**
+     * Builds the entity toolbar HTML; attaches to DOM; sets starting position.
+     */
+    buildToolbarEl: function () {
+      var $toolbar = $(Drupal.theme('editEntityToolbar', {
+        id: 'edit-entity-toolbar'
+      }));
+
+      $toolbar
+        .find('.edit-toolbar-entity')
+        // Append the "ops" toolgroup into the toolbar.
+        .prepend(Drupal.theme('editToolgroup', {
+          classes: ['ops'],
+          buttons: [
+            {
+              label: Drupal.t('Save'),
+              type: 'submit',
+              classes: 'action-save edit-button icon',
+              attributes: {
+                'aria-hidden': true
+              }
+            },
+            {
+              label: Drupal.t('Close'),
+              classes: 'action-cancel edit-button icon icon-close icon-only'
+            }
+          ]
+        }));
+
+      // Give the toolbar a sensible starting position so that it doesn't animate
+      // on to the screen from a far off corner.
+      $toolbar
         .css({
-          'max-width': (document.documentElement.clientWidth < 450) ? document.documentElement.clientWidth : 450,
-          // Set a minimum width of 240px for the entity toolbar, or the width
-          // of the client if it is less than 240px, so that the toolbar
-          // never folds up into a squashed and jumbled mess.
-          'min-width': (document.documentElement.clientWidth < 240) ? document.documentElement.clientWidth : 240,
-          'width': '100%'
+          left: this.$entity.offset().left,
+          top: this.$entity.offset().top
         });
-    }
 
-    // Uses the jQuery.ui.position() method. Use a timeout to move the toolbar
-    // only after the user has focused on an editable for 250ms. This prevents
-    // the toolbar from jumping around the screen.
-    this.timer = setTimeout(function () {
-      // Render the position in the next execution cycle, so that animations on
-      // the field have time to process. This is not strictly speaking, a
-      // guarantee that all animations will be finished, but it's a simple way
-      // to get better positioning without too much additional code.
-      _.defer(positionToolbar);
-    }, delay);
-  },
-
-  /**
-   * Set the model state to 'saving' when the save button is clicked.
-   *
-   * @param jQuery event
-   */
-  onClickSave: function (event) {
-    event.stopPropagation();
-    event.preventDefault();
-    // Save the model.
-    this.model.set('state', 'committing');
-  },
-
-  /**
-   * Sets the model state to candidate when the cancel button is clicked.
-   *
-   * @param jQuery event
-   */
-  onClickCancel: function (event) {
-    event.preventDefault();
-    this.model.set('state', 'deactivating');
-  },
-
-  /**
-   * Clears the timeout that will eventually reposition the entity toolbar.
-   *
-   * Without this, it may reposition itself, away from the user's cursor!
-   *
-   * @param jQuery event
-   */
-  onMouseenter: function (event) {
-    clearTimeout(this.timer);
-  },
-
-  /**
-   * Builds the entity toolbar HTML; attaches to DOM; sets starting position.
-   */
-  buildToolbarEl: function () {
-    var $toolbar = $(Drupal.theme('editEntityToolbar', {
-      id: 'edit-entity-toolbar'
-    }));
-
-    $toolbar
-      .find('.edit-toolbar-entity')
-      // Append the "ops" toolgroup into the toolbar.
-      .prepend(Drupal.theme('editToolgroup', {
-        classes: ['ops'],
-        buttons: [
-          {
-            label: Drupal.t('Save'),
-            type: 'submit',
-            classes: 'action-save edit-button icon',
-            attributes: {
-              'aria-hidden': true
-            }
-          },
-          {
-            label: Drupal.t('Close'),
-            classes: 'action-cancel edit-button icon icon-close icon-only'
-          }
-        ]
-      }));
+      return $toolbar;
+    },
 
-    // Give the toolbar a sensible starting position so that it doesn't animate
-    // on to the screen from a far off corner.
-    $toolbar
-      .css({
-        left: this.$entity.offset().left,
-        top: this.$entity.offset().top
-      });
+    /**
+     * Returns the DOM element that fields will attach their toolbars to.
+     *
+     * @return jQuery
+     *   The DOM element that fields will attach their toolbars to.
+     */
+    getToolbarRoot: function () {
+      return this._fieldToolbarRoot;
+    },
 
-    return $toolbar;
-  },
-
-  /**
-   * Returns the DOM element that fields will attach their toolbars to.
-   *
-   * @return jQuery
-   *   The DOM element that fields will attach their toolbars to.
-   */
-  getToolbarRoot: function () {
-    return this._fieldToolbarRoot;
-  },
-
-  /**
-   * Generates a state-dependent label for the entity toolbar.
-   */
-  label: function () {
-    // The entity label.
-    var label = '';
-    var entityLabel = this.model.get('label');
-
-    // Label of an active field, if it exists.
-    var activeField = Drupal.edit.app.model.get('activeField');
-    var activeFieldLabel = activeField && activeField.get('metadata').label;
-    // Label of a highlighted field, if it exists.
-    var highlightedField = Drupal.edit.app.model.get('highlightedField');
-    var highlightedFieldLabel = highlightedField && highlightedField.get('metadata').label;
-    // The label is constructed in a priority order.
-    if (activeFieldLabel) {
-      label = Drupal.theme('editEntityToolbarLabel', {
-        entityLabel: entityLabel,
-        fieldLabel: activeFieldLabel
-      });
-    }
-    else if (highlightedFieldLabel) {
-      label = Drupal.theme('editEntityToolbarLabel', {
-        entityLabel: entityLabel,
-        fieldLabel: highlightedFieldLabel
-      });
-    }
-    else {
-      label = entityLabel;
+    /**
+     * Generates a state-dependent label for the entity toolbar.
+     */
+    label: function () {
+      // The entity label.
+      var label = '';
+      var entityLabel = this.model.get('label');
+
+      // Label of an active field, if it exists.
+      var activeField = Drupal.edit.app.model.get('activeField');
+      var activeFieldLabel = activeField && activeField.get('metadata').label;
+      // Label of a highlighted field, if it exists.
+      var highlightedField = Drupal.edit.app.model.get('highlightedField');
+      var highlightedFieldLabel = highlightedField && highlightedField.get('metadata').label;
+      // The label is constructed in a priority order.
+      if (activeFieldLabel) {
+        label = Drupal.theme('editEntityToolbarLabel', {
+          entityLabel: entityLabel,
+          fieldLabel: activeFieldLabel
+        });
+      }
+      else if (highlightedFieldLabel) {
+        label = Drupal.theme('editEntityToolbarLabel', {
+          entityLabel: entityLabel,
+          fieldLabel: highlightedFieldLabel
+        });
+      }
+      else {
+        label = entityLabel;
+      }
+
+      this.$el
+        .find('.edit-toolbar-label')
+        .html(label);
+    },
+
+    /**
+     * Adds classes to a toolgroup.
+     *
+     * @param String toolgroup
+     *   A toolgroup name.
+     * @param String classes
+     *   A string of space-delimited class names that will be applied to the
+     *   wrapping element of the toolbar group.
+     */
+    addClass: function (toolgroup, classes) {
+      this._find(toolgroup).addClass(classes);
+    },
+
+    /**
+     * Removes classes from a toolgroup.
+     *
+     * @param String toolgroup
+     *   A toolgroup name.
+     * @param String classes
+     *   A string of space-delimited class names that will be removed from the
+     *   wrapping element of the toolbar group.
+     */
+    removeClass: function (toolgroup, classes) {
+      this._find(toolgroup).removeClass(classes);
+    },
+
+    /**
+     * Finds a toolgroup.
+     *
+     * @param String toolgroup
+     *   A toolgroup name.
+     * @return jQuery
+     *   The toolgroup DOM element.
+     */
+    _find: function (toolgroup) {
+      return this.$el.find('.edit-toolbar .edit-toolgroup.' + toolgroup);
+    },
+
+    /**
+     * Shows a toolgroup.
+     *
+     * @param String toolgroup
+     *   A toolgroup name.
+     */
+    show: function (toolgroup) {
+      this.$el.removeClass('edit-animate-invisible');
     }
 
-    this.$el
-      .find('.edit-toolbar-label')
-      .html(label);
-  },
-
-  /**
-   * Adds classes to a toolgroup.
-   *
-   * @param String toolgroup
-   *   A toolgroup name.
-   * @param String classes
-   *   A string of space-delimited class names that will be applied to the
-   *   wrapping element of the toolbar group.
-   */
-  addClass: function (toolgroup, classes) {
-    this._find(toolgroup).addClass(classes);
-  },
-
-  /**
-   * Removes classes from a toolgroup.
-   *
-   * @param String toolgroup
-   *   A toolgroup name.
-   * @param String classes
-   *   A string of space-delimited class names that will be removed from the
-   *   wrapping element of the toolbar group.
-   */
-  removeClass: function (toolgroup, classes) {
-    this._find(toolgroup).removeClass(classes);
-  },
-
-  /**
-   * Finds a toolgroup.
-   *
-   * @param String toolgroup
-   *   A toolgroup name.
-   * @return jQuery
-   *   The toolgroup DOM element.
-   */
-  _find: function (toolgroup) {
-    return this.$el.find('.edit-toolbar .edit-toolgroup.' + toolgroup);
-  },
-
-  /**
-   * Shows a toolgroup.
-   *
-   * @param String toolgroup
-   *   A toolgroup name.
-   */
-  show: function (toolgroup) {
-    this.$el.removeClass('edit-animate-invisible');
-  }
-
-});
+  });
 
 })(jQuery, _, Backbone, Drupal, Drupal.debounce);
diff --git a/core/modules/edit/js/views/FieldDecorationView.js b/core/modules/edit/js/views/FieldDecorationView.js
index 4172840..e873e45 100644
--- a/core/modules/edit/js/views/FieldDecorationView.js
+++ b/core/modules/edit/js/views/FieldDecorationView.js
@@ -5,321 +5,321 @@
 
 (function ($, Backbone, Drupal) {
 
-"use strict";
-
-Drupal.edit.FieldDecorationView = Backbone.View.extend({
-
-  _widthAttributeIsEmpty: null,
-
-  events: {
-    'mouseenter.edit' : 'onMouseEnter',
-    'mouseleave.edit' : 'onMouseLeave',
-    'click': 'onClick',
-    'tabIn.edit': 'onMouseEnter',
-    'tabOut.edit': 'onMouseLeave'
-  },
-
-  /**
-   * {@inheritdoc}
-   *
-   * @param Object options
-   *   An object with the following keys:
-   *   - Drupal.edit.EditorView editorView: the editor object view.
-   */
-  initialize: function (options) {
-    this.editorView = options.editorView;
-
-    this.listenTo(this.model, 'change:state', this.stateChange);
-    this.listenTo(this.model, 'change:isChanged change:inTempStore', this.renderChanged);
-  },
-
-  /**
-   * {@inheritdoc}
-   */
-  remove: function () {
-    // The el property is the field, which should not be removed. Remove the
-    // pointer to it, then call Backbone.View.prototype.remove().
-    this.setElement();
-    Backbone.View.prototype.remove.call(this);
-  },
-
-  /**
-   * Determines the actions to take given a change of state.
-   *
-   * @param Drupal.edit.FieldModel model
-   * @param String state
-   *   The state of the associated field. One of Drupal.edit.FieldModel.states.
-   */
-  stateChange: function (model, state) {
-    var from = model.previous('state');
-    var to = state;
-    switch (to) {
-      case 'inactive':
-        this.undecorate();
-        break;
-      case 'candidate':
-        this.decorate();
-        if (from !== 'inactive') {
-          this.stopHighlight();
-          if (from !== 'highlighted') {
-            this.model.set('isChanged', false);
-            this.stopEdit();
+  "use strict";
+
+  Drupal.edit.FieldDecorationView = Backbone.View.extend({
+
+    _widthAttributeIsEmpty: null,
+
+    events: {
+      'mouseenter.edit': 'onMouseEnter',
+      'mouseleave.edit': 'onMouseLeave',
+      'click': 'onClick',
+      'tabIn.edit': 'onMouseEnter',
+      'tabOut.edit': 'onMouseLeave'
+    },
+
+    /**
+     * {@inheritdoc}
+     *
+     * @param Object options
+     *   An object with the following keys:
+     *   - Drupal.edit.EditorView editorView: the editor object view.
+     */
+    initialize: function (options) {
+      this.editorView = options.editorView;
+
+      this.listenTo(this.model, 'change:state', this.stateChange);
+      this.listenTo(this.model, 'change:isChanged change:inTempStore', this.renderChanged);
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    remove: function () {
+      // The el property is the field, which should not be removed. Remove the
+      // pointer to it, then call Backbone.View.prototype.remove().
+      this.setElement();
+      Backbone.View.prototype.remove.call(this);
+    },
+
+    /**
+     * Determines the actions to take given a change of state.
+     *
+     * @param Drupal.edit.FieldModel model
+     * @param String state
+     *   The state of the associated field. One of Drupal.edit.FieldModel.states.
+     */
+    stateChange: function (model, state) {
+      var from = model.previous('state');
+      var to = state;
+      switch (to) {
+        case 'inactive':
+          this.undecorate();
+          break;
+        case 'candidate':
+          this.decorate();
+          if (from !== 'inactive') {
+            this.stopHighlight();
+            if (from !== 'highlighted') {
+              this.model.set('isChanged', false);
+              this.stopEdit();
+            }
           }
-        }
-        this._unpad();
-        break;
-      case 'highlighted':
-        this.startHighlight();
-        break;
-      case 'activating':
-        // NOTE: this state is not used by every editor! It's only used by those
-        // that need to interact with the server.
-        this.prepareEdit();
-        break;
-      case 'active':
-        if (from !== 'activating') {
+          this._unpad();
+          break;
+        case 'highlighted':
+          this.startHighlight();
+          break;
+        case 'activating':
+          // NOTE: this state is not used by every editor! It's only used by those
+          // that need to interact with the server.
           this.prepareEdit();
-        }
-        if (this.editorView.getEditUISettings().padding) {
-          this._pad();
-        }
-        break;
-      case 'changed':
-        this.model.set('isChanged', true);
-        break;
-      case 'saving':
-        break;
-      case 'saved':
-        break;
-      case 'invalid':
-        break;
-    }
-  },
-
-  /**
-   * Adds a class to the edited element that indicates whether the field has
-   * been changed by the user (i.e. locally) or the field has already been
-   * changed and stored before by the user (i.e. remotely, stored in TempStore).
-   */
-  renderChanged: function () {
-    this.$el.toggleClass('edit-changed', this.model.get('isChanged') || this.model.get('inTempStore'));
-  },
-
-  /**
-   * Starts hover; transitions to 'highlight' state.
-   *
-   * @param jQuery event
-   */
-  onMouseEnter: function (event) {
-    var that = this;
-    that.model.set('state', 'highlighted');
-    event.stopPropagation();
-  },
-
-  /**
-   * Stops hover; transitions to 'candidate' state.
-   *
-   * @param jQuery event
-   */
-  onMouseLeave: function (event) {
-    var that = this;
-    that.model.set('state', 'candidate', { reason: 'mouseleave' });
-    event.stopPropagation();
-  },
-
-  /**
-   * Transition to 'activating' stage.
-   *
-   * @param jQuery event
-   */
-  onClick: function (event) {
-    this.model.set('state', 'activating');
-    event.preventDefault();
-    event.stopPropagation();
-  },
-
-  /**
-   * Adds classes used to indicate an elements editable state.
-   */
-  decorate: function () {
-    this.$el.addClass('edit-candidate edit-editable');
-  },
-
-  /**
-   * Removes classes used to indicate an elements editable state.
-   */
-  undecorate: function () {
-    this.$el.removeClass('edit-candidate edit-editable edit-highlighted edit-editing');
-  },
-
-  /**
-   * Adds that class that indicates that an element is highlighted.
-   */
-  startHighlight: function () {
-    // Animations.
-    var that = this;
-    // Use a timeout to grab the next available animation frame.
-    that.$el.addClass('edit-highlighted');
-  },
-
-  /**
-   * Removes the class that indicates that an element is highlighted.
-   */
-  stopHighlight: function () {
-    this.$el.removeClass('edit-highlighted');
-  },
-
-  /**
-   * Removes the class that indicates that an element as editable.
-   */
-  prepareEdit: function () {
-    this.$el.addClass('edit-editing');
-
-    // Allow the field to be styled differently while editing in a pop-up
-    // in-place editor.
-    if (this.editorView.getEditUISettings().popup) {
-      this.$el.addClass('edit-editor-is-popup');
-    }
-  },
-
-  /**
-   * Removes the class that indicates that an element is being edited.
-   *
-   * Reapplies the class that indicates that a candidate editable element is
-   * again available to be edited.
-   */
-  stopEdit: function () {
-    this.$el.removeClass('edit-highlighted edit-editing');
-
-    // Done editing in a pop-up in-place editor; remove the class.
-    if (this.editorView.getEditUISettings().popup) {
-      this.$el.removeClass('edit-editor-is-popup');
-    }
-
-    // Make the other editors show up again.
-    $('.edit-candidate').addClass('edit-editable');
-  },
-
-  /**
-   * Adds padding around the editable element in order to make it pop visually.
-   */
-  _pad: function () {
-    // Early return if the element has already been padded.
-    if (this.$el.data('edit-padded')) {
-      return;
-    }
-    var self = this;
-
-    // Add 5px padding for readability. This means we'll freeze the current
-    // width and *then* add 5px padding, hence ensuring the padding is added "on
-    // the outside".
-    // 1) Freeze the width (if it's not already set); don't use animations.
-    if (this.$el[0].style.width === "") {
-      this._widthAttributeIsEmpty = true;
-      this.$el
-        .addClass('edit-animate-disable-width')
-        .css('width', this.$el.width())
-        .css('background-color', this._getBgColor(this.$el));
-    }
-
-    // 2) Add padding; use animations.
-    var posProp = this._getPositionProperties(this.$el);
-    setTimeout(function () {
-      // Re-enable width animations (padding changes affect width too!).
-      self.$el.removeClass('edit-animate-disable-width');
-
-      // Pad the editable.
-      self.$el
-      .css({
-        'position': 'relative',
-        'top':  posProp.top  - 5 + 'px',
-        'left': posProp.left - 5 + 'px',
-        'padding-top'   : posProp['padding-top']    + 5 + 'px',
-        'padding-left'  : posProp['padding-left']   + 5 + 'px',
-        'padding-right' : posProp['padding-right']  + 5 + 'px',
-        'padding-bottom': posProp['padding-bottom'] + 5 + 'px',
-        'margin-bottom':  posProp['margin-bottom'] - 10 + 'px'
-      })
-      .data('edit-padded', true);
-    }, 0);
-  },
-
-  /**
-   * Removes the padding around the element being edited when editing ceases.
-   */
-  _unpad: function () {
-    // Early return if the element has not been padded.
-    if (!this.$el.data('edit-padded')) {
-      return;
-    }
-    var self = this;
-
-    // 1) Set the empty width again.
-    if (this._widthAttributeIsEmpty) {
-      this.$el
-        .addClass('edit-animate-disable-width')
-        .css('width', '')
-        .css('background-color', '');
-    }
-
-    // 2) Remove padding; use animations (these will run simultaneously with)
-    // the fading out of the toolbar as its gets removed).
-    var posProp = this._getPositionProperties(this.$el);
-    setTimeout(function () {
-      // Re-enable width animations (padding changes affect width too!).
-      self.$el.removeClass('edit-animate-disable-width');
-
-      // Unpad the editable.
-      self.$el
-      .css({
-        'position': 'relative',
-        'top':  posProp.top  + 5 + 'px',
-        'left': posProp.left + 5 + 'px',
-        'padding-top'   : posProp['padding-top']    - 5 + 'px',
-        'padding-left'  : posProp['padding-left']   - 5 + 'px',
-        'padding-right' : posProp['padding-right']  - 5 + 'px',
-        'padding-bottom': posProp['padding-bottom'] - 5 + 'px',
-        'margin-bottom': posProp['margin-bottom'] + 10 + 'px'
-      });
-    }, 0);
-    // Remove the marker that indicates that this field has padding. This is
-    // done outside the timed out function above so that we don't get numerous
-    // queued functions that will remove padding before the data marker has
-    // been removed.
-    this.$el.removeData('edit-padded');
-  },
-
-  /**
-   * Gets the background color of an element (or the inherited one).
-   *
-   * @param DOM $e
-   */
-  _getBgColor: function ($e) {
-    var c;
-
-    if ($e === null || $e[0].nodeName === 'HTML') {
-      // Fallback to white.
-      return 'rgb(255, 255, 255)';
-    }
-    c = $e.css('background-color');
-    // TRICKY: edge case for Firefox' "transparent" here; this is a
-    // browser bug: https://bugzilla.mozilla.org/show_bug.cgi?id=635724
-    if (c === 'rgba(0, 0, 0, 0)' || c === 'transparent') {
-      return this._getBgColor($e.parent());
-    }
-    return c;
-  },
-
-  /**
-   * Gets the top and left properties of an element.
-   *
-   * Convert extraneous values and information into numbers ready for
-   * subtraction.
-   *
-   * @param DOM $e
-   */
-  _getPositionProperties: function ($e) {
-    var p,
+          break;
+        case 'active':
+          if (from !== 'activating') {
+            this.prepareEdit();
+          }
+          if (this.editorView.getEditUISettings().padding) {
+            this._pad();
+          }
+          break;
+        case 'changed':
+          this.model.set('isChanged', true);
+          break;
+        case 'saving':
+          break;
+        case 'saved':
+          break;
+        case 'invalid':
+          break;
+      }
+    },
+
+    /**
+     * Adds a class to the edited element that indicates whether the field has
+     * been changed by the user (i.e. locally) or the field has already been
+     * changed and stored before by the user (i.e. remotely, stored in TempStore).
+     */
+    renderChanged: function () {
+      this.$el.toggleClass('edit-changed', this.model.get('isChanged') || this.model.get('inTempStore'));
+    },
+
+    /**
+     * Starts hover; transitions to 'highlight' state.
+     *
+     * @param jQuery event
+     */
+    onMouseEnter: function (event) {
+      var that = this;
+      that.model.set('state', 'highlighted');
+      event.stopPropagation();
+    },
+
+    /**
+     * Stops hover; transitions to 'candidate' state.
+     *
+     * @param jQuery event
+     */
+    onMouseLeave: function (event) {
+      var that = this;
+      that.model.set('state', 'candidate', { reason: 'mouseleave' });
+      event.stopPropagation();
+    },
+
+    /**
+     * Transition to 'activating' stage.
+     *
+     * @param jQuery event
+     */
+    onClick: function (event) {
+      this.model.set('state', 'activating');
+      event.preventDefault();
+      event.stopPropagation();
+    },
+
+    /**
+     * Adds classes used to indicate an elements editable state.
+     */
+    decorate: function () {
+      this.$el.addClass('edit-candidate edit-editable');
+    },
+
+    /**
+     * Removes classes used to indicate an elements editable state.
+     */
+    undecorate: function () {
+      this.$el.removeClass('edit-candidate edit-editable edit-highlighted edit-editing');
+    },
+
+    /**
+     * Adds that class that indicates that an element is highlighted.
+     */
+    startHighlight: function () {
+      // Animations.
+      var that = this;
+      // Use a timeout to grab the next available animation frame.
+      that.$el.addClass('edit-highlighted');
+    },
+
+    /**
+     * Removes the class that indicates that an element is highlighted.
+     */
+    stopHighlight: function () {
+      this.$el.removeClass('edit-highlighted');
+    },
+
+    /**
+     * Removes the class that indicates that an element as editable.
+     */
+    prepareEdit: function () {
+      this.$el.addClass('edit-editing');
+
+      // Allow the field to be styled differently while editing in a pop-up
+      // in-place editor.
+      if (this.editorView.getEditUISettings().popup) {
+        this.$el.addClass('edit-editor-is-popup');
+      }
+    },
+
+    /**
+     * Removes the class that indicates that an element is being edited.
+     *
+     * Reapplies the class that indicates that a candidate editable element is
+     * again available to be edited.
+     */
+    stopEdit: function () {
+      this.$el.removeClass('edit-highlighted edit-editing');
+
+      // Done editing in a pop-up in-place editor; remove the class.
+      if (this.editorView.getEditUISettings().popup) {
+        this.$el.removeClass('edit-editor-is-popup');
+      }
+
+      // Make the other editors show up again.
+      $('.edit-candidate').addClass('edit-editable');
+    },
+
+    /**
+     * Adds padding around the editable element in order to make it pop visually.
+     */
+    _pad: function () {
+      // Early return if the element has already been padded.
+      if (this.$el.data('edit-padded')) {
+        return;
+      }
+      var self = this;
+
+      // Add 5px padding for readability. This means we'll freeze the current
+      // width and *then* add 5px padding, hence ensuring the padding is added "on
+      // the outside".
+      // 1) Freeze the width (if it's not already set); don't use animations.
+      if (this.$el[0].style.width === "") {
+        this._widthAttributeIsEmpty = true;
+        this.$el
+          .addClass('edit-animate-disable-width')
+          .css('width', this.$el.width())
+          .css('background-color', this._getBgColor(this.$el));
+      }
+
+      // 2) Add padding; use animations.
+      var posProp = this._getPositionProperties(this.$el);
+      setTimeout(function () {
+        // Re-enable width animations (padding changes affect width too!).
+        self.$el.removeClass('edit-animate-disable-width');
+
+        // Pad the editable.
+        self.$el
+          .css({
+            'position': 'relative',
+            'top': posProp.top - 5 + 'px',
+            'left': posProp.left - 5 + 'px',
+            'padding-top': posProp['padding-top'] + 5 + 'px',
+            'padding-left': posProp['padding-left'] + 5 + 'px',
+            'padding-right': posProp['padding-right'] + 5 + 'px',
+            'padding-bottom': posProp['padding-bottom'] + 5 + 'px',
+            'margin-bottom': posProp['margin-bottom'] - 10 + 'px'
+          })
+          .data('edit-padded', true);
+      }, 0);
+    },
+
+    /**
+     * Removes the padding around the element being edited when editing ceases.
+     */
+    _unpad: function () {
+      // Early return if the element has not been padded.
+      if (!this.$el.data('edit-padded')) {
+        return;
+      }
+      var self = this;
+
+      // 1) Set the empty width again.
+      if (this._widthAttributeIsEmpty) {
+        this.$el
+          .addClass('edit-animate-disable-width')
+          .css('width', '')
+          .css('background-color', '');
+      }
+
+      // 2) Remove padding; use animations (these will run simultaneously with)
+      // the fading out of the toolbar as its gets removed).
+      var posProp = this._getPositionProperties(this.$el);
+      setTimeout(function () {
+        // Re-enable width animations (padding changes affect width too!).
+        self.$el.removeClass('edit-animate-disable-width');
+
+        // Unpad the editable.
+        self.$el
+          .css({
+            'position': 'relative',
+            'top': posProp.top + 5 + 'px',
+            'left': posProp.left + 5 + 'px',
+            'padding-top': posProp['padding-top'] - 5 + 'px',
+            'padding-left': posProp['padding-left'] - 5 + 'px',
+            'padding-right': posProp['padding-right'] - 5 + 'px',
+            'padding-bottom': posProp['padding-bottom'] - 5 + 'px',
+            'margin-bottom': posProp['margin-bottom'] + 10 + 'px'
+          });
+      }, 0);
+      // Remove the marker that indicates that this field has padding. This is
+      // done outside the timed out function above so that we don't get numerous
+      // queued functions that will remove padding before the data marker has
+      // been removed.
+      this.$el.removeData('edit-padded');
+    },
+
+    /**
+     * Gets the background color of an element (or the inherited one).
+     *
+     * @param DOM $e
+     */
+    _getBgColor: function ($e) {
+      var c;
+
+      if ($e === null || $e[0].nodeName === 'HTML') {
+        // Fallback to white.
+        return 'rgb(255, 255, 255)';
+      }
+      c = $e.css('background-color');
+      // TRICKY: edge case for Firefox' "transparent" here; this is a
+      // browser bug: https://bugzilla.mozilla.org/show_bug.cgi?id=635724
+      if (c === 'rgba(0, 0, 0, 0)' || c === 'transparent') {
+        return this._getBgColor($e.parent());
+      }
+      return c;
+    },
+
+    /**
+     * Gets the top and left properties of an element.
+     *
+     * Convert extraneous values and information into numbers ready for
+     * subtraction.
+     *
+     * @param DOM $e
+     */
+    _getPositionProperties: function ($e) {
+      var p,
         r = {},
         props = [
           'top', 'left', 'bottom', 'right',
@@ -327,27 +327,27 @@ Drupal.edit.FieldDecorationView = Backbone.View.extend({
           'margin-bottom'
         ];
 
-    var propCount = props.length;
-    for (var i = 0; i < propCount; i++) {
-      p = props[i];
-      r[p] = parseInt(this._replaceBlankPosition($e.css(p)), 10);
-    }
-    return r;
-  },
-
-  /**
-   * Replaces blank or 'auto' CSS "position: <value>" values with "0px".
-   *
-   * @param String pos
-   *   (optional) The value for a CSS position declaration.
-   */
-  _replaceBlankPosition: function (pos) {
-    if (pos === 'auto' || !pos) {
-      pos = '0px';
+      var propCount = props.length;
+      for (var i = 0; i < propCount; i++) {
+        p = props[i];
+        r[p] = parseInt(this._replaceBlankPosition($e.css(p)), 10);
+      }
+      return r;
+    },
+
+    /**
+     * Replaces blank or 'auto' CSS "position: <value>" values with "0px".
+     *
+     * @param String pos
+     *   (optional) The value for a CSS position declaration.
+     */
+    _replaceBlankPosition: function (pos) {
+      if (pos === 'auto' || !pos) {
+        pos = '0px';
+      }
+      return pos;
     }
-    return pos;
-  }
 
-});
+  });
 
 })(jQuery, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/FieldToolbarView.js b/core/modules/edit/js/views/FieldToolbarView.js
index 67e567a..55fcf37 100644
--- a/core/modules/edit/js/views/FieldToolbarView.js
+++ b/core/modules/edit/js/views/FieldToolbarView.js
@@ -5,184 +5,184 @@
 
 (function ($, _, Backbone, Drupal) {
 
-"use strict";
-
-Drupal.edit.FieldToolbarView = Backbone.View.extend({
-
-  // The edited element, as indicated by EditorView.getEditedElement().
-  $editedElement: null,
-
-  // A reference to the in-place editor.
-  editorView: null,
-
-  _id: null,
-
-  /**
-   * {@inheritdoc}
-   */
-  initialize: function (options) {
-    this.$editedElement = options.$editedElement;
-    this.editorView = options.editorView;
-    this.$root = this.$el;
-
-    // Generate a DOM-compatible ID for the form container DOM element.
-    this._id = 'edit-toolbar-for-' + this.model.id.replace(/[\/\[\]]/g, '_');
-
-    this.listenTo(this.model, 'change:state', this.stateChange);
-  },
-
-  /**
-   * {@inheritdoc}
-   */
-  render: function () {
-    // Render toolbar and set it as the view's element.
-    this.setElement($(Drupal.theme('editFieldToolbar', {
-      id: this._id
-    })));
-
-    // Attach to the field toolbar $root element in the entity toolbar.
-    this.$el.prependTo(this.$root);
-
-    return this;
-  },
-
-  /**
-   * Determines the actions to take given a change of state.
-   *
-   * @param Drupal.edit.FieldModel model
-   * @param String state
-   *   The state of the associated field. One of Drupal.edit.FieldModel.states.
-   */
-  stateChange: function (model, state) {
-    var from = model.previous('state');
-    var to = state;
-    switch (to) {
-      case 'inactive':
-        break;
-      case 'candidate':
-        // Remove the view's existing element if we went to the 'activating'
-        // state or later, because it will be recreated. Not doing this would
-        // result in memory leaks.
-        if (from !== 'inactive' && from !== 'highlighted') {
-          this.$el.remove();
-          this.setElement();
-        }
-        break;
-      case 'highlighted':
-        break;
-      case 'activating':
-        this.render();
-
-        if (this.editorView.getEditUISettings().fullWidthToolbar) {
-          this.$el.addClass('edit-toolbar-fullwidth');
-        }
-
-        if (this.editorView.getEditUISettings().unifiedToolbar) {
-          this.insertWYSIWYGToolGroups();
-        }
-        break;
-      case 'active':
-        break;
-      case 'changed':
-        break;
-      case 'saving':
-        break;
-      case 'saved':
-        break;
-      case 'invalid':
-        break;
+  "use strict";
+
+  Drupal.edit.FieldToolbarView = Backbone.View.extend({
+
+    // The edited element, as indicated by EditorView.getEditedElement().
+    $editedElement: null,
+
+    // A reference to the in-place editor.
+    editorView: null,
+
+    _id: null,
+
+    /**
+     * {@inheritdoc}
+     */
+    initialize: function (options) {
+      this.$editedElement = options.$editedElement;
+      this.editorView = options.editorView;
+      this.$root = this.$el;
+
+      // Generate a DOM-compatible ID for the form container DOM element.
+      this._id = 'edit-toolbar-for-' + this.model.id.replace(/[\/\[\]]/g, '_');
+
+      this.listenTo(this.model, 'change:state', this.stateChange);
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    render: function () {
+      // Render toolbar and set it as the view's element.
+      this.setElement($(Drupal.theme('editFieldToolbar', {
+        id: this._id
+      })));
+
+      // Attach to the field toolbar $root element in the entity toolbar.
+      this.$el.prependTo(this.$root);
+
+      return this;
+    },
+
+    /**
+     * Determines the actions to take given a change of state.
+     *
+     * @param Drupal.edit.FieldModel model
+     * @param String state
+     *   The state of the associated field. One of Drupal.edit.FieldModel.states.
+     */
+    stateChange: function (model, state) {
+      var from = model.previous('state');
+      var to = state;
+      switch (to) {
+        case 'inactive':
+          break;
+        case 'candidate':
+          // Remove the view's existing element if we went to the 'activating'
+          // state or later, because it will be recreated. Not doing this would
+          // result in memory leaks.
+          if (from !== 'inactive' && from !== 'highlighted') {
+            this.$el.remove();
+            this.setElement();
+          }
+          break;
+        case 'highlighted':
+          break;
+        case 'activating':
+          this.render();
+
+          if (this.editorView.getEditUISettings().fullWidthToolbar) {
+            this.$el.addClass('edit-toolbar-fullwidth');
+          }
+
+          if (this.editorView.getEditUISettings().unifiedToolbar) {
+            this.insertWYSIWYGToolGroups();
+          }
+          break;
+        case 'active':
+          break;
+        case 'changed':
+          break;
+        case 'saving':
+          break;
+        case 'saved':
+          break;
+        case 'invalid':
+          break;
+      }
+    },
+
+    /**
+     * Insert WYSIWYG markup into the associated toolbar.
+     */
+    insertWYSIWYGToolGroups: function () {
+      this.$el
+        .append(Drupal.theme('editToolgroup', {
+          id: this.getFloatedWysiwygToolgroupId(),
+          classes: ['wysiwyg-floated', 'edit-animate-slow', 'edit-animate-invisible', 'edit-animate-delay-veryfast'],
+          buttons: []
+        }))
+        .append(Drupal.theme('editToolgroup', {
+          id: this.getMainWysiwygToolgroupId(),
+          classes: ['wysiwyg-main', 'edit-animate-slow', 'edit-animate-invisible', 'edit-animate-delay-veryfast'],
+          buttons: []
+        }));
+
+      // Animate the toolgroups into visibility.
+      this.show('wysiwyg-floated');
+      this.show('wysiwyg-main');
+    },
+
+    /**
+     * Retrieves the ID for this toolbar's container.
+     *
+     * Only used to make sane hovering behavior possible.
+     *
+     * @return String
+     *   A string that can be used as the ID for this toolbar's container.
+     */
+    getId: function () {
+      return 'edit-toolbar-for-' + this._id;
+    },
+
+    /**
+     * Retrieves the ID for this toolbar's floating WYSIWYG toolgroup.
+     *
+     * Used to provide an abstraction for any WYSIWYG editor to plug in.
+     *
+     * @return String
+     *   A string that can be used as the ID.
+     */
+    getFloatedWysiwygToolgroupId: function () {
+      return 'edit-wysiwyg-floated-toolgroup-for-' + this._id;
+    },
+
+    /**
+     * Retrieves the ID for this toolbar's main WYSIWYG toolgroup.
+     *
+     * Used to provide an abstraction for any WYSIWYG editor to plug in.
+     *
+     * @return String
+     *   A string that can be used as the ID.
+     */
+    getMainWysiwygToolgroupId: function () {
+      return 'edit-wysiwyg-main-toolgroup-for-' + this._id;
+    },
+
+    /**
+     * Finds a toolgroup.
+     *
+     * @param String toolgroup
+     *   A toolgroup name.
+     * @return jQuery
+     */
+    _find: function (toolgroup) {
+      return this.$el.find('.edit-toolgroup.' + toolgroup);
+    },
+
+    /**
+     * Shows a toolgroup.
+     *
+     * @param String toolgroup
+     *   A toolgroup name.
+     */
+    show: function (toolgroup) {
+      var $group = this._find(toolgroup);
+      // Attach a transitionEnd event handler to the toolbar group so that update
+      // events can be triggered after the animations have ended.
+      $group.on(Drupal.edit.util.constants.transitionEnd, function (event) {
+        $group.off(Drupal.edit.util.constants.transitionEnd);
+      });
+      // The call to remove the class and start the animation must be started in
+      // the next animation frame or the event handler attached above won't be
+      // triggered.
+      window.setTimeout(function () {
+        $group.removeClass('edit-animate-invisible');
+      }, 0);
     }
-  },
-
-  /**
-   * Insert WYSIWYG markup into the associated toolbar.
-   */
-  insertWYSIWYGToolGroups: function () {
-    this.$el
-      .append(Drupal.theme('editToolgroup', {
-        id: this.getFloatedWysiwygToolgroupId(),
-        classes: ['wysiwyg-floated', 'edit-animate-slow', 'edit-animate-invisible', 'edit-animate-delay-veryfast'],
-        buttons: []
-      }))
-      .append(Drupal.theme('editToolgroup', {
-        id: this.getMainWysiwygToolgroupId(),
-        classes: ['wysiwyg-main', 'edit-animate-slow', 'edit-animate-invisible', 'edit-animate-delay-veryfast'],
-        buttons: []
-      }));
-
-    // Animate the toolgroups into visibility.
-    this.show('wysiwyg-floated');
-    this.show('wysiwyg-main');
-  },
-
-  /**
-   * Retrieves the ID for this toolbar's container.
-   *
-   * Only used to make sane hovering behavior possible.
-   *
-   * @return String
-   *   A string that can be used as the ID for this toolbar's container.
-   */
-  getId: function () {
-    return 'edit-toolbar-for-' + this._id;
-  },
-
-  /**
-   * Retrieves the ID for this toolbar's floating WYSIWYG toolgroup.
-   *
-   * Used to provide an abstraction for any WYSIWYG editor to plug in.
-   *
-   * @return String
-   *   A string that can be used as the ID.
-   */
-  getFloatedWysiwygToolgroupId: function () {
-    return 'edit-wysiwyg-floated-toolgroup-for-' + this._id;
-  },
-
-  /**
-   * Retrieves the ID for this toolbar's main WYSIWYG toolgroup.
-   *
-   * Used to provide an abstraction for any WYSIWYG editor to plug in.
-   *
-   * @return String
-   *   A string that can be used as the ID.
-   */
-  getMainWysiwygToolgroupId: function () {
-    return 'edit-wysiwyg-main-toolgroup-for-' + this._id;
-  },
-
-  /**
-   * Finds a toolgroup.
-   *
-   * @param String toolgroup
-   *   A toolgroup name.
-   * @return jQuery
-   */
-  _find: function (toolgroup) {
-    return this.$el.find('.edit-toolgroup.' + toolgroup);
-  },
-
-  /**
-   * Shows a toolgroup.
-   *
-   * @param String toolgroup
-   *   A toolgroup name.
-   */
-  show: function (toolgroup) {
-    var $group = this._find(toolgroup);
-    // Attach a transitionEnd event handler to the toolbar group so that update
-    // events can be triggered after the animations have ended.
-    $group.on(Drupal.edit.util.constants.transitionEnd, function (event) {
-      $group.off(Drupal.edit.util.constants.transitionEnd);
-    });
-    // The call to remove the class and start the animation must be started in
-    // the next animation frame or the event handler attached above won't be
-    // triggered.
-    window.setTimeout(function () {
-      $group.removeClass('edit-animate-invisible');
-    }, 0);
-   }
-
-});
+
+  });
 
 })(jQuery, _, Backbone, Drupal);
diff --git a/core/modules/editor/js/editor.admin.js b/core/modules/editor/js/editor.admin.js
index ab0c714..c8b1870 100644
--- a/core/modules/editor/js/editor.admin.js
+++ b/core/modules/editor/js/editor.admin.js
@@ -9,81 +9,81 @@
 
 (function ($, _, Drupal, document) {
 
-"use strict";
+  "use strict";
 
-Drupal.editorConfiguration = {
+  Drupal.editorConfiguration = {
 
-  /**
-   * Must be called by a specific text editor's configuration whenever a feature
-   * is added by the user.
-   *
-   * Triggers the drupalEditorFeatureAdded event on the document, which receives
-   * a Drupal.EditorFeature object.
-   *
-   * @param Drupal.EditorFeature feature
-   *   A text editor feature object.
-   */
-  addedFeature: function (feature) {
-    $(document).trigger('drupalEditorFeatureAdded', feature);
-  },
-
-  /**
-   * Must be called by a specific text editor's configuration whenever a feature
-   * is removed by the user.
-   *
-   * Triggers the drupalEditorFeatureRemoved event on the document, which
-   * receives a Drupal.EditorFeature object.
-   *
-   * @param Drupal.EditorFeature feature
-   *   A text editor feature object.
-   */
-  removedFeature: function (feature) {
-    $(document).trigger('drupalEditorFeatureRemoved', feature);
-  },
+    /**
+     * Must be called by a specific text editor's configuration whenever a feature
+     * is added by the user.
+     *
+     * Triggers the drupalEditorFeatureAdded event on the document, which receives
+     * a Drupal.EditorFeature object.
+     *
+     * @param Drupal.EditorFeature feature
+     *   A text editor feature object.
+     */
+    addedFeature: function (feature) {
+      $(document).trigger('drupalEditorFeatureAdded', feature);
+    },
 
-  /**
-   * Must be called by a specific text editor's configuration whenever a feature
-   * is modified, i.e. has different rules.
-   *
-   * For example when the "Bold" button is configured to use the <b> tag instead
-   * of the <strong> tag.
-   *
-   * Triggers the drupalEditorFeatureModified event on the document, which
-   * receives a Drupal.EditorFeature object.
-   *
-   * @param Drupal.EditorFeature feature
-   *   A text editor feature object.
-   */
-  modifiedFeature: function (feature) {
-    $(document).trigger('drupalEditorFeatureModified', feature);
-  },
+    /**
+     * Must be called by a specific text editor's configuration whenever a feature
+     * is removed by the user.
+     *
+     * Triggers the drupalEditorFeatureRemoved event on the document, which
+     * receives a Drupal.EditorFeature object.
+     *
+     * @param Drupal.EditorFeature feature
+     *   A text editor feature object.
+     */
+    removedFeature: function (feature) {
+      $(document).trigger('drupalEditorFeatureRemoved', feature);
+    },
 
-  /**
-   * May be called by a specific text editor's configuration whenever a feature
-   * is being added, to check whether it would require the filter settings to be
-   * updated.
-   *
-   * The canonical use case is when a text editor is being enabled: preferably
-   * this would not cause the filter settings to be changed; rather, the default
-   * set of buttons (features) for the text editor should adjust itself to not
-   * cause filter setting changes.
-   *
-   * Note: for filters to integrate with this functionality, it is necessary
-   * that they implement Drupal.filterSettingsForEditors[filterID].getRules().
-   *
-   * @param Drupal.EditorFeature feature
-   *   A text editor feature object.
-   * @return Boolean
-   *   Whether the given feature is allowed by the current filters.
-   */
-  featureIsAllowedByFilters: function (feature) {
+    /**
+     * Must be called by a specific text editor's configuration whenever a feature
+     * is modified, i.e. has different rules.
+     *
+     * For example when the "Bold" button is configured to use the <b> tag instead
+     * of the <strong> tag.
+     *
+     * Triggers the drupalEditorFeatureModified event on the document, which
+     * receives a Drupal.EditorFeature object.
+     *
+     * @param Drupal.EditorFeature feature
+     *   A text editor feature object.
+     */
+    modifiedFeature: function (feature) {
+      $(document).trigger('drupalEditorFeatureModified', feature);
+    },
 
     /**
-     * Generate the universe U of possible values that can result from the
-     * feature's rules' requirements.
+     * May be called by a specific text editor's configuration whenever a feature
+     * is being added, to check whether it would require the filter settings to be
+     * updated.
+     *
+     * The canonical use case is when a text editor is being enabled: preferably
+     * this would not cause the filter settings to be changed; rather, the default
+     * set of buttons (features) for the text editor should adjust itself to not
+     * cause filter setting changes.
+     *
+     * Note: for filters to integrate with this functionality, it is necessary
+     * that they implement Drupal.filterSettingsForEditors[filterID].getRules().
      *
-     * This generates an object of this form:
-     *   var universe = {
+     * @param Drupal.EditorFeature feature
+     *   A text editor feature object.
+     * @return Boolean
+     *   Whether the given feature is allowed by the current filters.
+     */
+    featureIsAllowedByFilters: function (feature) {
+
+      /**
+       * Generate the universe U of possible values that can result from the
+       * feature's rules' requirements.
+       *
+       * This generates an object of this form:
+       *   var universe = {
      *     a: {
      *       'touchedByAllowedPropertyRule': false,
      *       'tag': false,
@@ -100,522 +100,522 @@ Drupal.editorConfiguration = {
      *       'attributes:src': false
      *     }
      *   };
-     *
-     * In this example, the given text editor feature resulted in the above
-     * universe, which shows that it must be allowed to generate the a, strong
-     * and img tags. For the a tag, it must be able to set the "href" attribute
-     * and the "external" class. For the strong tag, no further properties are
-     * required. For the img tag, the "src" attribute is required.
-     * The "tag" key is used to track whether that tag was explicitly allowed by
-     * one of the filter's rules. The "touchedByAllowedPropertyRule" key is used
-     * for state tracking that is essential for filterStatusAllowsFeature() to
-     * be able to reason: when all of a filter's rules have been applied, and
-     * none of the forbidden rules matched (which would have resulted in early
-     * termination) yet the universe has not been made empty (which would be the
-     * end result if everything in the universe were explicitly allowed), then
-     * this piece of state data enables us to determine whether a tag whose
-     * properties were not all explicitly allowed are in fact still allowed,
-     * because its tag was explicitly allowed and there were no filter rules
-     * applying "allowed tag property value" restrictions for this particular
-     * tag.
-     *
-     * @see findPropertyValueOnTag()
-     * @see filterStatusAllowsFeature()
-     */
-    function generateUniverseFromFeatureRequirements (feature) {
-      var properties = ['attributes', 'styles', 'classes'];
-      var universe = {};
-
-      for (var r = 0; r < feature.rules.length; r++) {
-        var featureRule = feature.rules[r];
-
-        // For each tag required by this feature rule, create a basic entry in
-        // the universe.
-        var requiredTags = featureRule.required.tags;
-        for (var t = 0; t < requiredTags.length; t++) {
-          universe[requiredTags[t]] = {
-            // Whether this tag was allowed or not.
-            tag: false,
-            // Whether any filter rule that applies to this tag had an allowed
-            // property rule. i.e. will become true if >=1 filter rule has >=1
-            // allowed property rule.
-            touchedByAllowedPropertyRule: false,
-            // Analogous, but for forbidden property rule.
-            touchedBytouchedByForbiddenPropertyRule: false
-          };
-        }
+       *
+       * In this example, the given text editor feature resulted in the above
+       * universe, which shows that it must be allowed to generate the a, strong
+       * and img tags. For the a tag, it must be able to set the "href" attribute
+       * and the "external" class. For the strong tag, no further properties are
+       * required. For the img tag, the "src" attribute is required.
+       * The "tag" key is used to track whether that tag was explicitly allowed by
+       * one of the filter's rules. The "touchedByAllowedPropertyRule" key is used
+       * for state tracking that is essential for filterStatusAllowsFeature() to
+       * be able to reason: when all of a filter's rules have been applied, and
+       * none of the forbidden rules matched (which would have resulted in early
+       * termination) yet the universe has not been made empty (which would be the
+       * end result if everything in the universe were explicitly allowed), then
+       * this piece of state data enables us to determine whether a tag whose
+       * properties were not all explicitly allowed are in fact still allowed,
+       * because its tag was explicitly allowed and there were no filter rules
+       * applying "allowed tag property value" restrictions for this particular
+       * tag.
+       *
+       * @see findPropertyValueOnTag()
+       * @see filterStatusAllowsFeature()
+       */
+      function generateUniverseFromFeatureRequirements(feature) {
+        var properties = ['attributes', 'styles', 'classes'];
+        var universe = {};
+
+        for (var r = 0; r < feature.rules.length; r++) {
+          var featureRule = feature.rules[r];
+
+          // For each tag required by this feature rule, create a basic entry in
+          // the universe.
+          var requiredTags = featureRule.required.tags;
+          for (var t = 0; t < requiredTags.length; t++) {
+            universe[requiredTags[t]] = {
+              // Whether this tag was allowed or not.
+              tag: false,
+              // Whether any filter rule that applies to this tag had an allowed
+              // property rule. i.e. will become true if >=1 filter rule has >=1
+              // allowed property rule.
+              touchedByAllowedPropertyRule: false,
+              // Analogous, but for forbidden property rule.
+              touchedBytouchedByForbiddenPropertyRule: false
+            };
+          }
 
-        // If no required properties are defined for this rule, we can move on
-        // to the next feature.
-        if (emptyProperties(featureRule.required)) {
-          continue;
-        }
+          // If no required properties are defined for this rule, we can move on
+          // to the next feature.
+          if (emptyProperties(featureRule.required)) {
+            continue;
+          }
 
-        // Expand the existing universe, assume that each tags' property value
-        // is disallowed. If the filter rules allow everything in the feature's
-        // universe, then the feature is allowed.
-        for (var p = 0; p < properties.length; p++) {
-          var property = properties[p];
-          for (var pv = 0; pv < featureRule.required[property].length; pv++) {
-            var propertyValue = featureRule.required[property];
-            universe[requiredTags][property + ':' + propertyValue] = false;
+          // Expand the existing universe, assume that each tags' property value
+          // is disallowed. If the filter rules allow everything in the feature's
+          // universe, then the feature is allowed.
+          for (var p = 0; p < properties.length; p++) {
+            var property = properties[p];
+            for (var pv = 0; pv < featureRule.required[property].length; pv++) {
+              var propertyValue = featureRule.required[property];
+              universe[requiredTags][property + ':' + propertyValue] = false;
+            }
           }
         }
-      }
-
-      return universe;
-    }
-
-    /**
-     * Provided a section of a feature or filter rule, checks if no property
-     * values are defined for all properties: attributes, classes and styles.
-     */
-    function emptyProperties (section) {
-      return section.attributes.length === 0 && section.classes.length === 0 && section.styles.length === 0;
-    }
 
-    /**
-     * Calls findPropertyValueOnTag on the given tag for every property value
-     * that is listed in the "propertyValues" parameter. Supports the wildcard
-     * tag.
-     */
-    function findPropertyValuesOnTag (universe, tag, property, propertyValues, allowing) {
-      // Detect the wildcard case.
-      if (tag === '*') {
-        return findPropertyValuesOnAllTags(universe, property, propertyValues, allowing);
+        return universe;
       }
 
-      var atLeastOneFound = false;
-      _.each(propertyValues, function (propertyValue) {
-        if (findPropertyValueOnTag(universe, tag, property, propertyValue, allowing)) {
-          atLeastOneFound = true;
-        }
-      });
-      return atLeastOneFound;
-    }
-
-    /**
-     * Calls findPropertyValuesOnAllTags for all tags in the universe.
-     */
-    function findPropertyValuesOnAllTags (universe, property, propertyValues, allowing) {
-      var atLeastOneFound = false;
-      _.each(_.keys(universe), function (tag) {
-        if (findPropertyValuesOnTag(universe, tag, property, propertyValues, allowing)) {
-          atLeastOneFound = true;
-        }
-      });
-      return atLeastOneFound;
-    }
-
-    /**
-     * Finds out if a specific property value (potentially containing wildcards)
-     * exists on the given tag. When the "allowing" parameter equals true, the
-     * universe will be updated if that specific property value exists.
-     * Returns true if found, false otherwise.
-     */
-    function findPropertyValueOnTag (universe, tag, property, propertyValue, allowing) {
-      // If the tag does not exist in the universe, then it definitely can't
-      // have this specific property value.
-      if (!_.has(universe, tag)) {
-        return false;
+      /**
+       * Provided a section of a feature or filter rule, checks if no property
+       * values are defined for all properties: attributes, classes and styles.
+       */
+      function emptyProperties(section) {
+        return section.attributes.length === 0 && section.classes.length === 0 && section.styles.length === 0;
       }
 
-      var key = property + ':' + propertyValue;
-
-      // Track whether a tag was touched by a filter rule that allows specific
-      // property values on this particular tag.
-      // @see generateUniverseFromFeatureRequirements
-      if (allowing) {
-        universe[tag].touchedByAllowedPropertyRule = true;
-      }
+      /**
+       * Calls findPropertyValueOnTag on the given tag for every property value
+       * that is listed in the "propertyValues" parameter. Supports the wildcard
+       * tag.
+       */
+      function findPropertyValuesOnTag(universe, tag, property, propertyValues, allowing) {
+        // Detect the wildcard case.
+        if (tag === '*') {
+          return findPropertyValuesOnAllTags(universe, property, propertyValues, allowing);
+        }
 
-      // The simple case: no wildcard in property value.
-      if (_.indexOf(propertyValue, '*') === -1) {
-        if (_.has(universe, tag) && _.has(universe[tag], key)) {
-          if (allowing) {
-            universe[tag][key] = true;
+        var atLeastOneFound = false;
+        _.each(propertyValues, function (propertyValue) {
+          if (findPropertyValueOnTag(universe, tag, property, propertyValue, allowing)) {
+            atLeastOneFound = true;
           }
-          return true;
-        }
-        return false;
+        });
+        return atLeastOneFound;
       }
-      // The complex case: wildcard in property value.
-      else {
+
+      /**
+       * Calls findPropertyValuesOnAllTags for all tags in the universe.
+       */
+      function findPropertyValuesOnAllTags(universe, property, propertyValues, allowing) {
         var atLeastOneFound = false;
-        var regex = key.replace(/\*/g, "[^ ]*");
-        _.each(_.keys(universe[tag]), function (key) {
-          if (key.match(regex)) {
+        _.each(_.keys(universe), function (tag) {
+          if (findPropertyValuesOnTag(universe, tag, property, propertyValues, allowing)) {
             atLeastOneFound = true;
-            if (allowing) {
-              universe[tag][key] = true;
-            }
           }
         });
         return atLeastOneFound;
       }
-    }
 
-    /**
-     * Deletes a tag from the universe if the tag itself and each of its
-     * properties are marked as allowed.
-     */
-    function deleteFromUniverseIfAllowed (universe, tag) {
-      // Detect the wildcard case.
-      if (tag === '*') {
-        return deleteAllTagsFromUniverseIfAllowed(universe);
-      }
-      if (_.has(universe, tag) && _.every(_.omit(universe[tag], 'touchedByAllowedPropertyRule'))) {
-        delete universe[tag];
-        return true;
-      }
-      return false;
-    }
+      /**
+       * Finds out if a specific property value (potentially containing wildcards)
+       * exists on the given tag. When the "allowing" parameter equals true, the
+       * universe will be updated if that specific property value exists.
+       * Returns true if found, false otherwise.
+       */
+      function findPropertyValueOnTag(universe, tag, property, propertyValue, allowing) {
+        // If the tag does not exist in the universe, then it definitely can't
+        // have this specific property value.
+        if (!_.has(universe, tag)) {
+          return false;
+        }
 
-    /**
-     * Calls deleteFromUniverseIfAllowed for all tags in the universe.
-     */
-    function deleteAllTagsFromUniverseIfAllowed (universe) {
-      var atLeastOneDeleted = false;
-      _.each(_.keys(universe), function (tag) {
-        if (deleteFromUniverseIfAllowed(universe, tag)) {
-          atLeastOneDeleted = true;
+        var key = property + ':' + propertyValue;
+
+        // Track whether a tag was touched by a filter rule that allows specific
+        // property values on this particular tag.
+        // @see generateUniverseFromFeatureRequirements
+        if (allowing) {
+          universe[tag].touchedByAllowedPropertyRule = true;
         }
-      });
-      return atLeastOneDeleted;
-    }
 
-    /**
-     * Checks if any filter rule forbids either a tag or a tag property value
-     * that exists in the universe.
-     */
-    function anyForbiddenFilterRuleMatches (universe, filterStatus) {
-      var properties = ['attributes', 'styles', 'classes'];
-
-      // Check if a tag in the universe is forbidden.
-      var allRequiredTags = _.keys(universe);
-      var filterRule, i;
-      for (i = 0; i < filterStatus.rules.length; i++) {
-        filterRule = filterStatus.rules[i];
-        if (filterRule.allow === false) {
-          if (_.intersection(allRequiredTags, filterRule.tags).length > 0) {
+        // The simple case: no wildcard in property value.
+        if (_.indexOf(propertyValue, '*') === -1) {
+          if (_.has(universe, tag) && _.has(universe[tag], key)) {
+            if (allowing) {
+              universe[tag][key] = true;
+            }
             return true;
           }
+          return false;
         }
-      }
-
-      // Check if a property value of a tag in the universe is forbidden.
-      // For all filter rules…
-      var j, k;
-      for (i = 0; i < filterStatus.rules.length; i++) {
-        filterRule = filterStatus.rules[i];
-        // … if there are tags with restricted property values …
-        if (filterRule.restrictedTags.tags.length && !emptyProperties(filterRule.restrictedTags.forbidden)) {
-          // … for all those tags …
-          for (j = 0; j < filterRule.restrictedTags.tags.length; j++) {
-            var tag = filterRule.restrictedTags.tags[j];
-            // … then iterate over all properties …
-            for (k = 0; k < properties.length; k++) {
-              var property = properties[k];
-              // … and return true if just one of the forbidden property values
-              // for this tag and property is listed in the universe.
-              if (findPropertyValuesOnTag(universe, tag, property, filterRule.restrictedTags.forbidden[property], false)) {
-                return true;
+        // The complex case: wildcard in property value.
+        else {
+          var atLeastOneFound = false;
+          var regex = key.replace(/\*/g, "[^ ]*");
+          _.each(_.keys(universe[tag]), function (key) {
+            if (key.match(regex)) {
+              atLeastOneFound = true;
+              if (allowing) {
+                universe[tag][key] = true;
               }
             }
-          }
+          });
+          return atLeastOneFound;
         }
       }
 
-      return false;
-    }
+      /**
+       * Deletes a tag from the universe if the tag itself and each of its
+       * properties are marked as allowed.
+       */
+      function deleteFromUniverseIfAllowed(universe, tag) {
+        // Detect the wildcard case.
+        if (tag === '*') {
+          return deleteAllTagsFromUniverseIfAllowed(universe);
+        }
+        if (_.has(universe, tag) && _.every(_.omit(universe[tag], 'touchedByAllowedPropertyRule'))) {
+          delete universe[tag];
+          return true;
+        }
+        return false;
+      }
 
-    /**
-     * Applies every filter rule's explicit allowing of a tag or a tag property
-     * value to the universe. Whenever both the tag and all of its required
-     * property values are marked as explicitly allowed, they are deleted from
-     * the universe.
-     */
-    function markAllowedTagsAndPropertyValues (universe, filterStatus) {
-      var properties = ['attributes', 'styles', 'classes'];
-
-      // Check if a tag in the universe is allowed.
-      var filterRule, tag, i, j;
-      for (i = 0; !_.isEmpty(universe) && i < filterStatus.rules.length; i++) {
-        filterRule = filterStatus.rules[i];
-        if (filterRule.allow === true) {
-          for (j = 0; !_.isEmpty(universe) && j < filterRule.tags.length; j++) {
-            tag = filterRule.tags[j];
-            if (_.has(universe, tag)) {
-              universe[tag].tag = true;
-              deleteFromUniverseIfAllowed(universe, tag);
+      /**
+       * Calls deleteFromUniverseIfAllowed for all tags in the universe.
+       */
+      function deleteAllTagsFromUniverseIfAllowed(universe) {
+        var atLeastOneDeleted = false;
+        _.each(_.keys(universe), function (tag) {
+          if (deleteFromUniverseIfAllowed(universe, tag)) {
+            atLeastOneDeleted = true;
+          }
+        });
+        return atLeastOneDeleted;
+      }
+
+      /**
+       * Checks if any filter rule forbids either a tag or a tag property value
+       * that exists in the universe.
+       */
+      function anyForbiddenFilterRuleMatches(universe, filterStatus) {
+        var properties = ['attributes', 'styles', 'classes'];
+
+        // Check if a tag in the universe is forbidden.
+        var allRequiredTags = _.keys(universe);
+        var filterRule, i;
+        for (i = 0; i < filterStatus.rules.length; i++) {
+          filterRule = filterStatus.rules[i];
+          if (filterRule.allow === false) {
+            if (_.intersection(allRequiredTags, filterRule.tags).length > 0) {
+              return true;
             }
           }
         }
-      }
 
-      // Check if a property value of a tag in the universe is allowed.
-      // For all filter rules…
-      var k;
-      for (i = 0; !_.isEmpty(universe) && i < filterStatus.rules.length; i++) {
-        filterRule = filterStatus.rules[i];
-        // … if there are tags with restricted property values …
-        if (filterRule.restrictedTags.tags.length && !emptyProperties(filterRule.restrictedTags.allowed)) {
-          // … for all those tags …
-          for (j = 0; !_.isEmpty(universe) && j < filterRule.restrictedTags.tags.length; j++) {
-            tag = filterRule.restrictedTags.tags[j];
-            // … then iterate over all properties …
-            for (k = 0; k < properties.length; k++) {
-              var property = properties[k];
-              // … and try to delete this tag from the universe if just one of
-              // the allowed property values for this tag and property is listed
-              // in the universe. (Because everything might be allowed now.)
-              if (findPropertyValuesOnTag(universe, tag, property, filterRule.restrictedTags.allowed[property], true)) {
-                deleteFromUniverseIfAllowed(universe, tag);
+        // Check if a property value of a tag in the universe is forbidden.
+        // For all filter rules…
+        var j, k;
+        for (i = 0; i < filterStatus.rules.length; i++) {
+          filterRule = filterStatus.rules[i];
+          // … if there are tags with restricted property values …
+          if (filterRule.restrictedTags.tags.length && !emptyProperties(filterRule.restrictedTags.forbidden)) {
+            // … for all those tags …
+            for (j = 0; j < filterRule.restrictedTags.tags.length; j++) {
+              var tag = filterRule.restrictedTags.tags[j];
+              // … then iterate over all properties …
+              for (k = 0; k < properties.length; k++) {
+                var property = properties[k];
+                // … and return true if just one of the forbidden property values
+                // for this tag and property is listed in the universe.
+                if (findPropertyValuesOnTag(universe, tag, property, filterRule.restrictedTags.forbidden[property], false)) {
+                  return true;
+                }
               }
             }
           }
         }
-      }
-    }
 
-    /**
-     * Checks whether the current status of a filter allows a specific feature
-     * by building the universe of potential values from the feature's require-
-     * ments and then checking whether anything in the filter prevents that.
-     *
-     * @see generateUniverseFromFeatureRequirements()
-     */
-    function filterStatusAllowsFeature (filterStatus, feature) {
-      // An inactive filter by definition allows the feature.
-      if (!filterStatus.active) {
-        return true;
+        return false;
       }
 
-      // A feature that specifies no rules has no HTML requirements and is hence
-      // allowed by definition.
-      if (feature.rules.length === 0) {
-        return true;
-      }
+      /**
+       * Applies every filter rule's explicit allowing of a tag or a tag property
+       * value to the universe. Whenever both the tag and all of its required
+       * property values are marked as explicitly allowed, they are deleted from
+       * the universe.
+       */
+      function markAllowedTagsAndPropertyValues(universe, filterStatus) {
+        var properties = ['attributes', 'styles', 'classes'];
+
+        // Check if a tag in the universe is allowed.
+        var filterRule, tag, i, j;
+        for (i = 0; !_.isEmpty(universe) && i < filterStatus.rules.length; i++) {
+          filterRule = filterStatus.rules[i];
+          if (filterRule.allow === true) {
+            for (j = 0; !_.isEmpty(universe) && j < filterRule.tags.length; j++) {
+              tag = filterRule.tags[j];
+              if (_.has(universe, tag)) {
+                universe[tag].tag = true;
+                deleteFromUniverseIfAllowed(universe, tag);
+              }
+            }
+          }
+        }
 
-      // Analogously for a filter that specifies no rules.
-      if (filterStatus.rules.length === 0) {
-        return true;
+        // Check if a property value of a tag in the universe is allowed.
+        // For all filter rules…
+        var k;
+        for (i = 0; !_.isEmpty(universe) && i < filterStatus.rules.length; i++) {
+          filterRule = filterStatus.rules[i];
+          // … if there are tags with restricted property values …
+          if (filterRule.restrictedTags.tags.length && !emptyProperties(filterRule.restrictedTags.allowed)) {
+            // … for all those tags …
+            for (j = 0; !_.isEmpty(universe) && j < filterRule.restrictedTags.tags.length; j++) {
+              tag = filterRule.restrictedTags.tags[j];
+              // … then iterate over all properties …
+              for (k = 0; k < properties.length; k++) {
+                var property = properties[k];
+                // … and try to delete this tag from the universe if just one of
+                // the allowed property values for this tag and property is listed
+                // in the universe. (Because everything might be allowed now.)
+                if (findPropertyValuesOnTag(universe, tag, property, filterRule.restrictedTags.allowed[property], true)) {
+                  deleteFromUniverseIfAllowed(universe, tag);
+                }
+              }
+            }
+          }
+        }
       }
 
-      // Generate the universe U of possible values that can result from the
-      // feature's rules' requirements.
-      var universe = generateUniverseFromFeatureRequirements(feature);
+      /**
+       * Checks whether the current status of a filter allows a specific feature
+       * by building the universe of potential values from the feature's require-
+       * ments and then checking whether anything in the filter prevents that.
+       *
+       * @see generateUniverseFromFeatureRequirements()
+       */
+      function filterStatusAllowsFeature(filterStatus, feature) {
+        // An inactive filter by definition allows the feature.
+        if (!filterStatus.active) {
+          return true;
+        }
 
-      // If anything that is in the universe (and is thus required by the
-      // feature) is forbidden by any of the filter's rules, then this filter
-      // does not allow this feature.
-      if (anyForbiddenFilterRuleMatches(universe, filterStatus)) {
-        return false;
-      }
+        // A feature that specifies no rules has no HTML requirements and is hence
+        // allowed by definition.
+        if (feature.rules.length === 0) {
+          return true;
+        }
 
-      // Mark anything in the universe that is allowed by any of the filter's
-      // rules as allowed. If everything is explicitly allowed, then the
-      // universe will become empty.
-      markAllowedTagsAndPropertyValues(universe, filterStatus);
-
-      // If there was at least one filter rule allowing tags, then everything in
-      // the universe must be allowed for this feature to be allowed, and thus
-      // by now it must be empty.
-      // However, it is still possible that the filter allows the feature, due
-      // to no rules for allowing tag property values and/or rules for
-      // forbidding tag property values. For details: see the comments below.
-      //
-      // @see generateUniverseFromFeatureRequirements()
-      if (_.some(_.pluck(filterStatus.rules, 'allow'))) {
-        // If the universe is empty, then everything was explicitly allowed and
-        // our job is done: this filter allows this feature!
-        if (_.isEmpty(universe)) {
+        // Analogously for a filter that specifies no rules.
+        if (filterStatus.rules.length === 0) {
           return true;
         }
-        // Otherwise, it is still possible that this feature is allowed.
-        else {
-          // Every tag must be explicitly allowed if there are filter rules
-          // doing tag whitelisting.
-          if (!_.every(_.pluck(universe, 'tag'))) {
-            return false;
+
+        // Generate the universe U of possible values that can result from the
+        // feature's rules' requirements.
+        var universe = generateUniverseFromFeatureRequirements(feature);
+
+        // If anything that is in the universe (and is thus required by the
+        // feature) is forbidden by any of the filter's rules, then this filter
+        // does not allow this feature.
+        if (anyForbiddenFilterRuleMatches(universe, filterStatus)) {
+          return false;
+        }
+
+        // Mark anything in the universe that is allowed by any of the filter's
+        // rules as allowed. If everything is explicitly allowed, then the
+        // universe will become empty.
+        markAllowedTagsAndPropertyValues(universe, filterStatus);
+
+        // If there was at least one filter rule allowing tags, then everything in
+        // the universe must be allowed for this feature to be allowed, and thus
+        // by now it must be empty.
+        // However, it is still possible that the filter allows the feature, due
+        // to no rules for allowing tag property values and/or rules for
+        // forbidding tag property values. For details: see the comments below.
+        //
+        // @see generateUniverseFromFeatureRequirements()
+        if (_.some(_.pluck(filterStatus.rules, 'allow'))) {
+          // If the universe is empty, then everything was explicitly allowed and
+          // our job is done: this filter allows this feature!
+          if (_.isEmpty(universe)) {
+            return true;
           }
-          // Every tag was explicitly allowed, but since the universe is not
-          // empty, one or more tag properties are disallowed. However, if only
-          // blacklisting of tag properties was applied to these tags, and no
-          // whitelisting was ever applied, then it's still fine: since none of
-          // the tag properties were blacklisted, we got to this point, and since
-          // no whitelisting was applied, it doesn't matter that the properties:
-          // this could never have happened anyway.
-          // It's only this late that we can know this for certain.
+          // Otherwise, it is still possible that this feature is allowed.
           else {
-            var tags = _.keys(universe);
-            // Figure out if there was any rule applying whitelisting tag
-            // restrictions to each of the remaining tags.
-            for (var i = 0; i < tags.length; i++) {
-              var tag = tags[i];
-              if (_.has(universe, tag)) {
-                if (universe[tag].touchedByAllowedPropertyRule === false) {
-                  delete universe[tag];
+            // Every tag must be explicitly allowed if there are filter rules
+            // doing tag whitelisting.
+            if (!_.every(_.pluck(universe, 'tag'))) {
+              return false;
+            }
+            // Every tag was explicitly allowed, but since the universe is not
+            // empty, one or more tag properties are disallowed. However, if only
+            // blacklisting of tag properties was applied to these tags, and no
+            // whitelisting was ever applied, then it's still fine: since none of
+            // the tag properties were blacklisted, we got to this point, and since
+            // no whitelisting was applied, it doesn't matter that the properties:
+            // this could never have happened anyway.
+            // It's only this late that we can know this for certain.
+            else {
+              var tags = _.keys(universe);
+              // Figure out if there was any rule applying whitelisting tag
+              // restrictions to each of the remaining tags.
+              for (var i = 0; i < tags.length; i++) {
+                var tag = tags[i];
+                if (_.has(universe, tag)) {
+                  if (universe[tag].touchedByAllowedPropertyRule === false) {
+                    delete universe[tag];
+                  }
                 }
               }
+              return _.isEmpty(universe);
             }
-            return _.isEmpty(universe);
           }
         }
+        // Otherwise, if all filter rules were doing blacklisting, then the sole
+        // fact that we got to this point indicates that this filter allows for
+        // everything that is required for this feature.
+        else {
+          return true;
+        }
       }
-      // Otherwise, if all filter rules were doing blacklisting, then the sole
-      // fact that we got to this point indicates that this filter allows for
-      // everything that is required for this feature.
-      else {
-        return true;
-      }
-    }
 
-    // If any filter's current status forbids the editor feature, return false.
-    Drupal.filterConfiguration.update();
-    for (var filterID in Drupal.filterConfiguration.statuses) {
-      if (Drupal.filterConfiguration.statuses.hasOwnProperty(filterID)) {
-        var filterStatus = Drupal.filterConfiguration.statuses[filterID];
-        if (!(filterStatusAllowsFeature(filterStatus, feature))) {
-          return false;
+      // If any filter's current status forbids the editor feature, return false.
+      Drupal.filterConfiguration.update();
+      for (var filterID in Drupal.filterConfiguration.statuses) {
+        if (Drupal.filterConfiguration.statuses.hasOwnProperty(filterID)) {
+          var filterStatus = Drupal.filterConfiguration.statuses[filterID];
+          if (!(filterStatusAllowsFeature(filterStatus, feature))) {
+            return false;
+          }
         }
       }
-    }
 
-    return true;
-  }
-};
+      return true;
+    }
+  };
 
-/**
- * A text editor feature object. Initialized with the feature name.
- *
- * Contains a set of HTML rules (Drupal.EditorFeatureHTMLRule objects) that
- * describe which HTML tags, attributes, styles and classes are required (i.e.
- * essential for the feature to function at all) and which are allowed (i.e. the
- * feature may generate this, but they're not essential).
- *
- * It is necessary to allow for multiple HTML rules per feature: with just one
- * HTML rule per feature, there is not enough expressiveness to describe certain
- * cases. For example: a "table" feature would probably require the <table> tag,
- * and might allow for e.g. the "summary" attribute on that tag. However, the
- * table feature would also require the <tr> and <td> tags, but it doesn't make
- * sense to allow for a "summary" attribute on these tags. Hence these would
- * need to be split in two separate rules.
- *
- * HTML rules must be added with the addHTMLRule() method. A feature that has
- * zero HTML rules does not create or modify HTML.
- *
- * @param String name
- *   The name of the feature.
- *
- * @see Drupal.EditorFeatureHTMLRule
- */
-Drupal.EditorFeature = function (name) {
-  this.name = name;
-  this.rules = [];
-};
+  /**
+   * A text editor feature object. Initialized with the feature name.
+   *
+   * Contains a set of HTML rules (Drupal.EditorFeatureHTMLRule objects) that
+   * describe which HTML tags, attributes, styles and classes are required (i.e.
+   * essential for the feature to function at all) and which are allowed (i.e. the
+   * feature may generate this, but they're not essential).
+   *
+   * It is necessary to allow for multiple HTML rules per feature: with just one
+   * HTML rule per feature, there is not enough expressiveness to describe certain
+   * cases. For example: a "table" feature would probably require the <table> tag,
+   * and might allow for e.g. the "summary" attribute on that tag. However, the
+   * table feature would also require the <tr> and <td> tags, but it doesn't make
+   * sense to allow for a "summary" attribute on these tags. Hence these would
+   * need to be split in two separate rules.
+   *
+   * HTML rules must be added with the addHTMLRule() method. A feature that has
+   * zero HTML rules does not create or modify HTML.
+   *
+   * @param String name
+   *   The name of the feature.
+   *
+   * @see Drupal.EditorFeatureHTMLRule
+   */
+  Drupal.EditorFeature = function (name) {
+    this.name = name;
+    this.rules = [];
+  };
 
-/**
- * Adds a HTML rule to the list of HTML rules for this feature.
- *
- * @param Drupal.editorFeatureHTMLRule rule
- *   A text editor feature HTML rule.
- */
-Drupal.EditorFeature.prototype.addHTMLRule = function (rule) {
-  this.rules.push(rule);
-};
+  /**
+   * Adds a HTML rule to the list of HTML rules for this feature.
+   *
+   * @param Drupal.editorFeatureHTMLRule rule
+   *   A text editor feature HTML rule.
+   */
+  Drupal.EditorFeature.prototype.addHTMLRule = function (rule) {
+    this.rules.push(rule);
+  };
 
-/**
- * Constructor for an editor feature HTML rule. Intended to be used in
- * combination with Drupal.EditorFeature.
- *
- * A text editor feature rule object describes both
- *  - required HTML tags, attributes, styles and classes: without these, the
- *    text editor feature is unable to function. It's possible that a
- *  - allowed HTML tags, attributes, styles and classes: these are optional in
- *    the strictest sense, but it is possible that the feature generates them.
- *
- * The structure can be very clearly seen below: there's a "required" and an
- * "allowed" key. For each of those, there are objects with the "tags",
- * "attributes", "styles" and "classes" keys. For all these keys the values are
- * initialized to the empty array. List each possible value as an array value.
- * Besides the "required" and "allowed" keys, there's an optional "raw" key: it
- * allows text editor implementations to optionally pass in their raw
- * representation instead of the Drupal-defined representation for HTML rules.
- *
- * Examples:
- *  - tags: ['<a>']
- *  - attributes: ['href', 'alt']
- *  - styles: ['color', 'text-decoration']
- *  - classes: ['external', 'internal']
- */
-Drupal.EditorFeatureHTMLRule = function () {
-  this.required = { tags: [], attributes: [], styles: [], classes: [] };
-  this.allowed = { tags: [], attributes: [], styles: [], classes: [] };
-  this.raw = null;
-};
+  /**
+   * Constructor for an editor feature HTML rule. Intended to be used in
+   * combination with Drupal.EditorFeature.
+   *
+   * A text editor feature rule object describes both
+   *  - required HTML tags, attributes, styles and classes: without these, the
+   *    text editor feature is unable to function. It's possible that a
+   *  - allowed HTML tags, attributes, styles and classes: these are optional in
+   *    the strictest sense, but it is possible that the feature generates them.
+   *
+   * The structure can be very clearly seen below: there's a "required" and an
+   * "allowed" key. For each of those, there are objects with the "tags",
+   * "attributes", "styles" and "classes" keys. For all these keys the values are
+   * initialized to the empty array. List each possible value as an array value.
+   * Besides the "required" and "allowed" keys, there's an optional "raw" key: it
+   * allows text editor implementations to optionally pass in their raw
+   * representation instead of the Drupal-defined representation for HTML rules.
+   *
+   * Examples:
+   *  - tags: ['<a>']
+   *  - attributes: ['href', 'alt']
+   *  - styles: ['color', 'text-decoration']
+   *  - classes: ['external', 'internal']
+   */
+  Drupal.EditorFeatureHTMLRule = function () {
+    this.required = { tags: [], attributes: [], styles: [], classes: [] };
+    this.allowed = { tags: [], attributes: [], styles: [], classes: [] };
+    this.raw = null;
+  };
 
-/**
- * Constructor for a text filter status object. Initialized with the filter ID.
- *
- * Indicates whether the text filter is currently active (enabled) or not.
- *
- * Contains a set of HTML rules (Drupal.FilterHTMLRule objects) that describe
- * which HTML tags are allowed or forbidden. They can also describe for a set of
- * tags (or all tags) which attributes, styles and classes are allowed and which
- * are forbidden.
- *
- * It is necessary to allow for multiple HTML rules per feature, for analogous
- * reasons as Drupal.EditorFeature.
- *
- * HTML rules must be added with the addHTMLRule() method. A filter that has
- * zero HTML rules does not disallow any HTML.
- *
- * @param String name
- *   The name of the feature.
- *
- * @see Drupal.FilterHTMLRule
- */
-Drupal.FilterStatus = function (name) {
-  this.name = name;
-  this.active = false;
-  this.rules = [];
-};
+  /**
+   * Constructor for a text filter status object. Initialized with the filter ID.
+   *
+   * Indicates whether the text filter is currently active (enabled) or not.
+   *
+   * Contains a set of HTML rules (Drupal.FilterHTMLRule objects) that describe
+   * which HTML tags are allowed or forbidden. They can also describe for a set of
+   * tags (or all tags) which attributes, styles and classes are allowed and which
+   * are forbidden.
+   *
+   * It is necessary to allow for multiple HTML rules per feature, for analogous
+   * reasons as Drupal.EditorFeature.
+   *
+   * HTML rules must be added with the addHTMLRule() method. A filter that has
+   * zero HTML rules does not disallow any HTML.
+   *
+   * @param String name
+   *   The name of the feature.
+   *
+   * @see Drupal.FilterHTMLRule
+   */
+  Drupal.FilterStatus = function (name) {
+    this.name = name;
+    this.active = false;
+    this.rules = [];
+  };
 
-/**
- * Adds a HTML rule to the list of HTML rules for this filter.
- *
- * @param Drupal.FilterHTMLRule rule
- *   A text filter HTML rule.
- */
-Drupal.FilterStatus.prototype.addHTMLRule = function (rule) {
-  this.rules.push(rule);
-};
+  /**
+   * Adds a HTML rule to the list of HTML rules for this filter.
+   *
+   * @param Drupal.FilterHTMLRule rule
+   *   A text filter HTML rule.
+   */
+  Drupal.FilterStatus.prototype.addHTMLRule = function (rule) {
+    this.rules.push(rule);
+  };
 
-/**
- * A text filter HTML rule object. Intended to be used in combination with
- * Drupal.FilterStatus.
- *
- * A text filter rule object describes
- *  1. allowed or forbidden tags: (optional) whitelist or blacklist HTML tags
- *  2. restricted tag properties: (optional) whitelist or blacklist attributes,
- *     styles and classes on a set of HTML tags.
- *
- * Typically, each text filter rule object does either 1 or 2, not both.
- *
- * The structure can be very clearly seen below:
- *  1. use the "tags" key to list HTML tags, and set the "allow" key to either
- *     true (to allow these HTML tags) or false (to forbid these HTML tags). If
- *     you leave the "tags" key's default value (the empty array), no
- *     restrictions are applied.
- *  2. all nested within the "restrictedTags" key: use the "tags" subkey to list
- *     HTML tags to which you want to apply property restrictions, then use the
- *     "allowed" subkey to whitelist specific property values, and similarly use
- *     the "forbidden" subkey to blacklist specific property values.
- *
- * Examples:
- *  - Whitelist the "p", "strong" and "a" HTML tags:
- *    {
+  /**
+   * A text filter HTML rule object. Intended to be used in combination with
+   * Drupal.FilterStatus.
+   *
+   * A text filter rule object describes
+   *  1. allowed or forbidden tags: (optional) whitelist or blacklist HTML tags
+   *  2. restricted tag properties: (optional) whitelist or blacklist attributes,
+   *     styles and classes on a set of HTML tags.
+   *
+   * Typically, each text filter rule object does either 1 or 2, not both.
+   *
+   * The structure can be very clearly seen below:
+   *  1. use the "tags" key to list HTML tags, and set the "allow" key to either
+   *     true (to allow these HTML tags) or false (to forbid these HTML tags). If
+   *     you leave the "tags" key's default value (the empty array), no
+   *     restrictions are applied.
+   *  2. all nested within the "restrictedTags" key: use the "tags" subkey to list
+   *     HTML tags to which you want to apply property restrictions, then use the
+   *     "allowed" subkey to whitelist specific property values, and similarly use
+   *     the "forbidden" subkey to blacklist specific property values.
+   *
+   * Examples:
+   *  - Whitelist the "p", "strong" and "a" HTML tags:
+   *    {
  *      tags: ['p', 'strong', 'a'],
  *      allow: true,
  *      restrictedTags: {
@@ -624,9 +624,9 @@ Drupal.FilterStatus.prototype.addHTMLRule = function (rule) {
  *        forbidden: { attributes: [], styles: [], classes: [] }
  *      }
  *    }
- *  - For the "a" HTML tag, only allow the "href" attribute and the "external"
- *    class and disallow the "target" attribute.
- *    {
+   *  - For the "a" HTML tag, only allow the "href" attribute and the "external"
+   *    class and disallow the "target" attribute.
+   *    {
  *      tags: [],
  *      allow: null,
  *      restrictedTags: {
@@ -635,9 +635,9 @@ Drupal.FilterStatus.prototype.addHTMLRule = function (rule) {
  *        forbidden: { attributes: ['target'], styles: [], classes: [] }
  *      }
  *    }
- *  - For all tags, allow the "data-*" attribute (that is, any attribute that
- *    begins with "data-").
- *    {
+   *  - For all tags, allow the "data-*" attribute (that is, any attribute that
+   *    begins with "data-").
+   *    {
  *      tags: [],
  *      allow: null,
  *      restrictedTags: {
@@ -646,87 +646,87 @@ Drupal.FilterStatus.prototype.addHTMLRule = function (rule) {
  *        forbidden: { attributes: [], styles: [], classes: [] }
  *      }
  *    }
- */
-Drupal.FilterHTMLRule = function () {
-  return {
-    // Allow or forbid tags.
-    tags: [],
-    allow: null,
-    // Apply restrictions to properties set on tags.
-    restrictedTags: {
+   */
+  Drupal.FilterHTMLRule = function () {
+    return {
+      // Allow or forbid tags.
       tags: [],
-      allowed: { attributes: [], styles: [], classes: [] },
-      forbidden: { attributes: [], styles: [], classes: [] }
-    }
+      allow: null,
+      // Apply restrictions to properties set on tags.
+      restrictedTags: {
+        tags: [],
+        allowed: { attributes: [], styles: [], classes: [] },
+        forbidden: { attributes: [], styles: [], classes: [] }
+      }
+    };
   };
-};
-
-/**
- * Tracks the configuration of all text filters in Drupal.FilterStatus objects
- * for Drupal.editorConfiguration.featureIsAllowedByFilters().
- */
-Drupal.filterConfiguration = {
 
   /**
-   * Drupal.FilterStatus objects, keyed by filter ID.
+   * Tracks the configuration of all text filters in Drupal.FilterStatus objects
+   * for Drupal.editorConfiguration.featureIsAllowedByFilters().
    */
-  statuses: {},
+  Drupal.filterConfiguration = {
 
-  /**
-   * Live filter setting parsers, keyed by filter ID, for those filters that
-   * implement it.
-   *
-   * Filters should load the implementing JavaScript on the filter configuration
-   * form and implement Drupal.filterSettings[filterID].getRules(), which should
-   * return an array of Drupal.FilterHTMLRule objects.
-   */
-  liveSettingParsers: {},
+    /**
+     * Drupal.FilterStatus objects, keyed by filter ID.
+     */
+    statuses: {},
 
-  /**
-   * Updates all Drupal.FilterStatus objects to reflect the current state.
-   *
-   * Automatically checks whether a filter is currently enabled or not. To
-   * support more finegrained
-   *
-   * If a filter implements a live setting parser, then that will be used to
-   * keep the HTML rules for the Drupal.FilterStatus object up-to-date.
-   */
-  update: function () {
-    for (var filterID in Drupal.filterConfiguration.statuses) {
-      if (Drupal.filterConfiguration.statuses.hasOwnProperty(filterID)) {
-        // Update status.
-        Drupal.filterConfiguration.statuses[filterID].active = $('[name="filters[' + filterID + '][status]"]').is(':checked');
-
-        // Update current rules.
-        if (Drupal.filterConfiguration.liveSettingParsers[filterID]) {
-          Drupal.filterConfiguration.statuses[filterID].rules = Drupal.filterConfiguration.liveSettingParsers[filterID].getRules();
+    /**
+     * Live filter setting parsers, keyed by filter ID, for those filters that
+     * implement it.
+     *
+     * Filters should load the implementing JavaScript on the filter configuration
+     * form and implement Drupal.filterSettings[filterID].getRules(), which should
+     * return an array of Drupal.FilterHTMLRule objects.
+     */
+    liveSettingParsers: {},
+
+    /**
+     * Updates all Drupal.FilterStatus objects to reflect the current state.
+     *
+     * Automatically checks whether a filter is currently enabled or not. To
+     * support more finegrained
+     *
+     * If a filter implements a live setting parser, then that will be used to
+     * keep the HTML rules for the Drupal.FilterStatus object up-to-date.
+     */
+    update: function () {
+      for (var filterID in Drupal.filterConfiguration.statuses) {
+        if (Drupal.filterConfiguration.statuses.hasOwnProperty(filterID)) {
+          // Update status.
+          Drupal.filterConfiguration.statuses[filterID].active = $('[name="filters[' + filterID + '][status]"]').is(':checked');
+
+          // Update current rules.
+          if (Drupal.filterConfiguration.liveSettingParsers[filterID]) {
+            Drupal.filterConfiguration.statuses[filterID].rules = Drupal.filterConfiguration.liveSettingParsers[filterID].getRules();
+          }
         }
       }
     }
-  }
 
-};
+  };
 
-/**
- * Initializes Drupal.filterConfiguration.
- */
-Drupal.behaviors.initializeFilterConfiguration = {
-  attach: function (context, settings) {
-    var $context = $(context);
-
-    $context.find('#filters-status-wrapper input.form-checkbox').once('filter-editor-status', function () {
-      var $checkbox = $(this);
-      var nameAttribute = $checkbox.attr('name');
-
-      // The filter's checkbox has a name attribute of the form
-      // "filters[<name of filter>][status]", parse "<name of filter>" from it.
-      var filterID = nameAttribute.substring(8, nameAttribute.indexOf(']'));
-
-      // Create a Drupal.FilterStatus object to track the state (whether it's
-      // active or not and its current settings, if any) of each filter.
-      Drupal.filterConfiguration.statuses[filterID] = new Drupal.FilterStatus(filterID);
-    });
-  }
-};
+  /**
+   * Initializes Drupal.filterConfiguration.
+   */
+  Drupal.behaviors.initializeFilterConfiguration = {
+    attach: function (context, settings) {
+      var $context = $(context);
+
+      $context.find('#filters-status-wrapper input.form-checkbox').once('filter-editor-status', function () {
+        var $checkbox = $(this);
+        var nameAttribute = $checkbox.attr('name');
+
+        // The filter's checkbox has a name attribute of the form
+        // "filters[<name of filter>][status]", parse "<name of filter>" from it.
+        var filterID = nameAttribute.substring(8, nameAttribute.indexOf(']'));
+
+        // Create a Drupal.FilterStatus object to track the state (whether it's
+        // active or not and its current settings, if any) of each filter.
+        Drupal.filterConfiguration.statuses[filterID] = new Drupal.FilterStatus(filterID);
+      });
+    }
+  };
 
 })(jQuery, _, Drupal, document);
diff --git a/core/modules/editor/js/editor.dialog.js b/core/modules/editor/js/editor.dialog.js
index 35dd4be..3ec2372 100644
--- a/core/modules/editor/js/editor.dialog.js
+++ b/core/modules/editor/js/editor.dialog.js
@@ -4,18 +4,18 @@
 
 (function ($, Drupal) {
 
-"use strict";
+  "use strict";
 
-/**
- * Command to save the contents of an editor-provided modal.
- *
- * This command does not close the open modal. It should be followed by a call
- * to Drupal.AjaxCommands.prototype.closeDialog. Editors that are integrated
- * with dialogs must independently listen for an editor:dialogsave event to save
- * the changes into the contents of their interface.
- */
-Drupal.AjaxCommands.prototype.editorDialogSave = function (ajax, response, status) {
-  $(window).trigger('editor:dialogsave', [response.values]);
-};
+  /**
+   * Command to save the contents of an editor-provided modal.
+   *
+   * This command does not close the open modal. It should be followed by a call
+   * to Drupal.AjaxCommands.prototype.closeDialog. Editors that are integrated
+   * with dialogs must independently listen for an editor:dialogsave event to save
+   * the changes into the contents of their interface.
+   */
+  Drupal.AjaxCommands.prototype.editorDialogSave = function (ajax, response, status) {
+    $(window).trigger('editor:dialogsave', [response.values]);
+  };
 
 })(jQuery, Drupal);
diff --git a/core/modules/editor/js/editor.formattedTextEditor.js b/core/modules/editor/js/editor.formattedTextEditor.js
index 19439b9..900125c 100644
--- a/core/modules/editor/js/editor.formattedTextEditor.js
+++ b/core/modules/editor/js/editor.formattedTextEditor.js
@@ -12,175 +12,175 @@
  */
 (function ($, Drupal, drupalSettings) {
 
-"use strict";
-
-Drupal.edit.editors.editor = Drupal.edit.EditorView.extend({
-
-  // The text format for this field.
-  textFormat: null,
-
-  // Indicates whether this text format has transformations.
-  textFormatHasTransformations: null,
-
-  // Stores a reference to the text editor object for this field.
-  textEditor: null,
-
-  // Stores the textual DOM element that is being in-place edited.
-  $textElement: null,
-
-  /**
-   * {@inheritdoc}
-   */
-  initialize: function (options) {
-    Drupal.edit.EditorView.prototype.initialize.call(this, options);
-
-    var metadata = Drupal.edit.metadata.get(this.fieldModel.get('fieldID'), 'custom');
-    this.textFormat = drupalSettings.editor.formats[metadata.format];
-    this.textFormatHasTransformations = metadata.formatHasTransformations;
-    this.textEditor = Drupal.editors[this.textFormat.editor];
-
-    // Store the actual value of this field. We'll need this to restore the
-    // original value when the user discards his modifications.
-    this.$textElement = this.$el.find('.field-item:first');
-    this.model.set('originalValue', this.$textElement.html());
-  },
-
-  /**
-   * {@inheritdoc}
-   */
-  getEditedElement: function () {
-    return this.$textElement;
-  },
-
-  /**
-   * {@inheritdoc}
-   */
-  stateChange: function (fieldModel, state) {
-    var editorModel = this.model;
-    var from = fieldModel.previous('state');
-    var to = state;
-    switch (to) {
-      case 'inactive':
-        break;
-
-      case 'candidate':
-        // Detach the text editor when entering the 'candidate' state from one
-        // of the states where it could have been attached.
-        if (from !== 'inactive' && from !== 'highlighted') {
-          this.textEditor.detach(this.$textElement.get(0), this.textFormat);
-        }
-        if (from === 'invalid') {
-          this.removeValidationErrors();
-        }
-        break;
-
-      case 'highlighted':
-        break;
-
-      case 'activating':
-        // When transformation filters have been been applied to the processed
-        // text of this field, then we'll need to load a re-processed version of
-        // it without the transformation filters.
-        if (this.textFormatHasTransformations) {
-          var $textElement = this.$textElement;
-          this._getUntransformedText(function (untransformedText) {
-            $textElement.html(untransformedText);
-            fieldModel.set('state', 'active');
+  "use strict";
+
+  Drupal.edit.editors.editor = Drupal.edit.EditorView.extend({
+
+    // The text format for this field.
+    textFormat: null,
+
+    // Indicates whether this text format has transformations.
+    textFormatHasTransformations: null,
+
+    // Stores a reference to the text editor object for this field.
+    textEditor: null,
+
+    // Stores the textual DOM element that is being in-place edited.
+    $textElement: null,
+
+    /**
+     * {@inheritdoc}
+     */
+    initialize: function (options) {
+      Drupal.edit.EditorView.prototype.initialize.call(this, options);
+
+      var metadata = Drupal.edit.metadata.get(this.fieldModel.get('fieldID'), 'custom');
+      this.textFormat = drupalSettings.editor.formats[metadata.format];
+      this.textFormatHasTransformations = metadata.formatHasTransformations;
+      this.textEditor = Drupal.editors[this.textFormat.editor];
+
+      // Store the actual value of this field. We'll need this to restore the
+      // original value when the user discards his modifications.
+      this.$textElement = this.$el.find('.field-item:first');
+      this.model.set('originalValue', this.$textElement.html());
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    getEditedElement: function () {
+      return this.$textElement;
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    stateChange: function (fieldModel, state) {
+      var editorModel = this.model;
+      var from = fieldModel.previous('state');
+      var to = state;
+      switch (to) {
+        case 'inactive':
+          break;
+
+        case 'candidate':
+          // Detach the text editor when entering the 'candidate' state from one
+          // of the states where it could have been attached.
+          if (from !== 'inactive' && from !== 'highlighted') {
+            this.textEditor.detach(this.$textElement.get(0), this.textFormat);
+          }
+          if (from === 'invalid') {
+            this.removeValidationErrors();
+          }
+          break;
+
+        case 'highlighted':
+          break;
+
+        case 'activating':
+          // When transformation filters have been been applied to the processed
+          // text of this field, then we'll need to load a re-processed version of
+          // it without the transformation filters.
+          if (this.textFormatHasTransformations) {
+            var $textElement = this.$textElement;
+            this._getUntransformedText(function (untransformedText) {
+              $textElement.html(untransformedText);
+              fieldModel.set('state', 'active');
+            });
+          }
+          // When no transformation filters have been applied: start WYSIWYG
+          // editing immediately!
+          else {
+            // Defer updating the model until the current state change has
+            // propagated, to not trigger a nested state change event.
+            _.defer(function () {
+              fieldModel.set('state', 'active');
+            });
+          }
+          break;
+
+        case 'active':
+          var textElement = this.$textElement.get(0);
+          var toolbarView = fieldModel.toolbarView;
+          this.textEditor.attachInlineEditor(
+            textElement,
+            this.textFormat,
+            toolbarView.getMainWysiwygToolgroupId(),
+            toolbarView.getFloatedWysiwygToolgroupId()
+          );
+          // Set the state to 'changed' whenever the content has changed.
+          this.textEditor.onChange(textElement, function (htmlText) {
+            editorModel.set('currentValue', htmlText);
+            fieldModel.set('state', 'changed');
           });
-        }
-        // When no transformation filters have been applied: start WYSIWYG
-        // editing immediately!
-        else {
-          // Defer updating the model until the current state change has
-          // propagated, to not trigger a nested state change event.
-          _.defer(function () {
-            fieldModel.set('state', 'active');
-          });
-        }
-        break;
-
-      case 'active':
-        var textElement = this.$textElement.get(0);
-        var toolbarView = fieldModel.toolbarView;
-        this.textEditor.attachInlineEditor(
-          textElement,
-          this.textFormat,
-          toolbarView.getMainWysiwygToolgroupId(),
-          toolbarView.getFloatedWysiwygToolgroupId()
-        );
-        // Set the state to 'changed' whenever the content has changed.
-        this.textEditor.onChange(textElement, function (htmlText) {
-          editorModel.set('currentValue', htmlText);
-          fieldModel.set('state', 'changed');
-        });
-        break;
-
-      case 'changed':
-        break;
-
-      case 'saving':
-        if (from === 'invalid') {
-          this.removeValidationErrors();
-        }
-        this.save();
-        break;
-
-      case 'saved':
-        break;
-
-      case 'invalid':
-        this.showValidationErrors();
-        break;
+          break;
+
+        case 'changed':
+          break;
+
+        case 'saving':
+          if (from === 'invalid') {
+            this.removeValidationErrors();
+          }
+          this.save();
+          break;
+
+        case 'saved':
+          break;
+
+        case 'invalid':
+          this.showValidationErrors();
+          break;
+      }
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    getEditUISettings: function () {
+      return { padding: true, unifiedToolbar: true, fullWidthToolbar: true, popup: false };
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    revert: function () {
+      this.$textElement.html(this.model.get('originalValue'));
+    },
+
+    /**
+     * Loads untransformed text for this field.
+     *
+     * More accurately: it re-processes processed text to exclude transformation
+     * filters used by the text format.
+     *
+     * @param Function callback
+     *   A callback function that will receive the untransformed text.
+     *
+     * @see \Drupal\editor\Ajax\GetUntransformedTextCommand
+     */
+    _getUntransformedText: function (callback) {
+      var fieldID = this.fieldModel.get('fieldID');
+
+      // Create a Drupal.ajax instance to load the form.
+      var textLoaderAjax = new Drupal.ajax(fieldID, this.$el, {
+        url: Drupal.edit.util.buildUrl(fieldID, Drupal.url('editor/!entity_type/!id/!field_name/!langcode/!view_mode')),
+        event: 'editor-internal.editor',
+        submit: { nocssjs: true },
+        progress: { type: null } // No progress indicator.
+      });
+
+      // Implement a scoped editorGetUntransformedText AJAX command: calls the
+      // callback.
+      textLoaderAjax.commands.editorGetUntransformedText = function (ajax, response, status) {
+        callback(response.data);
+      };
+
+      // This will ensure our scoped editorGetUntransformedText AJAX command
+      // gets called.
+      this.$el.trigger('editor-internal.editor');
     }
-  },
-
-  /**
-   * {@inheritdoc}
-   */
-  getEditUISettings: function () {
-    return { padding: true, unifiedToolbar: true, fullWidthToolbar: true, popup: false };
-  },
-
-  /**
-   * {@inheritdoc}
-   */
-  revert: function () {
-    this.$textElement.html(this.model.get('originalValue'));
-  },
-
-  /**
-   * Loads untransformed text for this field.
-   *
-   * More accurately: it re-processes processed text to exclude transformation
-   * filters used by the text format.
-   *
-   * @param Function callback
-   *   A callback function that will receive the untransformed text.
-   *
-   * @see \Drupal\editor\Ajax\GetUntransformedTextCommand
-   */
-  _getUntransformedText: function (callback) {
-    var fieldID = this.fieldModel.get('fieldID');
-
-    // Create a Drupal.ajax instance to load the form.
-    var textLoaderAjax = new Drupal.ajax(fieldID, this.$el, {
-      url: Drupal.edit.util.buildUrl(fieldID, Drupal.url('editor/!entity_type/!id/!field_name/!langcode/!view_mode')),
-      event: 'editor-internal.editor',
-      submit: { nocssjs : true },
-      progress: { type : null } // No progress indicator.
-    });
-
-    // Implement a scoped editorGetUntransformedText AJAX command: calls the
-    // callback.
-    textLoaderAjax.commands.editorGetUntransformedText = function (ajax, response, status) {
-      callback(response.data);
-    };
-
-    // This will ensure our scoped editorGetUntransformedText AJAX command
-    // gets called.
-    this.$el.trigger('editor-internal.editor');
-  }
-
-});
+
+  });
 
 })(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/editor/js/editor.js b/core/modules/editor/js/editor.js
index 62733f4..ebaf5e4 100644
--- a/core/modules/editor/js/editor.js
+++ b/core/modules/editor/js/editor.js
@@ -5,216 +5,216 @@
 
 (function ($, Drupal, drupalSettings) {
 
-"use strict";
-
-/**
- * Finds the text area field associated with the given text format selector.
- *
- * @param jQuery $formatSelector
- *   A text format selector DOM element.
- *
- * @return DOM
- *   The text area DOM element.
- */
-function findFieldForFormatSelector ($formatSelector) {
-  var field_id = $formatSelector.attr('data-editor-for');
-  return $('#' + field_id).get(0);
-}
-
-/**
- * Changes the text editor on the text area for the given text format selector.
- *
- * @param jQuery $formatSelector
- *   A text format selector DOM element.
- * @param String activeFormatID
- *   The currently active text format; its associated text editor will be
- *   detached.
- * @param String newFormatID
- *   The text format we're changing to; its associated text editor will be
- *   attached.
- */
-function changeTextEditor ($formatSelector, activeFormatID, newFormatID) {
-  var field = findFieldForFormatSelector($formatSelector);
-  // Detach the current editor (if any) and attach a new editor.
-  if (drupalSettings.editor.formats[activeFormatID]) {
-    Drupal.editorDetach(field, drupalSettings.editor.formats[activeFormatID]);
-  }
-  activeFormatID = newFormatID;
-  if (drupalSettings.editor.formats[activeFormatID]) {
-    Drupal.editorAttach(field, drupalSettings.editor.formats[activeFormatID]);
+  "use strict";
+
+  /**
+   * Finds the text area field associated with the given text format selector.
+   *
+   * @param jQuery $formatSelector
+   *   A text format selector DOM element.
+   *
+   * @return DOM
+   *   The text area DOM element.
+   */
+  function findFieldForFormatSelector($formatSelector) {
+    var field_id = $formatSelector.attr('data-editor-for');
+    return $('#' + field_id).get(0);
   }
-  $formatSelector.attr('data-editor-active-text-format', newFormatID);
-}
 
-/**
- * Handles changes in text format.
- *
- * @param jQuery.Event event
- */
-function onTextFormatChange (event) {
-  var $select = $(event.target);
-  var activeFormatID = $select.attr('data-editor-active-text-format');
-  var newFormatID = $select.val();
-
-  // Prevent double-attaching if the change event is triggered manually.
-  if (newFormatID === activeFormatID) {
-    return;
+  /**
+   * Changes the text editor on the text area for the given text format selector.
+   *
+   * @param jQuery $formatSelector
+   *   A text format selector DOM element.
+   * @param String activeFormatID
+   *   The currently active text format; its associated text editor will be
+   *   detached.
+   * @param String newFormatID
+   *   The text format we're changing to; its associated text editor will be
+   *   attached.
+   */
+  function changeTextEditor($formatSelector, activeFormatID, newFormatID) {
+    var field = findFieldForFormatSelector($formatSelector);
+    // Detach the current editor (if any) and attach a new editor.
+    if (drupalSettings.editor.formats[activeFormatID]) {
+      Drupal.editorDetach(field, drupalSettings.editor.formats[activeFormatID]);
+    }
+    activeFormatID = newFormatID;
+    if (drupalSettings.editor.formats[activeFormatID]) {
+      Drupal.editorAttach(field, drupalSettings.editor.formats[activeFormatID]);
+    }
+    $formatSelector.attr('data-editor-active-text-format', newFormatID);
   }
 
-  // When changing to a text format that has a text editor associated
-  // with it that supports content filtering, then first ask for
-  // confirmation, because switching text formats might cause certain
-  // markup to be stripped away.
-  if (drupalSettings.editor.formats[newFormatID] && drupalSettings.editor.formats[newFormatID].editorSupportsContentFiltering) {
-    var message = Drupal.t('Changing the text format to %text_format will permanently remove content that is not allowed in that text format.<br><br>Save your changes before switching the text format to avoid losing data.', {
-      '%text_format': $select.find('option:selected').text()
-    });
-    var confirmationDialog = Drupal.dialog('<div>' + message + '</div>', {
-      title: Drupal.t('Change text format?'),
-      dialogClass: 'editor-change-text-format-modal',
-      resizable: false,
-      buttons: [
-        {
-          text: Drupal.t('Continue'),
-          'class': 'button button--primary',
-          click: function () {
-            changeTextEditor($select, activeFormatID, newFormatID);
-            confirmationDialog.close();
+  /**
+   * Handles changes in text format.
+   *
+   * @param jQuery.Event event
+   */
+  function onTextFormatChange(event) {
+    var $select = $(event.target);
+    var activeFormatID = $select.attr('data-editor-active-text-format');
+    var newFormatID = $select.val();
+
+    // Prevent double-attaching if the change event is triggered manually.
+    if (newFormatID === activeFormatID) {
+      return;
+    }
+
+    // When changing to a text format that has a text editor associated
+    // with it that supports content filtering, then first ask for
+    // confirmation, because switching text formats might cause certain
+    // markup to be stripped away.
+    if (drupalSettings.editor.formats[newFormatID] && drupalSettings.editor.formats[newFormatID].editorSupportsContentFiltering) {
+      var message = Drupal.t('Changing the text format to %text_format will permanently remove content that is not allowed in that text format.<br><br>Save your changes before switching the text format to avoid losing data.', {
+        '%text_format': $select.find('option:selected').text()
+      });
+      var confirmationDialog = Drupal.dialog('<div>' + message + '</div>', {
+        title: Drupal.t('Change text format?'),
+        dialogClass: 'editor-change-text-format-modal',
+        resizable: false,
+        buttons: [
+          {
+            text: Drupal.t('Continue'),
+            'class': 'button button--primary',
+            click: function () {
+              changeTextEditor($select, activeFormatID, newFormatID);
+              confirmationDialog.close();
+            }
+          },
+          {
+            text: Drupal.t('Cancel'),
+            'class': 'button',
+            click: function () {
+              // Restore the active format ID: cancel changing text format. We cannot
+              // simply call event.preventDefault() because jQuery's change event is
+              // only triggered after the change has already been accepted.
+              $select.val(activeFormatID);
+              confirmationDialog.close();
+            }
           }
+        ],
+        // Prevent this modal from being closed without the user making a choice
+        // as per http://stackoverflow.com/a/5438771.
+        closeOnEscape: false,
+        create: function () {
+          $(this).parent().find('.ui-dialog-titlebar-close').remove();
         },
-        {
-          text: Drupal.t('Cancel'),
-          'class': 'button',
-          click: function () {
-            // Restore the active format ID: cancel changing text format. We cannot
-            // simply call event.preventDefault() because jQuery's change event is
-            // only triggered after the change has already been accepted.
-            $select.val(activeFormatID);
-            confirmationDialog.close();
-          }
+        beforeClose: false,
+        close: function (event) {
+          // Automatically destroy the DOM element that was used for the dialog.
+          $(event.target).remove();
         }
-      ],
-      // Prevent this modal from being closed without the user making a choice
-      // as per http://stackoverflow.com/a/5438771.
-      closeOnEscape: false,
-      create: function () {
-        $(this).parent().find('.ui-dialog-titlebar-close').remove();
-      },
-      beforeClose: false,
-      close: function (event) {
-        // Automatically destroy the DOM element that was used for the dialog.
-        $(event.target).remove();
-      }
-    });
+      });
 
-    confirmationDialog.showModal();
-  }
-  else {
-    changeTextEditor($select, activeFormatID, newFormatID);
+      confirmationDialog.showModal();
+    }
+    else {
+      changeTextEditor($select, activeFormatID, newFormatID);
+    }
   }
-}
 
-/**
- * Initialize an empty object for editors to place their attachment code.
- */
-Drupal.editors = {};
+  /**
+   * Initialize an empty object for editors to place their attachment code.
+   */
+  Drupal.editors = {};
+
+  /**
+   * Enables editors on text_format elements.
+   */
+  Drupal.behaviors.editor = {
+    attach: function (context, settings) {
+      // If there are no editor settings, there are no editors to enable.
+      if (!settings.editor) {
+        return;
+      }
 
-/**
- * Enables editors on text_format elements.
- */
-Drupal.behaviors.editor = {
-  attach: function (context, settings) {
-    // If there are no editor settings, there are no editors to enable.
-    if (!settings.editor) {
-      return;
-    }
+      $(context).find('.editor').once('editor', function () {
+        var $this = $(this);
+        var activeFormatID = $this.val();
+        $this.attr('data-editor-active-text-format', activeFormatID);
+        var field = findFieldForFormatSelector($this);
 
-    $(context).find('.editor').once('editor', function () {
-      var $this = $(this);
-      var activeFormatID = $this.val();
-      $this.attr('data-editor-active-text-format', activeFormatID);
-      var field = findFieldForFormatSelector($this);
+        // Directly attach this editor, if the text format is enabled.
+        if (settings.editor.formats[activeFormatID]) {
+          Drupal.editorAttach(field, settings.editor.formats[activeFormatID]);
+        }
 
-      // Directly attach this editor, if the text format is enabled.
-      if (settings.editor.formats[activeFormatID]) {
-        Drupal.editorAttach(field, settings.editor.formats[activeFormatID]);
+        // Attach onChange handler to text format selector element.
+        if ($this.is('select')) {
+          $this.on('change.editorAttach', onTextFormatChange);
+        }
+        // Detach any editor when the containing form is submitted.
+        $this.parents('form').on('submit', function (event) {
+          // Do not detach if the event was canceled.
+          if (event.isDefaultPrevented()) {
+            return;
+          }
+          // Detach the current editor (if any).
+          if (settings.editor.formats[activeFormatID]) {
+            Drupal.editorDetach(field, settings.editor.formats[activeFormatID], 'serialize');
+          }
+        });
+      });
+    },
+
+    detach: function (context, settings, trigger) {
+      var editors;
+      // The 'serialize' trigger indicates that we should simply update the
+      // underlying element with the new text, without destroying the editor.
+      if (trigger === 'serialize') {
+        // Removing the editor-processed class guarantees that the editor will
+        // be reattached. Only do this if we're planning to destroy the editor.
+        editors = $(context).find('.editor-processed');
       }
-
-      // Attach onChange handler to text format selector element.
-      if ($this.is('select')) {
-        $this.on('change.editorAttach', onTextFormatChange);
+      else {
+        editors = $(context).find('.editor').removeOnce('editor');
       }
-      // Detach any editor when the containing form is submitted.
-      $this.parents('form').on('submit', function (event) {
-        // Do not detach if the event was canceled.
-        if (event.isDefaultPrevented()) {
-          return;
-        }
-        // Detach the current editor (if any).
-        if (settings.editor.formats[activeFormatID]) {
-          Drupal.editorDetach(field, settings.editor.formats[activeFormatID], 'serialize');
+
+      editors.each(function () {
+        var $this = $(this);
+        var activeFormatID = $this.val();
+        var field = findFieldForFormatSelector($this);
+        if (activeFormatID in settings.editor.formats) {
+          Drupal.editorDetach(field, settings.editor.formats[activeFormatID], trigger);
         }
       });
-    });
-  },
-
-  detach: function (context, settings, trigger) {
-    var editors;
-    // The 'serialize' trigger indicates that we should simply update the
-    // underlying element with the new text, without destroying the editor.
-    if (trigger === 'serialize') {
-      // Removing the editor-processed class guarantees that the editor will
-      // be reattached. Only do this if we're planning to destroy the editor.
-      editors = $(context).find('.editor-processed');
-    }
-    else {
-      editors = $(context).find('.editor').removeOnce('editor');
     }
-
-    editors.each(function () {
-      var $this = $(this);
-      var activeFormatID = $this.val();
-      var field = findFieldForFormatSelector($this);
-      if (activeFormatID in settings.editor.formats) {
-        Drupal.editorDetach(field, settings.editor.formats[activeFormatID], trigger);
+  };
+
+  Drupal.editorAttach = function (field, format) {
+    if (format.editor) {
+      // HTML5 validation cannot ever work for WYSIWYG editors, because WYSIWYG
+      // editors always hide the underlying textarea element, which prevents
+      // browsers from putting the error message bubble in the right location.
+      // Hence: disable HTML5 validation for this element.
+      if ('required' in field.attributes) {
+        field.setAttribute('data-editor-required', true);
+        field.removeAttribute('required');
       }
-    });
-  }
-};
-
-Drupal.editorAttach = function (field, format) {
-  if (format.editor) {
-    // HTML5 validation cannot ever work for WYSIWYG editors, because WYSIWYG
-    // editors always hide the underlying textarea element, which prevents
-    // browsers from putting the error message bubble in the right location.
-    // Hence: disable HTML5 validation for this element.
-    if ('required' in field.attributes) {
-      field.setAttribute('data-editor-required', true);
-      field.removeAttribute('required');
-    }
 
-    // Attach the text editor.
-    Drupal.editors[format.editor].attach(field, format);
+      // Attach the text editor.
+      Drupal.editors[format.editor].attach(field, format);
 
-    // Ensures form.js' 'formUpdated' event is triggered even for changes that
-    // happen within the text editor.
-    Drupal.editors[format.editor].onChange(field, function () {
-      $(field).trigger('formUpdated');
-    });
-  }
-};
-
-Drupal.editorDetach = function (field, format, trigger) {
-  if (format.editor) {
-    // Restore the HTML5 validation "required" attribute if it was removed in
-    // Drupal.editorAttach().
-    if ('data-editor-required' in field.attributes) {
-      field.setAttribute('required', 'required');
-      field.removeAttribute('data-editor-required');
+      // Ensures form.js' 'formUpdated' event is triggered even for changes that
+      // happen within the text editor.
+      Drupal.editors[format.editor].onChange(field, function () {
+        $(field).trigger('formUpdated');
+      });
     }
+  };
+
+  Drupal.editorDetach = function (field, format, trigger) {
+    if (format.editor) {
+      // Restore the HTML5 validation "required" attribute if it was removed in
+      // Drupal.editorAttach().
+      if ('data-editor-required' in field.attributes) {
+        field.setAttribute('required', 'required');
+        field.removeAttribute('data-editor-required');
+      }
 
-    Drupal.editors[format.editor].detach(field, format, trigger);
-  }
-};
+      Drupal.editors[format.editor].detach(field, format, trigger);
+    }
+  };
 
 })(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/field_ui/field_ui.js b/core/modules/field_ui/field_ui.js
index 9297462..acf53a7 100644
--- a/core/modules/field_ui/field_ui.js
+++ b/core/modules/field_ui/field_ui.js
@@ -3,251 +3,251 @@
  * Attaches the behaviors for the Field UI module.
  */
 
-(function($) {
+(function ($) {
 
-"use strict";
+  "use strict";
 
-Drupal.behaviors.fieldUIDisplayOverview = {
-  attach: function (context, settings) {
-    $(context).find('table#field-display-overview').once('field-display-overview', function() {
-      Drupal.fieldUIOverview.attach(this, settings.fieldUIRowsData, Drupal.fieldUIDisplayOverview);
-    });
-  }
-};
-
-Drupal.fieldUIOverview = {
-  /**
-   * Attaches the fieldUIOverview behavior.
-   */
-  attach: function (table, rowsData, rowHandlers) {
-    var tableDrag = Drupal.tableDrag[table.id];
-
-    // Add custom tabledrag callbacks.
-    tableDrag.onDrop = this.onDrop;
-    tableDrag.row.prototype.onSwap = this.onSwap;
-
-    // Create row handlers.
-    $(table).find('tr.draggable').each(function () {
-      // Extract server-side data for the row.
-      var row = this;
-      if (row.id in rowsData) {
-        var data = rowsData[row.id];
-        data.tableDrag = tableDrag;
-
-        // Create the row handler, make it accessible from the DOM row element.
-        var rowHandler = new rowHandlers[data.rowHandler](row, data);
-        $(row).data('fieldUIRowHandler', rowHandler);
-      }
-    });
-  },
-
-  /**
-   * Event handler to be attached to form inputs triggering a region change.
-   */
-  onChange: function () {
-    var $trigger = $(this);
-    var $row = $trigger.closest('tr');
-    var rowHandler = $row.data('fieldUIRowHandler');
-
-    var refreshRows = {};
-    refreshRows[rowHandler.name] = $trigger.get(0);
-
-    // Handle region change.
-    var region = rowHandler.getRegion();
-    if (region !== rowHandler.region) {
-      // Remove parenting.
-      $row.find('select.field-parent').val('');
-      // Let the row handler deal with the region change.
-      $.extend(refreshRows, rowHandler.regionChange(region));
-      // Update the row region.
-      rowHandler.region = region;
+  Drupal.behaviors.fieldUIDisplayOverview = {
+    attach: function (context, settings) {
+      $(context).find('table#field-display-overview').once('field-display-overview', function () {
+        Drupal.fieldUIOverview.attach(this, settings.fieldUIRowsData, Drupal.fieldUIDisplayOverview);
+      });
     }
-
-    // Ajax-update the rows.
-    Drupal.fieldUIOverview.AJAXRefreshRows(refreshRows);
-  },
-
-  /**
-   * Lets row handlers react when a row is dropped into a new region.
-   */
-  onDrop: function () {
-    var dragObject = this;
-    var row = dragObject.rowObject.element;
-    var $row = $(row);
-    var rowHandler = $row.data('fieldUIRowHandler');
-    if (typeof rowHandler !== 'undefined') {
-      var regionRow = $row.prevAll('tr.region-message').get(0);
-      var region = regionRow.className.replace(/([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
-
+  };
+
+  Drupal.fieldUIOverview = {
+    /**
+     * Attaches the fieldUIOverview behavior.
+     */
+    attach: function (table, rowsData, rowHandlers) {
+      var tableDrag = Drupal.tableDrag[table.id];
+
+      // Add custom tabledrag callbacks.
+      tableDrag.onDrop = this.onDrop;
+      tableDrag.row.prototype.onSwap = this.onSwap;
+
+      // Create row handlers.
+      $(table).find('tr.draggable').each(function () {
+        // Extract server-side data for the row.
+        var row = this;
+        if (row.id in rowsData) {
+          var data = rowsData[row.id];
+          data.tableDrag = tableDrag;
+
+          // Create the row handler, make it accessible from the DOM row element.
+          var rowHandler = new rowHandlers[data.rowHandler](row, data);
+          $(row).data('fieldUIRowHandler', rowHandler);
+        }
+      });
+    },
+
+    /**
+     * Event handler to be attached to form inputs triggering a region change.
+     */
+    onChange: function () {
+      var $trigger = $(this);
+      var $row = $trigger.closest('tr');
+      var rowHandler = $row.data('fieldUIRowHandler');
+
+      var refreshRows = {};
+      refreshRows[rowHandler.name] = $trigger.get(0);
+
+      // Handle region change.
+      var region = rowHandler.getRegion();
       if (region !== rowHandler.region) {
+        // Remove parenting.
+        $row.find('select.field-parent').val('');
         // Let the row handler deal with the region change.
-        var refreshRows = rowHandler.regionChange(region);
+        $.extend(refreshRows, rowHandler.regionChange(region));
         // Update the row region.
         rowHandler.region = region;
-        // Ajax-update the rows.
-        Drupal.fieldUIOverview.AJAXRefreshRows(refreshRows);
       }
-    }
-  },
 
-  /**
-   * Refreshes placeholder rows in empty regions while a row is being dragged.
-   *
-   * Copied from block.js.
-   *
-   * @param table
-   *   The table DOM element.
-   * @param rowObject
-   *   The tableDrag rowObject for the row being dragged.
-   */
-  onSwap: function (draggedRow) {
-    var rowObject = this;
-    $(rowObject.table).find('tr.region-message').each(function () {
-      var $this = $(this);
-      // If the dragged row is in this region, but above the message row, swap
-      // it down one space.
-      if ($this.prev('tr').get(0) === rowObject.group[rowObject.group.length - 1]) {
-        // Prevent a recursion problem when using the keyboard to move rows up.
-        if ((rowObject.method !== 'keyboard' || rowObject.direction === 'down')) {
-          rowObject.swap('after', this);
+      // Ajax-update the rows.
+      Drupal.fieldUIOverview.AJAXRefreshRows(refreshRows);
+    },
+
+    /**
+     * Lets row handlers react when a row is dropped into a new region.
+     */
+    onDrop: function () {
+      var dragObject = this;
+      var row = dragObject.rowObject.element;
+      var $row = $(row);
+      var rowHandler = $row.data('fieldUIRowHandler');
+      if (typeof rowHandler !== 'undefined') {
+        var regionRow = $row.prevAll('tr.region-message').get(0);
+        var region = regionRow.className.replace(/([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
+
+        if (region !== rowHandler.region) {
+          // Let the row handler deal with the region change.
+          var refreshRows = rowHandler.regionChange(region);
+          // Update the row region.
+          rowHandler.region = region;
+          // Ajax-update the rows.
+          Drupal.fieldUIOverview.AJAXRefreshRows(refreshRows);
         }
       }
-      // This region has become empty.
-      if ($this.next('tr').is(':not(.draggable)') || $this.next('tr').length === 0) {
-        $this.removeClass('region-populated').addClass('region-empty');
-      }
-      // This region has become populated.
-      else if ($this.is('.region-empty')) {
-        $this.removeClass('region-empty').addClass('region-populated');
-      }
-    });
-  },
-
-  /**
-   * Triggers Ajax refresh of selected rows.
-   *
-   * The 'format type' selects can trigger a series of changes in child rows.
-   * The #ajax behavior is therefore not attached directly to the selects, but
-   * triggered manually through a hidden #ajax 'Refresh' button.
-   *
-   * @param rows
-   *   A hash object, whose keys are the names of the rows to refresh (they
-   *   will receive the 'ajax-new-content' effect on the server side), and
-   *   whose values are the DOM element in the row that should get an Ajax
-   *   throbber.
-   */
-  AJAXRefreshRows: function (rows) {
-    // Separate keys and values.
-    var rowNames = [];
-    var ajaxElements = [];
-    var rowName;
-    for (rowName in rows) {
-      if (rows.hasOwnProperty(rowName)) {
-        rowNames.push(rowName);
-        ajaxElements.push(rows[rowName]);
+    },
+
+    /**
+     * Refreshes placeholder rows in empty regions while a row is being dragged.
+     *
+     * Copied from block.js.
+     *
+     * @param table
+     *   The table DOM element.
+     * @param rowObject
+     *   The tableDrag rowObject for the row being dragged.
+     */
+    onSwap: function (draggedRow) {
+      var rowObject = this;
+      $(rowObject.table).find('tr.region-message').each(function () {
+        var $this = $(this);
+        // If the dragged row is in this region, but above the message row, swap
+        // it down one space.
+        if ($this.prev('tr').get(0) === rowObject.group[rowObject.group.length - 1]) {
+          // Prevent a recursion problem when using the keyboard to move rows up.
+          if ((rowObject.method !== 'keyboard' || rowObject.direction === 'down')) {
+            rowObject.swap('after', this);
+          }
+        }
+        // This region has become empty.
+        if ($this.next('tr').is(':not(.draggable)') || $this.next('tr').length === 0) {
+          $this.removeClass('region-populated').addClass('region-empty');
+        }
+        // This region has become populated.
+        else if ($this.is('.region-empty')) {
+          $this.removeClass('region-empty').addClass('region-populated');
+        }
+      });
+    },
+
+    /**
+     * Triggers Ajax refresh of selected rows.
+     *
+     * The 'format type' selects can trigger a series of changes in child rows.
+     * The #ajax behavior is therefore not attached directly to the selects, but
+     * triggered manually through a hidden #ajax 'Refresh' button.
+     *
+     * @param rows
+     *   A hash object, whose keys are the names of the rows to refresh (they
+     *   will receive the 'ajax-new-content' effect on the server side), and
+     *   whose values are the DOM element in the row that should get an Ajax
+     *   throbber.
+     */
+    AJAXRefreshRows: function (rows) {
+      // Separate keys and values.
+      var rowNames = [];
+      var ajaxElements = [];
+      var rowName;
+      for (rowName in rows) {
+        if (rows.hasOwnProperty(rowName)) {
+          rowNames.push(rowName);
+          ajaxElements.push(rows[rowName]);
+        }
       }
-    }
 
-    if (rowNames.length) {
-      // Add a throbber next each of the ajaxElements.
-      var $throbber = $('<div class="ajax-progress ajax-progress-throbber"><div class="throbber">&nbsp;</div></div>');
-      $(ajaxElements)
-        .addClass('progress-disabled')
-        .after($throbber);
+      if (rowNames.length) {
+        // Add a throbber next each of the ajaxElements.
+        var $throbber = $('<div class="ajax-progress ajax-progress-throbber"><div class="throbber">&nbsp;</div></div>');
+        $(ajaxElements)
+          .addClass('progress-disabled')
+          .after($throbber);
 
-      // Fire the Ajax update.
-      $('input[name=refresh_rows]').val(rowNames.join(' '));
-      $('input#edit-refresh').trigger('mousedown');
+        // Fire the Ajax update.
+        $('input[name=refresh_rows]').val(rowNames.join(' '));
+        $('input#edit-refresh').trigger('mousedown');
 
-      // Disabled elements do not appear in POST ajax data, so we mark the
-      // elements disabled only after firing the request.
-      $(ajaxElements).prop('disabled', true);
+        // Disabled elements do not appear in POST ajax data, so we mark the
+        // elements disabled only after firing the request.
+        $(ajaxElements).prop('disabled', true);
+      }
     }
-  }
-};
-
+  };
 
-/**
- * Row handlers for the 'Manage display' screen.
- */
-Drupal.fieldUIDisplayOverview = {};
 
-/**
- * Constructor for a 'field' row handler.
- *
- * This handler is used for both fields and 'extra fields' rows.
- *
- * @param row
- *   The row DOM element.
- * @param data
- *   Additional data to be populated in the constructed object.
- */
-Drupal.fieldUIDisplayOverview.field = function (row, data) {
-  this.row = row;
-  this.name = data.name;
-  this.region = data.region;
-  this.tableDrag = data.tableDrag;
-
-  // Attach change listener to the 'plugin type' select.
-  this.$pluginSelect = $(row).find('select.field-plugin-type');
-  this.$pluginSelect.on('change', Drupal.fieldUIOverview.onChange);
-
-  return this;
-};
-
-Drupal.fieldUIDisplayOverview.field.prototype = {
   /**
-   * Returns the region corresponding to the current form values of the row.
+   * Row handlers for the 'Manage display' screen.
    */
-  getRegion: function () {
-    return (this.$pluginSelect.val() === 'hidden') ? 'hidden' : 'content';
-  },
+  Drupal.fieldUIDisplayOverview = {};
 
   /**
-   * Reacts to a row being changed regions.
+   * Constructor for a 'field' row handler.
    *
-   * This function is called when the row is moved to a different region, as a
-   * result of either :
-   * - a drag-and-drop action (the row's form elements then probably need to be
-   *   updated accordingly)
-   * - user input in one of the form elements watched by the
-   *   Drupal.fieldUIOverview.onChange change listener.
+   * This handler is used for both fields and 'extra fields' rows.
    *
-   * @param region
-   *   The name of the new region for the row.
-   * @return
-   *   A hash object indicating which rows should be Ajax-updated as a result
-   *   of the change, in the format expected by
-   *   Drupal.displayOverview.AJAXRefreshRows().
+   * @param row
+   *   The row DOM element.
+   * @param data
+   *   Additional data to be populated in the constructed object.
    */
-  regionChange: function (region) {
-
-    // When triggered by a row drag, the 'format' select needs to be adjusted
-    // to the new region.
-    var currentValue = this.$pluginSelect.val();
-    var value;
-    // @TODO Check if this couldn't just be like
-    // if (region !== 'hidden') {
-    if (region === 'content') {
-      if (currentValue === 'hidden') {
-        // Restore the formatter back to the default formatter. Pseudo-fields do
-        // not have default formatters, we just return to 'visible' for those.
-        value = (typeof this.defaultPlugin !== 'undefined') ? this.defaultPlugin : this.$pluginSelect.find('option').val();
+  Drupal.fieldUIDisplayOverview.field = function (row, data) {
+    this.row = row;
+    this.name = data.name;
+    this.region = data.region;
+    this.tableDrag = data.tableDrag;
+
+    // Attach change listener to the 'plugin type' select.
+    this.$pluginSelect = $(row).find('select.field-plugin-type');
+    this.$pluginSelect.on('change', Drupal.fieldUIOverview.onChange);
+
+    return this;
+  };
+
+  Drupal.fieldUIDisplayOverview.field.prototype = {
+    /**
+     * Returns the region corresponding to the current form values of the row.
+     */
+    getRegion: function () {
+      return (this.$pluginSelect.val() === 'hidden') ? 'hidden' : 'content';
+    },
+
+    /**
+     * Reacts to a row being changed regions.
+     *
+     * This function is called when the row is moved to a different region, as a
+     * result of either :
+     * - a drag-and-drop action (the row's form elements then probably need to be
+     *   updated accordingly)
+     * - user input in one of the form elements watched by the
+     *   Drupal.fieldUIOverview.onChange change listener.
+     *
+     * @param region
+     *   The name of the new region for the row.
+     * @return
+     *   A hash object indicating which rows should be Ajax-updated as a result
+     *   of the change, in the format expected by
+     *   Drupal.displayOverview.AJAXRefreshRows().
+     */
+    regionChange: function (region) {
+
+      // When triggered by a row drag, the 'format' select needs to be adjusted
+      // to the new region.
+      var currentValue = this.$pluginSelect.val();
+      var value;
+      // @TODO Check if this couldn't just be like
+      // if (region !== 'hidden') {
+      if (region === 'content') {
+        if (currentValue === 'hidden') {
+          // Restore the formatter back to the default formatter. Pseudo-fields do
+          // not have default formatters, we just return to 'visible' for those.
+          value = (typeof this.defaultPlugin !== 'undefined') ? this.defaultPlugin : this.$pluginSelect.find('option').val();
+        }
+      }
+      else {
+        value = 'hidden';
       }
-    }
-    else {
-      value = 'hidden';
-    }
 
-    if (typeof value !== 'undefined') {
-      this.$pluginSelect.val(value);
-    }
+      if (typeof value !== 'undefined') {
+        this.$pluginSelect.val(value);
+      }
 
-    var refreshRows = {};
-    refreshRows[this.name] = this.$pluginSelect.get(0);
+      var refreshRows = {};
+      refreshRows[this.name] = this.$pluginSelect.get(0);
 
-    return refreshRows;
-  }
-};
+      return refreshRows;
+    }
+  };
 
 })(jQuery);
diff --git a/core/modules/file/file.js b/core/modules/file/file.js
index 89f46c1..96918a7 100644
--- a/core/modules/file/file.js
+++ b/core/modules/file/file.js
@@ -9,182 +9,182 @@
 
 (function ($) {
 
-"use strict";
+  "use strict";
 
-/**
- * Attach behaviors to managed file element upload fields.
- */
-Drupal.behaviors.fileValidateAutoAttach = {
-  attach: function (context, settings) {
-    var $context = $(context);
-    var validateExtension = Drupal.file.validateExtension;
-    var selector, elements;
-    if (settings.file && settings.file.elements) {
-      elements = settings.file.elements;
-      for (selector in elements) {
-        if (elements.hasOwnProperty(selector)) {
-          $context.find(selector).on('change', {extensions: elements[selector]}, validateExtension);
+  /**
+   * Attach behaviors to managed file element upload fields.
+   */
+  Drupal.behaviors.fileValidateAutoAttach = {
+    attach: function (context, settings) {
+      var $context = $(context);
+      var validateExtension = Drupal.file.validateExtension;
+      var selector, elements;
+      if (settings.file && settings.file.elements) {
+        elements = settings.file.elements;
+        for (selector in elements) {
+          if (elements.hasOwnProperty(selector)) {
+            $context.find(selector).on('change', {extensions: elements[selector]}, validateExtension);
+          }
         }
       }
-    }
-  },
-  detach: function (context, settings) {
-    var $context = $(context);
-    var validateExtension = Drupal.file.validateExtension;
-    var selector, elements;
-    if (settings.file && settings.file.elements) {
-      elements = settings.file.elements;
-      for (selector in elements) {
-        if (elements.hasOwnProperty(selector)) {
-          $context.find(selector).off('change', validateExtension);
+    },
+    detach: function (context, settings) {
+      var $context = $(context);
+      var validateExtension = Drupal.file.validateExtension;
+      var selector, elements;
+      if (settings.file && settings.file.elements) {
+        elements = settings.file.elements;
+        for (selector in elements) {
+          if (elements.hasOwnProperty(selector)) {
+            $context.find(selector).off('change', validateExtension);
+          }
         }
       }
     }
-  }
-};
-
-/**
- * Attach behaviors to managed file element upload fields.
- */
-Drupal.behaviors.fileAutoUpload = {
-  attach: function (context) {
-    $(context).find('input[type="file"]').once('auto-file-upload').on('change.autoFileUpload', Drupal.file.triggerUploadButton);
-  },
-  detach: function (context, setting, trigger) {
-    if (trigger === 'unload') {
-      $(context).find('input[type="file"]').removeOnce('auto-file-upload').off('.autoFileUpload');
-    }
-  }
-};
+  };
 
-/**
- * Attach behaviors to the file upload and remove buttons.
- */
-Drupal.behaviors.fileButtons = {
-  attach: function (context) {
-    var $context = $(context);
-    $context.find('.form-submit').on('mousedown', Drupal.file.disableFields);
-    $context.find('.form-managed-file .form-submit').on('mousedown', Drupal.file.progressBar);
-  },
-  detach: function (context) {
-    var $context = $(context);
-    $context.find('.form-submit').off('mousedown', Drupal.file.disableFields);
-    $context.find('.form-managed-file .form-submit').off('mousedown', Drupal.file.progressBar);
-  }
-};
-
-/**
- * Attach behaviors to links within managed file elements.
- */
-Drupal.behaviors.filePreviewLinks = {
-  attach: function (context) {
-    $(context).find('div.form-managed-file .file a, .file-widget .file a').on('click',Drupal.file.openInNewWindow);
-  },
-  detach: function (context){
-    $(context).find('div.form-managed-file .file a, .file-widget .file a').off('click', Drupal.file.openInNewWindow);
-  }
-};
-
-/**
- * File upload utility functions.
- */
-Drupal.file = Drupal.file || {
   /**
-   * Client-side file input validation of file extensions.
+   * Attach behaviors to managed file element upload fields.
    */
-  validateExtension: function (event) {
-    event.preventDefault();
-    // Remove any previous errors.
-    $('.file-upload-js-error').remove();
-
-    // Add client side validation for the input[type=file].
-    var extensionPattern = event.data.extensions.replace(/,\s*/g, '|');
-    if (extensionPattern.length > 1 && this.value.length > 0) {
-      var acceptableMatch = new RegExp('\\.(' + extensionPattern + ')$', 'gi');
-      if (!acceptableMatch.test(this.value)) {
-        var error = Drupal.t("The selected file %filename cannot be uploaded. Only files with the following extensions are allowed: %extensions.", {
-          // According to the specifications of HTML5, a file upload control
-          // should not reveal the real local path to the file that a user
-          // has selected. Some web browsers implement this restriction by
-          // replacing the local path with "C:\fakepath\", which can cause
-          // confusion by leaving the user thinking perhaps Drupal could not
-          // find the file because it messed up the file path. To avoid this
-          // confusion, therefore, we strip out the bogus fakepath string.
-          '%filename': this.value.replace('C:\\fakepath\\', ''),
-          '%extensions': extensionPattern.replace(/\|/g, ', ')
-        });
-        $(this).closest('div.form-managed-file').prepend('<div class="messages messages--error file-upload-js-error" aria-live="polite">' + error + '</div>');
-        this.value = '';
+  Drupal.behaviors.fileAutoUpload = {
+    attach: function (context) {
+      $(context).find('input[type="file"]').once('auto-file-upload').on('change.autoFileUpload', Drupal.file.triggerUploadButton);
+    },
+    detach: function (context, setting, trigger) {
+      if (trigger === 'unload') {
+        $(context).find('input[type="file"]').removeOnce('auto-file-upload').off('.autoFileUpload');
       }
     }
-  },
-  /**
-   * Trigger the upload_button mouse event to auto-upload as a managed file.
-   */
-  triggerUploadButton: function (event){
-    $(event.target).closest('.form-managed-file').find('.form-submit').trigger('mousedown');
-  },
+  };
+
   /**
-   * Prevent file uploads when using buttons not intended to upload.
+   * Attach behaviors to the file upload and remove buttons.
    */
-  disableFields: function (event){
-    var $clickedButton = $(this);
-
-    // Only disable upload fields for Ajax buttons.
-    if (!$clickedButton.hasClass('ajax-processed')) {
-      return;
+  Drupal.behaviors.fileButtons = {
+    attach: function (context) {
+      var $context = $(context);
+      $context.find('.form-submit').on('mousedown', Drupal.file.disableFields);
+      $context.find('.form-managed-file .form-submit').on('mousedown', Drupal.file.progressBar);
+    },
+    detach: function (context) {
+      var $context = $(context);
+      $context.find('.form-submit').off('mousedown', Drupal.file.disableFields);
+      $context.find('.form-managed-file .form-submit').off('mousedown', Drupal.file.progressBar);
     }
+  };
 
-    // Check if we're working with an "Upload" button.
-    var $enabledFields = [];
-    if ($clickedButton.closest('div.form-managed-file').length > 0) {
-      $enabledFields = $clickedButton.closest('div.form-managed-file').find('input.form-file');
+  /**
+   * Attach behaviors to links within managed file elements.
+   */
+  Drupal.behaviors.filePreviewLinks = {
+    attach: function (context) {
+      $(context).find('div.form-managed-file .file a, .file-widget .file a').on('click', Drupal.file.openInNewWindow);
+    },
+    detach: function (context) {
+      $(context).find('div.form-managed-file .file a, .file-widget .file a').off('click', Drupal.file.openInNewWindow);
     }
+  };
 
-    // Temporarily disable upload fields other than the one we're currently
-    // working with. Filter out fields that are already disabled so that they
-    // do not get enabled when we re-enable these fields at the end of behavior
-    // processing. Re-enable in a setTimeout set to a relatively short amount
-    // of time (1 second). All the other mousedown handlers (like Drupal's Ajax
-    // behaviors) are excuted before any timeout functions are called, so we
-    // don't have to worry about the fields being re-enabled too soon.
-    // @todo If the previous sentence is true, why not set the timeout to 0?
-    var $fieldsToTemporarilyDisable = $('div.form-managed-file input.form-file').not($enabledFields).not(':disabled');
-    $fieldsToTemporarilyDisable.prop('disabled', true);
-    setTimeout(function (){
-      $fieldsToTemporarilyDisable.prop('disabled', false);
-    }, 1000);
-  },
   /**
-   * Add progress bar support if possible.
+   * File upload utility functions.
    */
-  progressBar: function (event) {
-    var $clickedButton = $(this);
-    var $progressId = $clickedButton.closest('div.form-managed-file').find('input.file-progress');
-    if ($progressId.length) {
-      var originalName = $progressId.attr('name');
+  Drupal.file = Drupal.file || {
+    /**
+     * Client-side file input validation of file extensions.
+     */
+    validateExtension: function (event) {
+      event.preventDefault();
+      // Remove any previous errors.
+      $('.file-upload-js-error').remove();
+
+      // Add client side validation for the input[type=file].
+      var extensionPattern = event.data.extensions.replace(/,\s*/g, '|');
+      if (extensionPattern.length > 1 && this.value.length > 0) {
+        var acceptableMatch = new RegExp('\\.(' + extensionPattern + ')$', 'gi');
+        if (!acceptableMatch.test(this.value)) {
+          var error = Drupal.t("The selected file %filename cannot be uploaded. Only files with the following extensions are allowed: %extensions.", {
+            // According to the specifications of HTML5, a file upload control
+            // should not reveal the real local path to the file that a user
+            // has selected. Some web browsers implement this restriction by
+            // replacing the local path with "C:\fakepath\", which can cause
+            // confusion by leaving the user thinking perhaps Drupal could not
+            // find the file because it messed up the file path. To avoid this
+            // confusion, therefore, we strip out the bogus fakepath string.
+            '%filename': this.value.replace('C:\\fakepath\\', ''),
+            '%extensions': extensionPattern.replace(/\|/g, ', ')
+          });
+          $(this).closest('div.form-managed-file').prepend('<div class="messages messages--error file-upload-js-error" aria-live="polite">' + error + '</div>');
+          this.value = '';
+        }
+      }
+    },
+    /**
+     * Trigger the upload_button mouse event to auto-upload as a managed file.
+     */
+    triggerUploadButton: function (event) {
+      $(event.target).closest('.form-managed-file').find('.form-submit').trigger('mousedown');
+    },
+    /**
+     * Prevent file uploads when using buttons not intended to upload.
+     */
+    disableFields: function (event) {
+      var $clickedButton = $(this);
 
-      // Replace the name with the required identifier.
-      $progressId.attr('name', originalName.match(/APC_UPLOAD_PROGRESS|UPLOAD_IDENTIFIER/)[0]);
+      // Only disable upload fields for Ajax buttons.
+      if (!$clickedButton.hasClass('ajax-processed')) {
+        return;
+      }
+
+      // Check if we're working with an "Upload" button.
+      var $enabledFields = [];
+      if ($clickedButton.closest('div.form-managed-file').length > 0) {
+        $enabledFields = $clickedButton.closest('div.form-managed-file').find('input.form-file');
+      }
 
-      // Restore the original name after the upload begins.
+      // Temporarily disable upload fields other than the one we're currently
+      // working with. Filter out fields that are already disabled so that they
+      // do not get enabled when we re-enable these fields at the end of behavior
+      // processing. Re-enable in a setTimeout set to a relatively short amount
+      // of time (1 second). All the other mousedown handlers (like Drupal's Ajax
+      // behaviors) are excuted before any timeout functions are called, so we
+      // don't have to worry about the fields being re-enabled too soon.
+      // @todo If the previous sentence is true, why not set the timeout to 0?
+      var $fieldsToTemporarilyDisable = $('div.form-managed-file input.form-file').not($enabledFields).not(':disabled');
+      $fieldsToTemporarilyDisable.prop('disabled', true);
       setTimeout(function () {
-        $progressId.attr('name', originalName);
+        $fieldsToTemporarilyDisable.prop('disabled', false);
       }, 1000);
+    },
+    /**
+     * Add progress bar support if possible.
+     */
+    progressBar: function (event) {
+      var $clickedButton = $(this);
+      var $progressId = $clickedButton.closest('div.form-managed-file').find('input.file-progress');
+      if ($progressId.length) {
+        var originalName = $progressId.attr('name');
+
+        // Replace the name with the required identifier.
+        $progressId.attr('name', originalName.match(/APC_UPLOAD_PROGRESS|UPLOAD_IDENTIFIER/)[0]);
+
+        // Restore the original name after the upload begins.
+        setTimeout(function () {
+          $progressId.attr('name', originalName);
+        }, 1000);
+      }
+      // Show the progress bar if the upload takes longer than half a second.
+      setTimeout(function () {
+        $clickedButton.closest('div.form-managed-file').find('div.ajax-progress-bar').slideDown();
+      }, 500);
+    },
+    /**
+     * Open links to files within forms in a new window.
+     */
+    openInNewWindow: function (event) {
+      event.preventDefault();
+      $(this).attr('target', '_blank');
+      window.open(this.href, 'filePreview', 'toolbar=0,scrollbars=1,location=1,statusbar=1,menubar=0,resizable=1,width=500,height=550');
     }
-    // Show the progress bar if the upload takes longer than half a second.
-    setTimeout(function () {
-      $clickedButton.closest('div.form-managed-file').find('div.ajax-progress-bar').slideDown();
-    }, 500);
-  },
-  /**
-   * Open links to files within forms in a new window.
-   */
-  openInNewWindow: function (event) {
-    event.preventDefault();
-    $(this).attr('target', '_blank');
-    window.open(this.href, 'filePreview', 'toolbar=0,scrollbars=1,location=1,statusbar=1,menubar=0,resizable=1,width=500,height=550');
-  }
-};
+  };
 
 })(jQuery);
diff --git a/core/modules/filter/filter.admin.js b/core/modules/filter/filter.admin.js
index cde65a5..02cdfbc 100644
--- a/core/modules/filter/filter.admin.js
+++ b/core/modules/filter/filter.admin.js
@@ -5,57 +5,57 @@
 
 (function ($) {
 
-"use strict";
+  "use strict";
 
-Drupal.behaviors.filterStatus = {
-  attach: function (context, settings) {
-    var $context = $(context);
-    $context.find('#filters-status-wrapper input.form-checkbox').once('filter-status', function () {
-      var $checkbox = $(this);
-      // Retrieve the tabledrag row belonging to this filter.
-      var $row = $context.find('#' + $checkbox.attr('id').replace(/-status$/, '-weight')).closest('tr');
-      // Retrieve the vertical tab belonging to this filter.
-      var $filterSettings = $context.find('#' + $checkbox.attr('id').replace(/-status$/, '-settings'));
-      var filterSettingsTab = $filterSettings.data('verticalTab');
+  Drupal.behaviors.filterStatus = {
+    attach: function (context, settings) {
+      var $context = $(context);
+      $context.find('#filters-status-wrapper input.form-checkbox').once('filter-status', function () {
+        var $checkbox = $(this);
+        // Retrieve the tabledrag row belonging to this filter.
+        var $row = $context.find('#' + $checkbox.attr('id').replace(/-status$/, '-weight')).closest('tr');
+        // Retrieve the vertical tab belonging to this filter.
+        var $filterSettings = $context.find('#' + $checkbox.attr('id').replace(/-status$/, '-settings'));
+        var filterSettingsTab = $filterSettings.data('verticalTab');
 
-      // Bind click handler to this checkbox to conditionally show and hide the
-      // filter's tableDrag row and vertical tab pane.
-      $checkbox.on('click.filterUpdate', function () {
-        if ($checkbox.is(':checked')) {
-          $row.show();
-          if (filterSettingsTab) {
-            filterSettingsTab.tabShow().updateSummary();
+        // Bind click handler to this checkbox to conditionally show and hide the
+        // filter's tableDrag row and vertical tab pane.
+        $checkbox.on('click.filterUpdate', function () {
+          if ($checkbox.is(':checked')) {
+            $row.show();
+            if (filterSettingsTab) {
+              filterSettingsTab.tabShow().updateSummary();
+            }
+            else {
+              // On very narrow viewports, Vertical Tabs are disabled.
+              $filterSettings.show();
+            }
           }
           else {
-            // On very narrow viewports, Vertical Tabs are disabled.
-            $filterSettings.show();
+            $row.hide();
+            if (filterSettingsTab) {
+              filterSettingsTab.tabHide().updateSummary();
+            }
+            else {
+              // On very narrow viewports, Vertical Tabs are disabled.
+              $filterSettings.hide();
+            }
           }
-        }
-        else {
-          $row.hide();
-          if (filterSettingsTab) {
-            filterSettingsTab.tabHide().updateSummary();
-          }
-          else {
-            // On very narrow viewports, Vertical Tabs are disabled.
-            $filterSettings.hide();
-          }
-        }
-        // Restripe table after toggling visibility of table row.
-        Drupal.tableDrag['filter-order'].restripeTable();
-      });
-
-      // Attach summary for configurable filters (only for screen-readers).
-      if (filterSettingsTab) {
-        filterSettingsTab.details.drupalSetSummary(function (tabContext) {
-          return $checkbox.is(':checked') ? Drupal.t('Enabled') : Drupal.t('Disabled');
+          // Restripe table after toggling visibility of table row.
+          Drupal.tableDrag['filter-order'].restripeTable();
         });
-      }
 
-      // Trigger our bound click handler to update elements to initial state.
-      $checkbox.triggerHandler('click.filterUpdate');
-    });
-  }
-};
+        // Attach summary for configurable filters (only for screen-readers).
+        if (filterSettingsTab) {
+          filterSettingsTab.details.drupalSetSummary(function (tabContext) {
+            return $checkbox.is(':checked') ? Drupal.t('Enabled') : Drupal.t('Disabled');
+          });
+        }
+
+        // Trigger our bound click handler to update elements to initial state.
+        $checkbox.triggerHandler('click.filterUpdate');
+      });
+    }
+  };
 
 })(jQuery);
diff --git a/core/modules/filter/filter.filter_html.admin.js b/core/modules/filter/filter.filter_html.admin.js
index ba256f0..ed0284c 100644
--- a/core/modules/filter/filter.filter_html.admin.js
+++ b/core/modules/filter/filter.filter_html.admin.js
@@ -5,181 +5,181 @@
 
 (function ($, _, document, window) {
 
-"use strict";
+  "use strict";
 
-/**
- * Implement a live setting parser to prevent text editors from automatically
- * enabling buttons that are not allowed by this filter's configuration.
- */
-if (Drupal.filterConfiguration) {
-  Drupal.filterConfiguration.liveSettingParsers.filter_html = {
-    getRules: function () {
-      var currentValue = $('#edit-filters-filter-html-settings-allowed-html').val();
-      var rules = [], rule;
-
-      // Build a FilterHTMLRule that reflects the hard-coded behavior that
-      // strips all "style" attribute and all "on*" attributes.
-      rule = new Drupal.FilterHTMLRule();
-      rule.restrictedTags.tags = ['*'];
-      rule.restrictedTags.forbidden.attributes = ['style', 'on*'];
-      rules.push(rule);
-
-      // Build a FilterHTMLRule that reflects the current settings.
-      rule = new Drupal.FilterHTMLRule();
-      var behavior = Drupal.behaviors.filterFilterHtmlUpdating;
-      rule.allow = true;
-      rule.tags = behavior._parseSetting(currentValue);
-      rules.push(rule);
-
-      return rules;
-    }
-  };
-}
+  /**
+   * Implement a live setting parser to prevent text editors from automatically
+   * enabling buttons that are not allowed by this filter's configuration.
+   */
+  if (Drupal.filterConfiguration) {
+    Drupal.filterConfiguration.liveSettingParsers.filter_html = {
+      getRules: function () {
+        var currentValue = $('#edit-filters-filter-html-settings-allowed-html').val();
+        var rules = [], rule;
+
+        // Build a FilterHTMLRule that reflects the hard-coded behavior that
+        // strips all "style" attribute and all "on*" attributes.
+        rule = new Drupal.FilterHTMLRule();
+        rule.restrictedTags.tags = ['*'];
+        rule.restrictedTags.forbidden.attributes = ['style', 'on*'];
+        rules.push(rule);
+
+        // Build a FilterHTMLRule that reflects the current settings.
+        rule = new Drupal.FilterHTMLRule();
+        var behavior = Drupal.behaviors.filterFilterHtmlUpdating;
+        rule.allow = true;
+        rule.tags = behavior._parseSetting(currentValue);
+        rules.push(rule);
+
+        return rules;
+      }
+    };
+  }
 
-Drupal.behaviors.filterFilterHtmlUpdating = {
+  Drupal.behaviors.filterFilterHtmlUpdating = {
 
-  // The form item containg the "Allowed HTML tags" setting.
-  $allowedHTMLFormItem: null,
+    // The form item containg the "Allowed HTML tags" setting.
+    $allowedHTMLFormItem: null,
 
-  // The description for the "Allowed HTML tags" field.
-  $allowedHTMLDescription: null,
+    // The description for the "Allowed HTML tags" field.
+    $allowedHTMLDescription: null,
 
-  // The user-entered tag list of $allowedHTMLFormItem.
-  userTags: null,
+    // The user-entered tag list of $allowedHTMLFormItem.
+    userTags: null,
 
-  // The auto-created tag list thus far added.
-  autoTags: null,
+    // The auto-created tag list thus far added.
+    autoTags: null,
 
-  // Track which new features have been added to the text editor.
-  newFeatures: {},
+    // Track which new features have been added to the text editor.
+    newFeatures: {},
 
-  attach: function (context, settings) {
-    var that = this;
-    $(context).find('[name="filters[filter_html][settings][allowed_html]"]').once('filter-filter_html-updating', function () {
-      that.$allowedHTMLFormItem = $(this);
-      that.$allowedHTMLDescription = that.$allowedHTMLFormItem.closest('.form-item').find('.description');
-      that.userTags = that._parseSetting(this.value);
+    attach: function (context, settings) {
+      var that = this;
+      $(context).find('[name="filters[filter_html][settings][allowed_html]"]').once('filter-filter_html-updating', function () {
+        that.$allowedHTMLFormItem = $(this);
+        that.$allowedHTMLDescription = that.$allowedHTMLFormItem.closest('.form-item').find('.description');
+        that.userTags = that._parseSetting(this.value);
 
-      // Update the new allowed tags based on added text editor features.
-      $(document)
-        .on('drupalEditorFeatureAdded', function (e, feature) {
-          that.newFeatures[feature.name] = feature.rules;
-          that._updateAllowedTags();
-        })
-        .on('drupalEditorFeatureModified', function (e, feature) {
-          if (that.newFeatures.hasOwnProperty(feature.name)) {
+        // Update the new allowed tags based on added text editor features.
+        $(document)
+          .on('drupalEditorFeatureAdded', function (e, feature) {
             that.newFeatures[feature.name] = feature.rules;
             that._updateAllowedTags();
-          }
-        })
-        .on('drupalEditorFeatureRemoved', function (e, feature) {
-          if (that.newFeatures.hasOwnProperty(feature.name)) {
-            delete that.newFeatures[feature.name];
-            that._updateAllowedTags();
-          }
+          })
+          .on('drupalEditorFeatureModified', function (e, feature) {
+            if (that.newFeatures.hasOwnProperty(feature.name)) {
+              that.newFeatures[feature.name] = feature.rules;
+              that._updateAllowedTags();
+            }
+          })
+          .on('drupalEditorFeatureRemoved', function (e, feature) {
+            if (that.newFeatures.hasOwnProperty(feature.name)) {
+              delete that.newFeatures[feature.name];
+              that._updateAllowedTags();
+            }
+          });
+
+        // When the allowed tags list is manually changed, update userTags.
+        that.$allowedHTMLFormItem.on('change.updateUserTags', function () {
+          that.userTags = _.difference(that._parseSetting(this.value), that.autoTags);
         });
-
-      // When the allowed tags list is manually changed, update userTags.
-      that.$allowedHTMLFormItem.on('change.updateUserTags', function () {
-        that.userTags = _.difference(that._parseSetting(this.value), that.autoTags);
       });
-    });
-  },
-
-  /**
-   * Updates the "Allowed HTML tags" setting and shows an informative message.
-   */
-  _updateAllowedTags: function () {
-    // Update the list of auto-created tags.
-    this.autoTags = this._calculateAutoAllowedTags(this.userTags, this.newFeatures);
-
-    // Remove any previous auto-created tag message.
-    this.$allowedHTMLDescription.find('.editor-update-message').remove();
-
-    // If any auto-created tags: insert message and update form item.
-    if (this.autoTags.length > 0) {
-      this.$allowedHTMLDescription.append(Drupal.theme('filterFilterHTMLUpdateMessage', this.autoTags));
-      this.$allowedHTMLFormItem.val(this._generateSetting(this.userTags) + ' ' + this._generateSetting(this.autoTags));
-    }
-    // Restore to original state.
-    else {
-      this.$allowedHTMLFormItem.val(this._generateSetting(this.userTags));
+    },
+
+    /**
+     * Updates the "Allowed HTML tags" setting and shows an informative message.
+     */
+    _updateAllowedTags: function () {
+      // Update the list of auto-created tags.
+      this.autoTags = this._calculateAutoAllowedTags(this.userTags, this.newFeatures);
+
+      // Remove any previous auto-created tag message.
+      this.$allowedHTMLDescription.find('.editor-update-message').remove();
+
+      // If any auto-created tags: insert message and update form item.
+      if (this.autoTags.length > 0) {
+        this.$allowedHTMLDescription.append(Drupal.theme('filterFilterHTMLUpdateMessage', this.autoTags));
+        this.$allowedHTMLFormItem.val(this._generateSetting(this.userTags) + ' ' + this._generateSetting(this.autoTags));
+      }
+      // Restore to original state.
+      else {
+        this.$allowedHTMLFormItem.val(this._generateSetting(this.userTags));
+      }
+    },
+
+    /**
+     * Calculates which HTML tags the added text editor buttons need to work.
+     *
+     * The filter_html filter is only concerned with the required tags, not with
+     * any properties, nor with each feature's "allowed" tags.
+     *
+     * @param Array userAllowedTags
+     *   The list of user-defined allowed tags.
+     * @param Object newFeatures
+     *   A list of Drupal.EditorFeature objects' rules, keyed by their name.
+     *
+     * @return Array
+     *   A list of new allowed tags.
+     */
+    _calculateAutoAllowedTags: function (userAllowedTags, newFeatures) {
+      return _
+        .chain(newFeatures)
+        // Reduce multiple features' rules.
+        .reduce(function (memo, featureRules) {
+          // Reduce a single features' rules' required tags.
+          return _.union(memo, _.reduce(featureRules, function (memo, featureRule) {
+            return _.union(memo, featureRule.required.tags);
+          }, []));
+        }, [])
+        // All new features' required tags are "new allowed tags", except
+        // for those that are already allowed in the original allowed tags.
+        .difference(userAllowedTags)
+        .value();
+    },
+
+    /**
+     * Parses the value of this.$allowedHTMLFormItem.
+     *
+     * @param String setting
+     *   The string representation of the setting. e.g. "<p> <br> <a>"
+     *
+     * @return Array
+     *   The array representation of the setting. e.g. ['p', 'br', 'a']
+     */
+    _parseSetting: function (setting) {
+      return setting.length ? setting.substring(1, setting.length - 1).split('> <') : [];
+    },
+
+    /**
+     * Generates the value of this.$allowedHTMLFormItem.
+     *
+     * @param Array setting
+     *   The array representation of the setting. e.g. ['p', 'br', 'a']
+     *
+     * @return Array
+     *   The string representation of the setting. e.g. "<p> <br> <a>"
+     */
+    _generateSetting: function (tags) {
+      return tags.length ? '<' + tags.join('> <') + '>' : '';
     }
-  },
 
-  /**
-   * Calculates which HTML tags the added text editor buttons need to work.
-   *
-   * The filter_html filter is only concerned with the required tags, not with
-   * any properties, nor with each feature's "allowed" tags.
-   *
-   * @param Array userAllowedTags
-   *   The list of user-defined allowed tags.
-   * @param Object newFeatures
-   *   A list of Drupal.EditorFeature objects' rules, keyed by their name.
-   *
-   * @return Array
-   *   A list of new allowed tags.
-   */
-  _calculateAutoAllowedTags: function (userAllowedTags, newFeatures) {
-    return _
-      .chain(newFeatures)
-      // Reduce multiple features' rules.
-      .reduce(function (memo, featureRules) {
-        // Reduce a single features' rules' required tags.
-        return _.union(memo, _.reduce(featureRules, function (memo, featureRule) {
-          return _.union(memo, featureRule.required.tags);
-        }, []));
-      }, [])
-      // All new features' required tags are "new allowed tags", except
-      // for those that are already allowed in the original allowed tags.
-      .difference(userAllowedTags)
-      .value();
-  },
-
-  /**
-   * Parses the value of this.$allowedHTMLFormItem.
-   *
-   * @param String setting
-   *   The string representation of the setting. e.g. "<p> <br> <a>"
-   *
-   * @return Array
-   *   The array representation of the setting. e.g. ['p', 'br', 'a']
-   */
-  _parseSetting: function (setting) {
-    return setting.length ? setting.substring(1, setting.length - 1).split('> <') : [];
-  },
+  };
 
   /**
-   * Generates the value of this.$allowedHTMLFormItem.
-   *
-   * @param Array setting
-   *   The array representation of the setting. e.g. ['p', 'br', 'a']
+   * Theme function for the filter_html update message.
    *
-   * @return Array
-   *   The string representation of the setting. e.g. "<p> <br> <a>"
+   * @param Array tags
+   *   An array of the new tags that are to be allowed.
+   * @return
+   *   The corresponding HTML.
    */
-  _generateSetting: function (tags) {
-    return tags.length ? '<' + tags.join('> <') + '>' : '';
-  }
-
-};
-
-/**
- * Theme function for the filter_html update message.
- *
- * @param Array tags
- *   An array of the new tags that are to be allowed.
- * @return
- *   The corresponding HTML.
- */
-Drupal.theme.filterFilterHTMLUpdateMessage = function (tags) {
-  var html = '';
-  var tagList = '<' + tags.join('> <') + '>';
-  html += '<p class="editor-update-message">';
-  html += Drupal.t('Based on the text editor configuration, these tags have automatically been added: <strong>@tag-list</strong>.', { '@tag-list' : tagList });
-  html += '</p>';
-  return html;
-};
+  Drupal.theme.filterFilterHTMLUpdateMessage = function (tags) {
+    var html = '';
+    var tagList = '<' + tags.join('> <') + '>';
+    html += '<p class="editor-update-message">';
+    html += Drupal.t('Based on the text editor configuration, these tags have automatically been added: <strong>@tag-list</strong>.', { '@tag-list': tagList });
+    html += '</p>';
+    return html;
+  };
 
 })(jQuery, _, document, window);
diff --git a/core/modules/filter/filter.js b/core/modules/filter/filter.js
index 2bab50b..38797e5 100644
--- a/core/modules/filter/filter.js
+++ b/core/modules/filter/filter.js
@@ -5,30 +5,30 @@
 
 (function ($) {
 
-"use strict";
+  "use strict";
 
-/**
- * Displays the guidelines of the selected text format automatically.
- */
-Drupal.behaviors.filterGuidelines = {
-  attach: function (context) {
+  /**
+   * Displays the guidelines of the selected text format automatically.
+   */
+  Drupal.behaviors.filterGuidelines = {
+    attach: function (context) {
 
-    function updateFilterGuidelines (event) {
-      var $this = $(event.target);
-      var value = $this.val();
-      $this.closest('.filter-wrapper')
-        .find('.filter-guidelines-item').hide()
-        .filter('.filter-guidelines-' + value).show();
-    }
+      function updateFilterGuidelines(event) {
+        var $this = $(event.target);
+        var value = $this.val();
+        $this.closest('.filter-wrapper')
+          .find('.filter-guidelines-item').hide()
+          .filter('.filter-guidelines-' + value).show();
+      }
 
-    $(context).find('.filter-guidelines').once('filter-guidelines')
-      .find(':header').hide()
-      .closest('.filter-wrapper').find('select.filter-list')
-      .on('change.filterGuidelines', updateFilterGuidelines)
-      // Need to trigger the namespaced event to avoid triggering formUpdated
-      // when initializing the select.
-      .trigger('change.filterGuidelines');
-  }
-};
+      $(context).find('.filter-guidelines').once('filter-guidelines')
+        .find(':header').hide()
+        .closest('.filter-wrapper').find('select.filter-list')
+        .on('change.filterGuidelines', updateFilterGuidelines)
+        // Need to trigger the namespaced event to avoid triggering formUpdated
+        // when initializing the select.
+        .trigger('change.filterGuidelines');
+    }
+  };
 
 })(jQuery);
diff --git a/core/modules/history/js/history.js b/core/modules/history/js/history.js
index 4fab216..c598dd4 100644
--- a/core/modules/history/js/history.js
+++ b/core/modules/history/js/history.js
@@ -5,121 +5,121 @@
  */
 (function ($, Drupal, drupalSettings, storage) {
 
-"use strict";
+  "use strict";
 
-var currentUserID = parseInt(drupalSettings.user.uid, 10);
+  var currentUserID = parseInt(drupalSettings.user.uid, 10);
 
-// Any comment that is older than 30 days is automatically considered read,
-// so for these we don't need to perform a request at all!
-var thirtyDaysAgo = Math.round(new Date().getTime() / 1000) - 30 * 24 * 60 * 60;
+  // Any comment that is older than 30 days is automatically considered read,
+  // so for these we don't need to perform a request at all!
+  var thirtyDaysAgo = Math.round(new Date().getTime() / 1000) - 30 * 24 * 60 * 60;
 
-// Use the data embedded in the page, if available.
-var embeddedLastReadTimestamps = false;
-if (drupalSettings.history && drupalSettings.history.lastReadTimestamps) {
-  embeddedLastReadTimestamps = drupalSettings.history.lastReadTimestamps;
-}
+  // Use the data embedded in the page, if available.
+  var embeddedLastReadTimestamps = false;
+  if (drupalSettings.history && drupalSettings.history.lastReadTimestamps) {
+    embeddedLastReadTimestamps = drupalSettings.history.lastReadTimestamps;
+  }
 
-Drupal.history = {
+  Drupal.history = {
 
-  /**
-   * Fetch "last read" timestamps for the given nodes.
-   *
-   * @param Array nodeIDs
-   *   An array of node IDs.
-   * @param Function callback
-   *   A callback that is called after the requested timestamps were fetched.
-   */
-  fetchTimestamps: function (nodeIDs, callback) {
-    // Use the data embedded in the page, if available.
-    if (embeddedLastReadTimestamps) {
-      callback();
-      return;
-    }
+    /**
+     * Fetch "last read" timestamps for the given nodes.
+     *
+     * @param Array nodeIDs
+     *   An array of node IDs.
+     * @param Function callback
+     *   A callback that is called after the requested timestamps were fetched.
+     */
+    fetchTimestamps: function (nodeIDs, callback) {
+      // Use the data embedded in the page, if available.
+      if (embeddedLastReadTimestamps) {
+        callback();
+        return;
+      }
 
-    $.ajax({
-      url: Drupal.url('history/get_node_read_timestamps'),
-      type: 'POST',
-      data: { 'node_ids[]' : nodeIDs },
-      dataType: 'json',
-      success: function (results) {
-        for (var nodeID in results) {
-          if (results.hasOwnProperty(nodeID)) {
-            storage.setItem('Drupal.history.' + currentUserID + '.' + nodeID, results[nodeID]);
+      $.ajax({
+        url: Drupal.url('history/get_node_read_timestamps'),
+        type: 'POST',
+        data: { 'node_ids[]': nodeIDs },
+        dataType: 'json',
+        success: function (results) {
+          for (var nodeID in results) {
+            if (results.hasOwnProperty(nodeID)) {
+              storage.setItem('Drupal.history.' + currentUserID + '.' + nodeID, results[nodeID]);
+            }
           }
+          callback();
         }
-        callback();
+      });
+    },
+
+    /**
+     * Get the last read timestamp for the given node.
+     *
+     * @param Number|String nodeID
+     *   A node ID.
+     *
+     * @return Number
+     *   A UNIX timestamp.
+     */
+    getLastRead: function (nodeID) {
+      // Use the data embedded in the page, if available.
+      if (embeddedLastReadTimestamps && embeddedLastReadTimestamps[nodeID]) {
+        return parseInt(embeddedLastReadTimestamps[nodeID], 10);
       }
-    });
-  },
+      return parseInt(storage.getItem('Drupal.history.' + currentUserID + '.' + nodeID) || 0, 10);
+    },
 
-  /**
-   * Get the last read timestamp for the given node.
-   *
-   * @param Number|String nodeID
-   *   A node ID.
-   *
-   * @return Number
-   *   A UNIX timestamp.
-   */
-  getLastRead: function (nodeID) {
-    // Use the data embedded in the page, if available.
-    if (embeddedLastReadTimestamps && embeddedLastReadTimestamps[nodeID]) {
-      return parseInt(embeddedLastReadTimestamps[nodeID], 10);
-    }
-    return parseInt(storage.getItem('Drupal.history.' + currentUserID + '.' + nodeID) || 0, 10);
-  },
+    /**
+     * Marks a node as read, store the last read timestamp in client-side storage.
+     *
+     * @param Number|String nodeID
+     *   A node ID.
+     */
+    markAsRead: function (nodeID) {
+      $.ajax({
+        url: Drupal.url('history/' + nodeID + '/read'),
+        type: 'POST',
+        dataType: 'json',
+        success: function (timestamp) {
+          // If the data is embedded in the page, don't store on the client side.
+          if (embeddedLastReadTimestamps && embeddedLastReadTimestamps[nodeID]) {
+            return;
+          }
 
-  /**
-   * Marks a node as read, store the last read timestamp in client-side storage.
-   *
-   * @param Number|String nodeID
-   *   A node ID.
-   */
-  markAsRead: function (nodeID) {
-    $.ajax({
-      url: Drupal.url('history/' + nodeID + '/read'),
-      type: 'POST',
-      dataType: 'json',
-      success: function (timestamp) {
-        // If the data is embedded in the page, don't store on the client side.
-        if (embeddedLastReadTimestamps && embeddedLastReadTimestamps[nodeID]) {
-          return;
+          storage.setItem('Drupal.history.' + currentUserID + '.' + nodeID, timestamp);
         }
+      });
+    },
 
-        storage.setItem('Drupal.history.' + currentUserID + '.' + nodeID, timestamp);
+    /**
+     * Determines whether a server check is necessary.
+     *
+     * Any content that is >30 days old never gets a "new" or "updated" indicator.
+     * Any content that was published before the oldest known reading also never
+     * gets a "new" or "updated" indicator, because it must've been read already.
+     *
+     * @param Number|String nodeID
+     *   A node ID.
+     * @param Number contentTimestamp
+     *   The time at which some content (e.g. a comment) was published.
+     *
+     * @return Boolean
+     *   Whether a server check is necessary for the given node and its timestamp.
+     */
+    needsServerCheck: function (nodeID, contentTimestamp) {
+      // First check if the content is older than 30 days, then we can bail early.
+      if (contentTimestamp < thirtyDaysAgo) {
+        return false;
       }
-    });
-  },
 
-  /**
-   * Determines whether a server check is necessary.
-   *
-   * Any content that is >30 days old never gets a "new" or "updated" indicator.
-   * Any content that was published before the oldest known reading also never
-   * gets a "new" or "updated" indicator, because it must've been read already.
-   *
-   * @param Number|String nodeID
-   *   A node ID.
-   * @param Number contentTimestamp
-   *   The time at which some content (e.g. a comment) was published.
-   *
-   * @return Boolean
-   *   Whether a server check is necessary for the given node and its timestamp.
-   */
-  needsServerCheck: function (nodeID, contentTimestamp) {
-    // First check if the content is older than 30 days, then we can bail early.
-    if (contentTimestamp < thirtyDaysAgo) {
-      return false;
-    }
+      // Use the data embedded in the page, if available.
+      if (embeddedLastReadTimestamps && embeddedLastReadTimestamps[nodeID]) {
+        return contentTimestamp > parseInt(embeddedLastReadTimestamps[nodeID], 10);
+      }
 
-    // Use the data embedded in the page, if available.
-    if (embeddedLastReadTimestamps && embeddedLastReadTimestamps[nodeID]) {
-      return contentTimestamp > parseInt(embeddedLastReadTimestamps[nodeID], 10);
+      var minLastReadTimestamp = parseInt(storage.getItem('Drupal.history.' + currentUserID + '.' + nodeID) || 0, 10);
+      return contentTimestamp > minLastReadTimestamp;
     }
-
-    var minLastReadTimestamp = parseInt(storage.getItem('Drupal.history.' + currentUserID + '.' + nodeID) || 0, 10);
-    return contentTimestamp > minLastReadTimestamp;
-  }
-};
+  };
 
 })(jQuery, Drupal, drupalSettings, window.localStorage);
diff --git a/core/modules/language/language.admin.js b/core/modules/language/language.admin.js
index 41623cf..3dc2dbe 100644
--- a/core/modules/language/language.admin.js
+++ b/core/modules/language/language.admin.js
@@ -1,32 +1,32 @@
 (function ($, Drupal) {
 
-"use strict";
+  "use strict";
 
-/**
- * Makes language negotiation inherit user interface negotiation.
- */
-Drupal.behaviors.negotiationLanguage = {
-  attach: function () {
-    var $configForm = $('#language-negotiation-configure-form');
-    var inputSelector = 'input[name$="[configurable]"]';
-    // Given a customization checkbox derive the language type being changed.
-    function toggleTable (checkbox) {
-      var $checkbox = $(checkbox);
-      // Get the language detection type such as User interface text language
-      // detection or Content language detection.
-      $checkbox.closest('.table-language-group')
-        .find('table, .tabledrag-toggle-weight')
-        .toggle($checkbox.prop('checked'));
+  /**
+   * Makes language negotiation inherit user interface negotiation.
+   */
+  Drupal.behaviors.negotiationLanguage = {
+    attach: function () {
+      var $configForm = $('#language-negotiation-configure-form');
+      var inputSelector = 'input[name$="[configurable]"]';
+      // Given a customization checkbox derive the language type being changed.
+      function toggleTable(checkbox) {
+        var $checkbox = $(checkbox);
+        // Get the language detection type such as User interface text language
+        // detection or Content language detection.
+        $checkbox.closest('.table-language-group')
+          .find('table, .tabledrag-toggle-weight')
+          .toggle($checkbox.prop('checked'));
+      }
+      // Bind hide/show and rearrange customization checkboxes.
+      $configForm.once('negotiation-language-admin-bind').on('change', inputSelector, function (event) {
+        toggleTable(event.target);
+      });
+      // Initially, hide language detection types that are not customized.
+      $configForm.find(inputSelector + ':not(:checked)').each(function (index, element) {
+        toggleTable(element);
+      });
     }
-    // Bind hide/show and rearrange customization checkboxes.
-    $configForm.once('negotiation-language-admin-bind').on('change', inputSelector, function (event) {
-      toggleTable(event.target);
-    });
-    // Initially, hide language detection types that are not customized.
-    $configForm.find(inputSelector + ':not(:checked)').each(function (index, element) {
-      toggleTable(element);
-    });
-  }
-};
+  };
 
 })(jQuery, Drupal);
diff --git a/core/modules/locale/locale.admin.js b/core/modules/locale/locale.admin.js
index f5d8cf2..c011dea 100644
--- a/core/modules/locale/locale.admin.js
+++ b/core/modules/locale/locale.admin.js
@@ -1,86 +1,86 @@
 (function ($, Drupal) {
 
-"use strict";
+  "use strict";
 
-/**
- * Marks changes of translations
- */
-Drupal.behaviors.localeTranslateDirty = {
-  attach: function () {
-    var $form = $("#locale-translate-edit-form").once('localetranslatedirty');
-    if ($form.length) {
-      // Display a notice if any row changed.
-      $form.one('change.localeTranslateDirty', 'table', function () {
-        var $marker = $(Drupal.theme('localeTranslateChangedWarning')).hide();
-        $(this).addClass('changed').before($marker);
-        $marker.fadeIn('slow');
-      });
-      // Highlight changed row.
-      $form.on('change.localeTranslateDirty', 'tr', function () {
-        var
-          $row = $(this),
-          $rowToMark = $row.once('localemark'),
-          marker = Drupal.theme('localeTranslateChangedMarker');
+  /**
+   * Marks changes of translations
+   */
+  Drupal.behaviors.localeTranslateDirty = {
+    attach: function () {
+      var $form = $("#locale-translate-edit-form").once('localetranslatedirty');
+      if ($form.length) {
+        // Display a notice if any row changed.
+        $form.one('change.localeTranslateDirty', 'table', function () {
+          var $marker = $(Drupal.theme('localeTranslateChangedWarning')).hide();
+          $(this).addClass('changed').before($marker);
+          $marker.fadeIn('slow');
+        });
+        // Highlight changed row.
+        $form.on('change.localeTranslateDirty', 'tr', function () {
+          var
+            $row = $(this),
+            $rowToMark = $row.once('localemark'),
+            marker = Drupal.theme('localeTranslateChangedMarker');
 
-        $row.addClass('changed');
-        // Add an asterisk only once if row changed.
-        if ($rowToMark.length) {
-          $rowToMark.find('td:first-child .form-item').append(marker);
+          $row.addClass('changed');
+          // Add an asterisk only once if row changed.
+          if ($rowToMark.length) {
+            $rowToMark.find('td:first-child .form-item').append(marker);
+          }
+        });
+      }
+    },
+    detach: function (context, settings, trigger) {
+      if (trigger === 'unload') {
+        var $form = $("#locale-translate-edit-form").removeOnce('localetranslatedirty');
+        if ($form.length) {
+          $form.off('change.localeTranslateDirty');
         }
-      });
-    }
-  },
-  detach: function (context, settings, trigger) {
-    if (trigger === 'unload') {
-      var $form = $("#locale-translate-edit-form").removeOnce('localetranslatedirty');
-      if ($form.length) {
-        $form.off('change.localeTranslateDirty');
       }
     }
-  }
-};
+  };
 
-/**
- * Show/hide the description details on Available translation updates page.
- */
-Drupal.behaviors.hideUpdateInformation = {
-  attach: function (context, settings) {
-    var $table = $('#locale-translation-status-form').once('expand-updates');
-    if ($table.length) {
-      var $tbodies = $table.find('tbody');
+  /**
+   * Show/hide the description details on Available translation updates page.
+   */
+  Drupal.behaviors.hideUpdateInformation = {
+    attach: function (context, settings) {
+      var $table = $('#locale-translation-status-form').once('expand-updates');
+      if ($table.length) {
+        var $tbodies = $table.find('tbody');
 
-      // Open/close the description details by toggling a tr class.
-      $tbodies.on('click keydown', '.description', function (e) {
-        if (e.keyCode && (e.keyCode !== 13 && e.keyCode !== 32)) {
-          return;
-        }
-        e.preventDefault();
-        var $tr = $(this).closest('tr');
+        // Open/close the description details by toggling a tr class.
+        $tbodies.on('click keydown', '.description', function (e) {
+          if (e.keyCode && (e.keyCode !== 13 && e.keyCode !== 32)) {
+            return;
+          }
+          e.preventDefault();
+          var $tr = $(this).closest('tr');
 
-        $tr.toggleClass('expanded');
+          $tr.toggleClass('expanded');
 
-        // Change screen reader text.
-        $tr.find('.update-description-prefix').text(function () {
-          if ($tr.hasClass('expanded')) {
-            return Drupal.t('Hide description');
-          }
-          else {
-            return Drupal.t('Show description');
-          }
+          // Change screen reader text.
+          $tr.find('.update-description-prefix').text(function () {
+            if ($tr.hasClass('expanded')) {
+              return Drupal.t('Hide description');
+            }
+            else {
+              return Drupal.t('Show description');
+            }
+          });
         });
-      });
-      $table.find('.requirements, .links').hide();
+        $table.find('.requirements, .links').hide();
+      }
     }
-  }
-};
+  };
 
-$.extend(Drupal.theme, {
-  localeTranslateChangedMarker: function () {
-    return '<abbr class="warning ajax-changed" title="' + Drupal.t('Changed') + '">*</abbr>';
-  },
-  localeTranslateChangedWarning: function () {
-    return '<div class="localetranslate-changed-warning messages warning">' + Drupal.theme('localeTranslateChangedMarker') + ' ' + Drupal.t('Changes made in this table will not be saved until the form is submitted.') + '</div>';
-  }
-});
+  $.extend(Drupal.theme, {
+    localeTranslateChangedMarker: function () {
+      return '<abbr class="warning ajax-changed" title="' + Drupal.t('Changed') + '">*</abbr>';
+    },
+    localeTranslateChangedWarning: function () {
+      return '<div class="localetranslate-changed-warning messages warning">' + Drupal.theme('localeTranslateChangedMarker') + ' ' + Drupal.t('Changes made in this table will not be saved until the form is submitted.') + '</div>';
+    }
+  });
 
 })(jQuery, Drupal);
diff --git a/core/modules/locale/locale.bulk.js b/core/modules/locale/locale.bulk.js
index eec2837..ba9986b 100644
--- a/core/modules/locale/locale.bulk.js
+++ b/core/modules/locale/locale.bulk.js
@@ -1,26 +1,26 @@
 (function ($, Drupal) {
 
-"use strict";
+  "use strict";
 
-/**
- * Select the language code of an imported file based on its filename.
- *
- * This only works if the file name ends with "LANGCODE.po".
- */
-Drupal.behaviors.importLanguageCodeSelector = {
-  attach: function (context, settings) {
-    var $form = $('#locale-translate-import-form').once('autodetect-lang');
-    if ($form.length) {
-      var $langcode = $form.find('.langcode-input');
-      $form.find('.file-import-input')
-        .on('change', function () {
-          var matches = $(this).val().match(/\.([\-\w]+)\.po/);
-          if (matches && $langcode.find('option[value="' + matches[1] + '"]').length) {
-            $langcode.val(matches[1]);
-          }
-        });
+  /**
+   * Select the language code of an imported file based on its filename.
+   *
+   * This only works if the file name ends with "LANGCODE.po".
+   */
+  Drupal.behaviors.importLanguageCodeSelector = {
+    attach: function (context, settings) {
+      var $form = $('#locale-translate-import-form').once('autodetect-lang');
+      if ($form.length) {
+        var $langcode = $form.find('.langcode-input');
+        $form.find('.file-import-input')
+          .on('change', function () {
+            var matches = $(this).val().match(/\.([\-\w]+)\.po/);
+            if (matches && $langcode.find('option[value="' + matches[1] + '"]').length) {
+              $langcode.val(matches[1]);
+            }
+          });
+      }
     }
-  }
-};
+  };
 
 })(jQuery, Drupal);
diff --git a/core/modules/locale/locale.datepicker.js b/core/modules/locale/locale.datepicker.js
index b23e57d..4628eda 100644
--- a/core/modules/locale/locale.datepicker.js
+++ b/core/modules/locale/locale.datepicker.js
@@ -5,82 +5,82 @@
 
 (function ($, Drupal, drupalSettings) {
 
-"use strict";
+  "use strict";
 
-/**
- * Attaches language support to the jQuery UI datepicker component.
- */
-Drupal.behaviors.localeDatepicker = {
-  attach: function(context, settings) {
-    // This code accesses drupalSettings and localized strings via Drupal.t().
-    // So this code should run after these are initialized. By placing it in an
-    // attach behavior this is assured.
-    $.datepicker.regional['drupal-locale'] = $.extend({
-      closeText: Drupal.t('Done'),
-      prevText: Drupal.t('Prev'),
-      nextText: Drupal.t('Next'),
-      currentText: Drupal.t('Today'),
-      monthNames: [
-        Drupal.t('January', {}, {context: "Long month name"}),
-        Drupal.t('February', {}, {context: "Long month name"}),
-        Drupal.t('March', {}, {context: "Long month name"}),
-        Drupal.t('April', {}, {context: "Long month name"}),
-        Drupal.t('May', {}, {context: "Long month name"}),
-        Drupal.t('June', {}, {context: "Long month name"}),
-        Drupal.t('July', {}, {context: "Long month name"}),
-        Drupal.t('August', {}, {context: "Long month name"}),
-        Drupal.t('September', {}, {context: "Long month name"}),
-        Drupal.t('October', {}, {context: "Long month name"}),
-        Drupal.t('November', {}, {context: "Long month name"}),
-        Drupal.t('December', {}, {context: "Long month name"})
-      ],
-      monthNamesShort: [
-        Drupal.t('Jan'),
-        Drupal.t('Feb'),
-        Drupal.t('Mar'),
-        Drupal.t('Apr'),
-        Drupal.t('May'),
-        Drupal.t('Jun'),
-        Drupal.t('Jul'),
-        Drupal.t('Aug'),
-        Drupal.t('Sep'),
-        Drupal.t('Oct'),
-        Drupal.t('Nov'),
-        Drupal.t('Dec')
-      ],
-      dayNames: [
-        Drupal.t('Sunday'),
-        Drupal.t('Monday'),
-        Drupal.t('Tuesday'),
-        Drupal.t('Wednesday'),
-        Drupal.t('Thursday'),
-        Drupal.t('Friday'),
-        Drupal.t('Saturday')
-      ],
-      dayNamesShort: [
-        Drupal.t('Sun'),
-        Drupal.t('Mon'),
-        Drupal.t('Tue'),
-        Drupal.t('Wed'),
-        Drupal.t('Thu'),
-        Drupal.t('Fri'),
-        Drupal.t('Sat')
-      ],
-      dayNamesMin: [
-        Drupal.t('Su'),
-        Drupal.t('Mo'),
-        Drupal.t('Tu'),
-        Drupal.t('We'),
-        Drupal.t('Th'),
-        Drupal.t('Fr'),
-        Drupal.t('Sa')
-      ],
-      dateFormat: Drupal.t('mm/dd/yy'),
-      firstDay: 0,
-      isRTL: 0
-    }, drupalSettings.jquery.ui.datepicker);
-    $.datepicker.setDefaults($.datepicker.regional['drupal-locale']);
-  }
-};
+  /**
+   * Attaches language support to the jQuery UI datepicker component.
+   */
+  Drupal.behaviors.localeDatepicker = {
+    attach: function (context, settings) {
+      // This code accesses drupalSettings and localized strings via Drupal.t().
+      // So this code should run after these are initialized. By placing it in an
+      // attach behavior this is assured.
+      $.datepicker.regional['drupal-locale'] = $.extend({
+        closeText: Drupal.t('Done'),
+        prevText: Drupal.t('Prev'),
+        nextText: Drupal.t('Next'),
+        currentText: Drupal.t('Today'),
+        monthNames: [
+          Drupal.t('January', {}, {context: "Long month name"}),
+          Drupal.t('February', {}, {context: "Long month name"}),
+          Drupal.t('March', {}, {context: "Long month name"}),
+          Drupal.t('April', {}, {context: "Long month name"}),
+          Drupal.t('May', {}, {context: "Long month name"}),
+          Drupal.t('June', {}, {context: "Long month name"}),
+          Drupal.t('July', {}, {context: "Long month name"}),
+          Drupal.t('August', {}, {context: "Long month name"}),
+          Drupal.t('September', {}, {context: "Long month name"}),
+          Drupal.t('October', {}, {context: "Long month name"}),
+          Drupal.t('November', {}, {context: "Long month name"}),
+          Drupal.t('December', {}, {context: "Long month name"})
+        ],
+        monthNamesShort: [
+          Drupal.t('Jan'),
+          Drupal.t('Feb'),
+          Drupal.t('Mar'),
+          Drupal.t('Apr'),
+          Drupal.t('May'),
+          Drupal.t('Jun'),
+          Drupal.t('Jul'),
+          Drupal.t('Aug'),
+          Drupal.t('Sep'),
+          Drupal.t('Oct'),
+          Drupal.t('Nov'),
+          Drupal.t('Dec')
+        ],
+        dayNames: [
+          Drupal.t('Sunday'),
+          Drupal.t('Monday'),
+          Drupal.t('Tuesday'),
+          Drupal.t('Wednesday'),
+          Drupal.t('Thursday'),
+          Drupal.t('Friday'),
+          Drupal.t('Saturday')
+        ],
+        dayNamesShort: [
+          Drupal.t('Sun'),
+          Drupal.t('Mon'),
+          Drupal.t('Tue'),
+          Drupal.t('Wed'),
+          Drupal.t('Thu'),
+          Drupal.t('Fri'),
+          Drupal.t('Sat')
+        ],
+        dayNamesMin: [
+          Drupal.t('Su'),
+          Drupal.t('Mo'),
+          Drupal.t('Tu'),
+          Drupal.t('We'),
+          Drupal.t('Th'),
+          Drupal.t('Fr'),
+          Drupal.t('Sa')
+        ],
+        dateFormat: Drupal.t('mm/dd/yy'),
+        firstDay: 0,
+        isRTL: 0
+      }, drupalSettings.jquery.ui.datepicker);
+      $.datepicker.setDefaults($.datepicker.regional['drupal-locale']);
+    }
+  };
 
 })(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/locale/tests/locale_test.js b/core/modules/locale/tests/locale_test.js
index d29680d..336d979 100644
--- a/core/modules/locale/tests/locale_test.js
+++ b/core/modules/locale/tests/locale_test.js
@@ -5,11 +5,11 @@
 
 Drupal.t("Standard Call t");
 Drupal
-.
-t
-(
-"Whitespace Call t"
-)
+  .
+  t
+  (
+    "Whitespace Call t"
+  )
 ;
 
 Drupal.t('Single Quote t');
@@ -28,13 +28,13 @@ Drupal.t("Context !key Args t", {'!key': 'value'}, {context: "Context string"});
 
 Drupal.formatPlural(1, "Standard Call plural", "Standard Call @count plural");
 Drupal
-.
-formatPlural
-(
-1,
-"Whitespace Call plural",
-"Whitespace Call @count plural"
-)
+  .
+  formatPlural
+  (
+    1,
+    "Whitespace Call plural",
+    "Whitespace Call @count plural"
+  )
 ;
 
 Drupal.formatPlural(1, 'Single Quote plural', 'Single Quote @count plural');
diff --git a/core/modules/menu/menu.admin.js b/core/modules/menu/menu.admin.js
index 8512971..50b3999 100644
--- a/core/modules/menu/menu.admin.js
+++ b/core/modules/menu/menu.admin.js
@@ -1,59 +1,59 @@
 (function ($) {
 
-"use strict";
+  "use strict";
+
+  Drupal.behaviors.menuChangeParentItems = {
+    attach: function (context, settings) {
+      var $menu = $('#edit-menu');
+      $menu.once('menu-parent', function () {
+        // Update the list of available parent menu items to match the initial
+        // available menus.
+        Drupal.menuUpdateParentList();
+
+        // Update list of available parent menu items.
+        $menu.on('change', 'input', Drupal.menuUpdateParentList);
+      });
+    }
+  };
 
-Drupal.behaviors.menuChangeParentItems = {
-  attach: function (context, settings) {
+  /**
+   * Function to set the options of the menu parent item dropdown.
+   */
+  Drupal.menuUpdateParentList = function () {
     var $menu = $('#edit-menu');
-    $menu.once('menu-parent', function () {
-      // Update the list of available parent menu items to match the initial
-      // available menus.
-      Drupal.menuUpdateParentList();
+    var values = [];
 
-      // Update list of available parent menu items.
-      $menu.on('change', 'input', Drupal.menuUpdateParentList);
+    $menu.find('input:checked').each(function () {
+      // Get the names of all checked menus.
+      values.push(Drupal.checkPlain($.trim($(this).val())));
     });
-  }
-};
-
-/**
- * Function to set the options of the menu parent item dropdown.
- */
-Drupal.menuUpdateParentList = function () {
-  var $menu = $('#edit-menu');
-  var values = [];
-
-  $menu.find('input:checked').each(function () {
-    // Get the names of all checked menus.
-    values.push(Drupal.checkPlain($.trim($(this).val())));
-  });
-
-  $.ajax({
-    url: location.protocol + '//' + location.host + Drupal.url('admin/structure/menu/parents'),
-    type: 'POST',
-    data: {'menus[]' : values},
-    dataType: 'json',
-    success: function (options) {
-      var $select = $('#edit-menu-parent');
-      // Save key of last selected element.
-      var selected = $select.val();
-      // Remove all exisiting options from dropdown.
-      $select.children().remove();
-      // Add new options to dropdown. Keep a count of options for testing later.
-      var totalOptions = 0;
-      for (var machineName in options) {
-        if (options.hasOwnProperty(machineName)) {
-          $select.append(
-            $('<option ' + (machineName === selected ? ' selected="selected"' : '') + '></option>').val(machineName).text(options[machineName])
-          );
-          totalOptions++;
+
+    $.ajax({
+      url: location.protocol + '//' + location.host + Drupal.url('admin/structure/menu/parents'),
+      type: 'POST',
+      data: {'menus[]': values},
+      dataType: 'json',
+      success: function (options) {
+        var $select = $('#edit-menu-parent');
+        // Save key of last selected element.
+        var selected = $select.val();
+        // Remove all exisiting options from dropdown.
+        $select.children().remove();
+        // Add new options to dropdown. Keep a count of options for testing later.
+        var totalOptions = 0;
+        for (var machineName in options) {
+          if (options.hasOwnProperty(machineName)) {
+            $select.append(
+              $('<option ' + (machineName === selected ? ' selected="selected"' : '') + '></option>').val(machineName).text(options[machineName])
+            );
+            totalOptions++;
+          }
         }
-      }
 
-      // Hide the parent options if there are no options for it.
-      $select.closest('div').toggle(totalOptions > 0).attr('hidden', totalOptions === 0);
-    }
-  });
-};
+        // Hide the parent options if there are no options for it.
+        $select.closest('div').toggle(totalOptions > 0).attr('hidden', totalOptions === 0);
+      }
+    });
+  };
 
 })(jQuery);
diff --git a/core/modules/menu/menu.js b/core/modules/menu/menu.js
index c9e14b5..589e69e 100644
--- a/core/modules/menu/menu.js
+++ b/core/modules/menu/menu.js
@@ -1,70 +1,70 @@
 (function ($) {
 
-"use strict";
+  "use strict";
 
-Drupal.behaviors.menuDetailsSummaries = {
-  attach: function (context) {
-    $(context).find('.menu-link-form').drupalSetSummary(function (context) {
-      var $context = $(context);
-      if ($context.find('.form-item-menu-enabled input').is(':checked')) {
-        return Drupal.checkPlain($context.find('.form-item-menu-link-title input').val());
-      }
-      else {
-        return Drupal.t('Not in menu');
-      }
-    });
-  }
-};
-
-/**
- * Automatically fill in a menu link title, if possible.
- */
-Drupal.behaviors.menuLinkAutomaticTitle = {
-  attach: function (context) {
-    var $context = $(context);
-    $context.find('.menu-link-form').each(function () {
-      var $this = $(this);
-      // Try to find menu settings widget elements as well as a 'title' field in
-      // the form, but play nicely with user permissions and form alterations.
-      var $checkbox = $this.find('.form-item-menu-enabled input');
-      var $link_title = $context.find('.form-item-menu-link-title input');
-      var $title = $this.closest('form').find('.form-item-title input');
-      // Bail out if we do not have all required fields.
-      if (!($checkbox.length && $link_title.length && $title.length)) {
-        return;
-      }
-      // If there is a link title already, mark it as overridden. The user expects
-      // that toggling the checkbox twice will take over the node's title.
-      if ($checkbox.is(':checked') && $link_title.val().length) {
-        $link_title.data('menuLinkAutomaticTitleOveridden', true);
-      }
-      // Whenever the value is changed manually, disable this behavior.
-      $link_title.on('keyup', function () {
-        $link_title.data('menuLinkAutomaticTitleOveridden', true);
-      });
-      // Global trigger on checkbox (do not fill-in a value when disabled).
-      $checkbox.on('change', function () {
-        if ($checkbox.is(':checked')) {
-          if (!$link_title.data('menuLinkAutomaticTitleOveridden')) {
-            $link_title.val($title.val());
-          }
+  Drupal.behaviors.menuDetailsSummaries = {
+    attach: function (context) {
+      $(context).find('.menu-link-form').drupalSetSummary(function (context) {
+        var $context = $(context);
+        if ($context.find('.form-item-menu-enabled input').is(':checked')) {
+          return Drupal.checkPlain($context.find('.form-item-menu-link-title input').val());
         }
         else {
-          $link_title.val('');
-          $link_title.removeData('menuLinkAutomaticTitleOveridden');
+          return Drupal.t('Not in menu');
         }
-        $checkbox.closest('.vertical-tabs-pane').trigger('summaryUpdated');
-        $checkbox.trigger('formUpdated');
       });
-      // Take over any title change.
-      $title.on('keyup', function () {
-        if (!$link_title.data('menuLinkAutomaticTitleOveridden') && $checkbox.is(':checked')) {
-          $link_title.val($title.val());
-          $link_title.val($title.val()).trigger('formUpdated');
+    }
+  };
+
+  /**
+   * Automatically fill in a menu link title, if possible.
+   */
+  Drupal.behaviors.menuLinkAutomaticTitle = {
+    attach: function (context) {
+      var $context = $(context);
+      $context.find('.menu-link-form').each(function () {
+        var $this = $(this);
+        // Try to find menu settings widget elements as well as a 'title' field in
+        // the form, but play nicely with user permissions and form alterations.
+        var $checkbox = $this.find('.form-item-menu-enabled input');
+        var $link_title = $context.find('.form-item-menu-link-title input');
+        var $title = $this.closest('form').find('.form-item-title input');
+        // Bail out if we do not have all required fields.
+        if (!($checkbox.length && $link_title.length && $title.length)) {
+          return;
+        }
+        // If there is a link title already, mark it as overridden. The user expects
+        // that toggling the checkbox twice will take over the node's title.
+        if ($checkbox.is(':checked') && $link_title.val().length) {
+          $link_title.data('menuLinkAutomaticTitleOveridden', true);
         }
+        // Whenever the value is changed manually, disable this behavior.
+        $link_title.on('keyup', function () {
+          $link_title.data('menuLinkAutomaticTitleOveridden', true);
+        });
+        // Global trigger on checkbox (do not fill-in a value when disabled).
+        $checkbox.on('change', function () {
+          if ($checkbox.is(':checked')) {
+            if (!$link_title.data('menuLinkAutomaticTitleOveridden')) {
+              $link_title.val($title.val());
+            }
+          }
+          else {
+            $link_title.val('');
+            $link_title.removeData('menuLinkAutomaticTitleOveridden');
+          }
+          $checkbox.closest('.vertical-tabs-pane').trigger('summaryUpdated');
+          $checkbox.trigger('formUpdated');
+        });
+        // Take over any title change.
+        $title.on('keyup', function () {
+          if (!$link_title.data('menuLinkAutomaticTitleOveridden') && $checkbox.is(':checked')) {
+            $link_title.val($title.val());
+            $link_title.val($title.val()).trigger('formUpdated');
+          }
+        });
       });
-    });
-  }
-};
+    }
+  };
 
 })(jQuery);
diff --git a/core/modules/node/content_types.js b/core/modules/node/content_types.js
index f6a545b..cf0df4b 100644
--- a/core/modules/node/content_types.js
+++ b/core/modules/node/content_types.js
@@ -5,50 +5,50 @@
 
 (function ($) {
 
-"use strict";
+  "use strict";
 
-Drupal.behaviors.contentTypes = {
-  attach: function (context) {
-    var $context = $(context);
-    // Provide the vertical tab summaries.
-    $context.find('#edit-submission').drupalSetSummary(function(context) {
-      var vals = [];
-      vals.push(Drupal.checkPlain($(context).find('#edit-title-label').val()) || Drupal.t('Requires a title'));
-      return vals.join(', ');
-    });
-    $context.find('#edit-workflow').drupalSetSummary(function(context) {
-      var vals = [];
-      $(context).find("input[name^='settings[node][options']:checked").parent().each(function() {
-        vals.push(Drupal.checkPlain($(this).text()));
+  Drupal.behaviors.contentTypes = {
+    attach: function (context) {
+      var $context = $(context);
+      // Provide the vertical tab summaries.
+      $context.find('#edit-submission').drupalSetSummary(function (context) {
+        var vals = [];
+        vals.push(Drupal.checkPlain($(context).find('#edit-title-label').val()) || Drupal.t('Requires a title'));
+        return vals.join(', ');
+      });
+      $context.find('#edit-workflow').drupalSetSummary(function (context) {
+        var vals = [];
+        $(context).find("input[name^='settings[node][options']:checked").parent().each(function () {
+          vals.push(Drupal.checkPlain($(this).text()));
+        });
+        if (!$(context).find('#edit-settings-node-options-status').is(':checked')) {
+          vals.unshift(Drupal.t('Not published'));
+        }
+        return vals.join(', ');
       });
-      if (!$(context).find('#edit-settings-node-options-status').is(':checked')) {
-        vals.unshift(Drupal.t('Not published'));
-      }
-      return vals.join(', ');
-    });
-    $('#edit-language', context).drupalSetSummary(function(context) {
-      var vals = [];
+      $('#edit-language', context).drupalSetSummary(function (context) {
+        var vals = [];
 
-      vals.push($(".form-item-language-configuration-langcode select option:selected", context).text());
+        vals.push($(".form-item-language-configuration-langcode select option:selected", context).text());
 
-      $('input:checked', context).next('label').each(function() {
-        vals.push(Drupal.checkPlain($(this).text()));
-      });
+        $('input:checked', context).next('label').each(function () {
+          vals.push(Drupal.checkPlain($(this).text()));
+        });
 
-      return vals.join(', ');
-    });
-    $context.find('#edit-display').drupalSetSummary(function(context) {
-      var vals = [];
-      var $context = $(context);
-      $context.find('input:checked').next('label').each(function() {
-        vals.push(Drupal.checkPlain($(this).text()));
+        return vals.join(', ');
+      });
+      $context.find('#edit-display').drupalSetSummary(function (context) {
+        var vals = [];
+        var $context = $(context);
+        $context.find('input:checked').next('label').each(function () {
+          vals.push(Drupal.checkPlain($(this).text()));
+        });
+        if (!$context.find('#edit-settings-node-submitted').is(':checked')) {
+          vals.unshift(Drupal.t("Don't display post information"));
+        }
+        return vals.join(', ');
       });
-      if (!$context.find('#edit-settings-node-submitted').is(':checked')) {
-        vals.unshift(Drupal.t("Don't display post information"));
-      }
-      return vals.join(', ');
-    });
-  }
-};
+    }
+  };
 
 })(jQuery);
diff --git a/core/modules/node/node.js b/core/modules/node/node.js
index f0b5a38..2ae5d5a 100644
--- a/core/modules/node/node.js
+++ b/core/modules/node/node.js
@@ -5,66 +5,66 @@
 
 (function ($, Drupal, drupalSettings) {
 
-"use strict";
+  "use strict";
 
-Drupal.behaviors.nodeDetailsSummaries = {
-  attach: function (context) {
-    var $context = $(context);
-    $context.find('.node-form-revision-information').drupalSetSummary(function (context) {
+  Drupal.behaviors.nodeDetailsSummaries = {
+    attach: function (context) {
       var $context = $(context);
-      var revisionCheckbox = $context.find('.form-item-revision input');
+      $context.find('.node-form-revision-information').drupalSetSummary(function (context) {
+        var $context = $(context);
+        var revisionCheckbox = $context.find('.form-item-revision input');
 
-      // Return 'New revision' if the 'Create new revision' checkbox is checked,
-      // or if the checkbox doesn't exist, but the revision log does. For users
-      // without the "Administer content" permission the checkbox won't appear,
-      // but the revision log will if the content type is set to auto-revision.
-      if (revisionCheckbox.is(':checked') || (!revisionCheckbox.length && $context.find('.form-item-log textarea').length)) {
-        return Drupal.t('New revision');
-      }
+        // Return 'New revision' if the 'Create new revision' checkbox is checked,
+        // or if the checkbox doesn't exist, but the revision log does. For users
+        // without the "Administer content" permission the checkbox won't appear,
+        // but the revision log will if the content type is set to auto-revision.
+        if (revisionCheckbox.is(':checked') || (!revisionCheckbox.length && $context.find('.form-item-log textarea').length)) {
+          return Drupal.t('New revision');
+        }
 
-      return Drupal.t('No revision');
-    });
+        return Drupal.t('No revision');
+      });
 
-    $context.find('.node-form-author').drupalSetSummary(function (context) {
-      var $context = $(context);
-      var name = $context.find('.form-item-name input').val() || drupalSettings.anonymous,
-        date = $context.find('.form-item-date input').val();
-      return date ?
-        Drupal.t('By @name on @date', { '@name': name, '@date': date }) :
-        Drupal.t('By @name', { '@name': name });
-    });
+      $context.find('.node-form-author').drupalSetSummary(function (context) {
+        var $context = $(context);
+        var name = $context.find('.form-item-name input').val() || drupalSettings.anonymous,
+          date = $context.find('.form-item-date input').val();
+        return date ?
+          Drupal.t('By @name on @date', { '@name': name, '@date': date }) :
+          Drupal.t('By @name', { '@name': name });
+      });
 
-    $context.find('.node-form-options').drupalSetSummary(function (context) {
-      var $context = $(context);
-      var vals = [];
+      $context.find('.node-form-options').drupalSetSummary(function (context) {
+        var $context = $(context);
+        var vals = [];
 
-      if ($context.find('input').is(':checked')) {
-        $context.find('input:checked').parent().each(function () {
-          vals.push(Drupal.checkPlain($.trim($(this).text())));
-        });
-        return vals.join(', ');
-      }
-      else {
-        return Drupal.t('Not promoted');
-      }
-    });
+        if ($context.find('input').is(':checked')) {
+          $context.find('input:checked').parent().each(function () {
+            vals.push(Drupal.checkPlain($.trim($(this).text())));
+          });
+          return vals.join(', ');
+        }
+        else {
+          return Drupal.t('Not promoted');
+        }
+      });
 
-    $context.find('fieldset.node-translation-options').drupalSetSummary(function (context) {
-      var $context = $(context);
-      var translate;
-      var $checkbox = $context.find('.form-item-translation-translate input');
+      $context.find('fieldset.node-translation-options').drupalSetSummary(function (context) {
+        var $context = $(context);
+        var translate;
+        var $checkbox = $context.find('.form-item-translation-translate input');
 
-      if ($checkbox.size()) {
-        translate = $checkbox.is(':checked') ? Drupal.t('Needs to be updated') : Drupal.t('Does not need to be updated');
-      }
-      else {
-        $checkbox = $context.find('.form-item-translation-retranslate input');
-        translate = $checkbox.is(':checked') ? Drupal.t('Flag other translations as outdated') : Drupal.t('Do not flag other translations as outdated');
-      }
+        if ($checkbox.size()) {
+          translate = $checkbox.is(':checked') ? Drupal.t('Needs to be updated') : Drupal.t('Does not need to be updated');
+        }
+        else {
+          $checkbox = $context.find('.form-item-translation-retranslate input');
+          translate = $checkbox.is(':checked') ? Drupal.t('Flag other translations as outdated') : Drupal.t('Do not flag other translations as outdated');
+        }
 
-      return translate;
-    });
-  }
-};
+        return translate;
+      });
+    }
+  };
 
 })(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/node/node.preview.js b/core/modules/node/node.preview.js
index 3be74e2..f27cbba 100644
--- a/core/modules/node/node.preview.js
+++ b/core/modules/node/node.preview.js
@@ -1,28 +1,28 @@
 (function ($, Drupal) {
 
-"use strict";
+  "use strict";
 
-/**
- * Disabling all links (except local fragment identifiers such as href="#frag")
- * in node previews to prevent users from leaving the page.
- */
-Drupal.behaviors.nodePreviewDestroyLinks = {
-  attach: function (context) {
-    var $preview = $(context).find('.node').once('node-preview');
-    if ($preview.length) {
-      $preview.on('click.preview', 'a:not([href^=#])', function (e) {
-        e.preventDefault();
-      });
-    }
-  },
-  detach: function (context, settings, trigger) {
-    if (trigger === 'unload') {
-      var $preview = $(context).find('.node').removeOnce('node-preview');
+  /**
+   * Disabling all links (except local fragment identifiers such as href="#frag")
+   * in node previews to prevent users from leaving the page.
+   */
+  Drupal.behaviors.nodePreviewDestroyLinks = {
+    attach: function (context) {
+      var $preview = $(context).find('.node').once('node-preview');
       if ($preview.length) {
-        $preview.off('click.preview');
+        $preview.on('click.preview', 'a:not([href^=#])', function (e) {
+          e.preventDefault();
+        });
+      }
+    },
+    detach: function (context, settings, trigger) {
+      if (trigger === 'unload') {
+        var $preview = $(context).find('.node').removeOnce('node-preview');
+        if ($preview.length) {
+          $preview.off('click.preview');
+        }
       }
     }
-  }
-};
+  };
 
 })(jQuery, Drupal);
diff --git a/core/modules/path/path.js b/core/modules/path/path.js
index 7349d12..f0a6687 100644
--- a/core/modules/path/path.js
+++ b/core/modules/path/path.js
@@ -4,18 +4,18 @@
  */
 (function ($) {
 
-"use strict";
+  "use strict";
 
-Drupal.behaviors.pathDetailsSummaries = {
-  attach: function (context) {
-    $(context).find('.path-form').drupalSetSummary(function (context) {
-      var path = $('.form-item-path-alias input').val();
+  Drupal.behaviors.pathDetailsSummaries = {
+    attach: function (context) {
+      $(context).find('.path-form').drupalSetSummary(function (context) {
+        var path = $('.form-item-path-alias input').val();
 
-      return path ?
-        Drupal.t('Alias: @alias', { '@alias': path }) :
-        Drupal.t('No alias');
-    });
-  }
-};
+        return path ?
+          Drupal.t('Alias: @alias', { '@alias': path }) :
+          Drupal.t('No alias');
+      });
+    }
+  };
 
 })(jQuery);
diff --git a/core/modules/picture/picturefill/picturefill.js b/core/modules/picture/picturefill/picturefill.js
index 8df8eb9..4d69569 100644
--- a/core/modules/picture/picturefill/picturefill.js
+++ b/core/modules/picture/picturefill/picturefill.js
@@ -1,6 +1,6 @@
 /*jshint loopfunc: true, browser: true, curly: true, eqeqeq: true, expr: true, forin: true, latedef: true, newcap: true, noarg: true, trailing: true, undef: true, unused: true */
 /*! Picturefill - Author: Scott Jehl, 2012 | License: MIT/GPLv2 */
-(function( w ){
+(function (w) {
 
   // Enable strict mode.
   "use strict";
@@ -10,7 +10,7 @@
     return;
   }
 
-  w.picturefill = function() {
+  w.picturefill = function () {
     // Copy attributes from the source to the destination.
     function _copyAttributes(src, tar) {
       if (src.getAttribute('width') && src.getAttribute('height')) {
@@ -23,7 +23,7 @@
     var ps = w.document.getElementsByTagName('picture');
 
     // Loop the pictures.
-    for (var i = 0, il = ps.length; i < il; i++ ) {
+    for (var i = 0, il = ps.length; i < il; i++) {
       var sources = ps[i].getElementsByTagName('source');
       var picImg = null;
       var matches = [];
@@ -41,7 +41,7 @@
       }
 
       // See which sources match.
-      for (var j = 0, jl = sources.length; j < jl; j++ ) {
+      for (var j = 0, jl = sources.length; j < jl; j++) {
         var media = sources[j].getAttribute('media');
         // If there's no media specified or the media query matches, add it.
         if (!media || (w.matchMedia && w.matchMedia(media).matches)) {
@@ -71,7 +71,7 @@
           sources = srcset.split(', ');
 
           // Loop through each source/resolution in srcset.
-          for (var res = sources.length, r = res - 1; r >= 0; r-- ) {
+          for (var res = sources.length, r = res - 1; r >= 0; r--) {
             // Remove any leading whitespace, then split on spaces.
             var source = sources[ r ].replace(/^\s*/, '').replace(/\s*$/, '').split(' ');
             // Parse out the resolution for each source in `srcset`.
@@ -84,7 +84,7 @@
                 newImg.src = source[0];
                 // When the image is loaded, set a width equal to that of the
                 // original’s intrinsic width divided by the screen resolution.
-                newImg.onload = function() {
+                newImg.onload = function () {
                   // Clone the original image into memory so the width is
                   // unaffected by page styles.
                   var w = this.cloneNode(true).width;
@@ -113,7 +113,7 @@
   // Run on resize and domready (w.load as a fallback)
   if (w.addEventListener) {
     w.addEventListener('resize', w.picturefill, false);
-    w.addEventListener('DOMContentLoaded', function() {
+    w.addEventListener('DOMContentLoaded', function () {
       w.picturefill();
       // Run once only.
       w.removeEventListener('load', w.picturefill, false);
diff --git a/core/modules/shortcut/shortcut.admin.js b/core/modules/shortcut/shortcut.admin.js
index c95e4fd..7fbdc5c 100644
--- a/core/modules/shortcut/shortcut.admin.js
+++ b/core/modules/shortcut/shortcut.admin.js
@@ -1,18 +1,18 @@
 (function ($) {
 
-"use strict";
+  "use strict";
 
-/**
- * Make it so when you enter text into the "New set" textfield, the
- * corresponding radio button gets selected.
- */
-Drupal.behaviors.newSet = {
-  attach: function (context, settings) {
-    var selectDefault = function() {
-      $(this).closest('form').find('.form-item-set .form-type-radio:last input').prop('checked', true);
-    };
-    $('div.form-item-new input').on('focus', selectDefault).on('keyup', selectDefault);
-  }
-};
+  /**
+   * Make it so when you enter text into the "New set" textfield, the
+   * corresponding radio button gets selected.
+   */
+  Drupal.behaviors.newSet = {
+    attach: function (context, settings) {
+      var selectDefault = function () {
+        $(this).closest('form').find('.form-item-set .form-type-radio:last input').prop('checked', true);
+      };
+      $('div.form-item-new input').on('focus', selectDefault).on('keyup', selectDefault);
+    }
+  };
 
 })(jQuery);
diff --git a/core/modules/simpletest/simpletest.js b/core/modules/simpletest/simpletest.js
index aa422ed..ce79a0f 100644
--- a/core/modules/simpletest/simpletest.js
+++ b/core/modules/simpletest/simpletest.js
@@ -1,160 +1,160 @@
 (function ($) {
 
-"use strict";
-
-/**
- * Add the cool table collapsing on the testing overview page.
- */
-Drupal.behaviors.simpleTestMenuCollapse = {
-  attach: function (context) {
-    var timeout = null;
-    // Adds expand-collapse functionality.
-    $('div.simpletest-image').once('simpletest-image', function () {
-      var $this = $(this);
-      var direction = drupalSettings.simpleTest[this.id].imageDirection;
-      $this.html(drupalSettings.simpleTest.images[direction]);
-
-      // Adds group toggling functionality to arrow images.
-      $this.on('click', function () {
-        var trs = $this.closest('tbody').children('.' + drupalSettings.simpleTest[this.id].testClass);
+  "use strict";
+
+  /**
+   * Add the cool table collapsing on the testing overview page.
+   */
+  Drupal.behaviors.simpleTestMenuCollapse = {
+    attach: function (context) {
+      var timeout = null;
+      // Adds expand-collapse functionality.
+      $('div.simpletest-image').once('simpletest-image', function () {
+        var $this = $(this);
         var direction = drupalSettings.simpleTest[this.id].imageDirection;
-        var row = direction ? trs.length - 1 : 0;
+        $this.html(drupalSettings.simpleTest.images[direction]);
 
-        // If clicked in the middle of expanding a group, stop so we can switch directions.
-        if (timeout) {
-          clearTimeout(timeout);
-        }
+        // Adds group toggling functionality to arrow images.
+        $this.on('click', function () {
+          var trs = $this.closest('tbody').children('.' + drupalSettings.simpleTest[this.id].testClass);
+          var direction = drupalSettings.simpleTest[this.id].imageDirection;
+          var row = direction ? trs.length - 1 : 0;
 
-        // Function to toggle an individual row according to the current direction.
-        // We set a timeout of 20 ms until the next row will be shown/hidden to
-        // create a sliding effect.
-        function rowToggle() {
-          if (direction) {
-            if (row >= 0) {
-              $(trs[row]).hide();
-              row--;
-              timeout = setTimeout(rowToggle, 20);
-            }
+          // If clicked in the middle of expanding a group, stop so we can switch directions.
+          if (timeout) {
+            clearTimeout(timeout);
           }
-          else {
-            if (row < trs.length) {
-              $(trs[row]).removeClass('js-hide').show();
-              row++;
-              timeout = setTimeout(rowToggle, 20);
+
+          // Function to toggle an individual row according to the current direction.
+          // We set a timeout of 20 ms until the next row will be shown/hidden to
+          // create a sliding effect.
+          function rowToggle() {
+            if (direction) {
+              if (row >= 0) {
+                $(trs[row]).hide();
+                row--;
+                timeout = setTimeout(rowToggle, 20);
+              }
+            }
+            else {
+              if (row < trs.length) {
+                $(trs[row]).removeClass('js-hide').show();
+                row++;
+                timeout = setTimeout(rowToggle, 20);
+              }
             }
           }
-        }
 
-        // Kick-off the toggling upon a new click.
-        rowToggle();
+          // Kick-off the toggling upon a new click.
+          rowToggle();
 
-        // Toggle the arrow image next to the test group title.
-        $this.html(drupalSettings.simpleTest.images[(direction ? 0 : 1)]);
-        drupalSettings.simpleTest[this.id].imageDirection = !direction;
+          // Toggle the arrow image next to the test group title.
+          $this.html(drupalSettings.simpleTest.images[(direction ? 0 : 1)]);
+          drupalSettings.simpleTest[this.id].imageDirection = !direction;
 
+        });
       });
-    });
-  }
-};
-
-/**
- * Select/deselect all the inner checkboxes when the outer checkboxes are
- * selected/deselected.
- */
-Drupal.behaviors.simpleTestSelectAll = {
-  attach: function (context) {
-    $('td.simpletest-select-all').once('simpletest-select-all', function () {
-      var testCheckboxes = drupalSettings.simpleTest['simpletest-test-group-' + $(this).attr('id')].testNames;
-      var groupCheckbox = $('<input type="checkbox" class="form-checkbox" id="' + $(this).attr('id') + '-select-all" />');
-
-      // Each time a single-test checkbox is checked or unchecked, make sure
-      // that the associated group checkbox gets the right state too.
-      function updateGroupCheckbox() {
-        var checkedTests = 0;
-        for (var i = 0; i < testCheckboxes.length; i++) {
-          if ($('#' + testCheckboxes[i]).prop('checked')) {
-            checkedTests++;
+    }
+  };
+
+  /**
+   * Select/deselect all the inner checkboxes when the outer checkboxes are
+   * selected/deselected.
+   */
+  Drupal.behaviors.simpleTestSelectAll = {
+    attach: function (context) {
+      $('td.simpletest-select-all').once('simpletest-select-all', function () {
+        var testCheckboxes = drupalSettings.simpleTest['simpletest-test-group-' + $(this).attr('id')].testNames;
+        var groupCheckbox = $('<input type="checkbox" class="form-checkbox" id="' + $(this).attr('id') + '-select-all" />');
+
+        // Each time a single-test checkbox is checked or unchecked, make sure
+        // that the associated group checkbox gets the right state too.
+        function updateGroupCheckbox() {
+          var checkedTests = 0;
+          for (var i = 0; i < testCheckboxes.length; i++) {
+            if ($('#' + testCheckboxes[i]).prop('checked')) {
+              checkedTests++;
+            }
           }
+          $(groupCheckbox).prop('checked', (checkedTests === testCheckboxes.length));
         }
-        $(groupCheckbox).prop('checked', (checkedTests === testCheckboxes.length));
-      }
 
-      // Have the single-test checkboxes follow the group checkbox.
-      groupCheckbox.on('change', function () {
-        var checked = $(this).prop('checked');
+        // Have the single-test checkboxes follow the group checkbox.
+        groupCheckbox.on('change', function () {
+          var checked = $(this).prop('checked');
+          for (var i = 0; i < testCheckboxes.length; i++) {
+            $('#' + testCheckboxes[i]).prop('checked', checked);
+          }
+        });
+
+        // Have the group checkbox follow the single-test checkboxes.
         for (var i = 0; i < testCheckboxes.length; i++) {
-          $('#' + testCheckboxes[i]).prop('checked', checked);
+          $('#' + testCheckboxes[i]).on('change', updateGroupCheckbox);
         }
+
+        // Initialize status for the group checkbox correctly.
+        updateGroupCheckbox();
+        $(this).append(groupCheckbox);
       });
+    }
+  };
+
+  /**
+   * Filters the test list table by a text input search string.
+   *
+   * Additionally accounts for multiple tables being wrapped in "package" details
+   * elements.
+   *
+   * Text search input: input.table-filter-text
+   * Target table:      input.table-filter-text[data-table]
+   * Source text:       .table-filter-text-source
+   */
+  Drupal.behaviors.simpletestTableFilterByText = {
+    attach: function (context) {
+      var $input = $('input.table-filter-text').once('table-filter-text');
+      var $table = $($input.attr('data-table'));
+      var $rows;
+      var searched = false;
+
+      function filterTestList(e) {
+        var query = $(e.target).val().toLowerCase();
+
+        function showTestRow(index, row) {
+          var $row = $(row);
+          var $sources = $row.find('.table-filter-text-source');
+          var textMatch = $sources.text().toLowerCase().indexOf(query) !== -1;
+          $row.closest('tr').toggle(textMatch);
+        }
 
-      // Have the group checkbox follow the single-test checkboxes.
-      for (var i = 0; i < testCheckboxes.length; i++) {
-        $('#' + testCheckboxes[i]).on('change', updateGroupCheckbox);
-      }
+        // Filter if the length of the query is at least 3 characters.
+        if (query.length >= 3) {
+          // Indicate that a search has been performed, and hide the "select all"
+          // checkbox.
+          searched = true;
+          $('#simpletest-form-table thead th.select-all input').hide();
 
-      // Initialize status for the group checkbox correctly.
-      updateGroupCheckbox();
-      $(this).append(groupCheckbox);
-    });
-  }
-};
-
-/**
- * Filters the test list table by a text input search string.
- *
- * Additionally accounts for multiple tables being wrapped in "package" details
- * elements.
- *
- * Text search input: input.table-filter-text
- * Target table:      input.table-filter-text[data-table]
- * Source text:       .table-filter-text-source
- */
-Drupal.behaviors.simpletestTableFilterByText = {
-  attach: function (context) {
-    var $input = $('input.table-filter-text').once('table-filter-text');
-    var $table = $($input.attr('data-table'));
-    var $rows;
-    var searched = false;
-
-    function filterTestList (e) {
-      var query = $(e.target).val().toLowerCase();
-
-      function showTestRow (index, row) {
-        var $row = $(row);
-        var $sources = $row.find('.table-filter-text-source');
-        var textMatch = $sources.text().toLowerCase().indexOf(query) !== -1;
-        $row.closest('tr').toggle(textMatch);
+          $rows.each(showTestRow);
+        }
+        // Restore to the original state if any searching has occurred.
+        else if (searched) {
+          searched = false;
+          $('#simpletest-form-table thead th.select-all input').show();
+          // Hide all rows and then show groups.
+          $rows.hide();
+          $rows.filter('.simpletest-group').show().each(function () {
+            var id = 'simpletest-test-group-' + $(this).children().first().attr('id');
+            if (drupalSettings.simpleTest[id].imageDirection) {
+              $(this).closest('tbody').children('.' + drupalSettings.simpleTest[id].testClass).show();
+            }
+          });
+        }
       }
 
-      // Filter if the length of the query is at least 3 characters.
-      if (query.length >= 3) {
-        // Indicate that a search has been performed, and hide the "select all"
-        // checkbox.
-        searched = true;
-        $('#simpletest-form-table thead th.select-all input').hide();
-
-        $rows.each(showTestRow);
-      }
-      // Restore to the original state if any searching has occurred.
-      else if (searched) {
-        searched = false;
-        $('#simpletest-form-table thead th.select-all input').show();
-        // Hide all rows and then show groups.
-        $rows.hide();
-        $rows.filter('.simpletest-group').show().each(function () {
-          var id = 'simpletest-test-group-' + $(this).children().first().attr('id');
-          if (drupalSettings.simpleTest[id].imageDirection) {
-            $(this).closest('tbody').children('.' + drupalSettings.simpleTest[id].testClass).show();
-          }
-        });
+      if ($table.length) {
+        $rows = $table.find('tbody tr');
+        $input.trigger('focus').on('keyup', Drupal.debounce(filterTestList, 200));
       }
     }
-
-    if ($table.length) {
-      $rows = $table.find('tbody tr');
-      $input.trigger('focus').on('keyup', Drupal.debounce(filterTestList, 200));
-    }
-  }
-};
+  };
 
 })(jQuery);
diff --git a/core/modules/statistics/statistics.js b/core/modules/statistics/statistics.js
index 5d66655..35bba66 100644
--- a/core/modules/statistics/statistics.js
+++ b/core/modules/statistics/statistics.js
@@ -2,7 +2,7 @@
 
   "use strict";
 
-  $(document).ready(function() {
+  $(document).ready(function () {
     $.ajax({
       type: "POST",
       cache: false,
diff --git a/core/modules/system/system.js b/core/modules/system/system.js
index 18f7e7a..bd9cd7d 100644
--- a/core/modules/system/system.js
+++ b/core/modules/system/system.js
@@ -1,63 +1,63 @@
 (function ($, Drupal, drupalSettings) {
 
-"use strict";
+  "use strict";
 
-// Cache IDs in an array for ease of use.
-var ids = [];
+  // Cache IDs in an array for ease of use.
+  var ids = [];
 
-/**
- * When a field is filled out, apply its value to other fields that will likely
- * use the same value. In the installer this is used to populate the
- * administrator e-mail address with the same value as the site e-mail address.
- */
-Drupal.behaviors.copyFieldValue = {
-  attach: function (context) {
-    // List of fields IDs on which to bind the event listener.
-    // Create an array of IDs to use with jQuery.
-    for (var sourceId in drupalSettings.copyFieldValue) {
-      if (drupalSettings.copyFieldValue.hasOwnProperty(sourceId)) {
-        ids.push(sourceId);
-      }
-    }
-    if (ids.length) {
-      // Listen to value:copy events on all dependent fields.
-      // We have to use body and not document because of the way jQuery events
-      // bubble up the DOM tree.
-      $('body').once('copy-field-values').on('value:copy', this.valueTargetCopyHandler);
-      // Listen on all source elements.
-      $('#' + ids.join(', #')).once('copy-field-values').on('blur', this.valueSourceBlurHandler);
-    }
-  },
-  detach: function (context, settings, trigger) {
-    if (trigger === 'unload' && ids.length) {
-      $('body').removeOnce('copy-field-values').off('value:copy');
-      $('#' + ids.join(', #')).removeOnce('copy-field-values').off('blur');
-    }
-  },
   /**
-   * Event handler that fill the target element with the specified value.
-   *
-   * @param e
-   *   Event object.
-   * @param value
-   *   Custom value from jQuery trigger.
+   * When a field is filled out, apply its value to other fields that will likely
+   * use the same value. In the installer this is used to populate the
+   * administrator e-mail address with the same value as the site e-mail address.
    */
-  valueTargetCopyHandler: function (e, value) {
-    var $target = $(e.target);
-    if ($target.val() === '') {
-      $target.val(value);
+  Drupal.behaviors.copyFieldValue = {
+    attach: function (context) {
+      // List of fields IDs on which to bind the event listener.
+      // Create an array of IDs to use with jQuery.
+      for (var sourceId in drupalSettings.copyFieldValue) {
+        if (drupalSettings.copyFieldValue.hasOwnProperty(sourceId)) {
+          ids.push(sourceId);
+        }
+      }
+      if (ids.length) {
+        // Listen to value:copy events on all dependent fields.
+        // We have to use body and not document because of the way jQuery events
+        // bubble up the DOM tree.
+        $('body').once('copy-field-values').on('value:copy', this.valueTargetCopyHandler);
+        // Listen on all source elements.
+        $('#' + ids.join(', #')).once('copy-field-values').on('blur', this.valueSourceBlurHandler);
+      }
+    },
+    detach: function (context, settings, trigger) {
+      if (trigger === 'unload' && ids.length) {
+        $('body').removeOnce('copy-field-values').off('value:copy');
+        $('#' + ids.join(', #')).removeOnce('copy-field-values').off('blur');
+      }
+    },
+    /**
+     * Event handler that fill the target element with the specified value.
+     *
+     * @param e
+     *   Event object.
+     * @param value
+     *   Custom value from jQuery trigger.
+     */
+    valueTargetCopyHandler: function (e, value) {
+      var $target = $(e.target);
+      if ($target.val() === '') {
+        $target.val(value);
+      }
+    },
+    /**
+     * Handler for a Blur event on a source field.
+     *
+     * This event handler will trigger a 'value:copy' event on all dependent fields.
+     */
+    valueSourceBlurHandler: function (e) {
+      var value = $(e.target).val();
+      var targetIds = drupalSettings.copyFieldValue[e.target.id];
+      $('#' + targetIds.join(', #')).trigger('value:copy', value);
     }
-  },
-  /**
-   * Handler for a Blur event on a source field.
-   *
-   * This event handler will trigger a 'value:copy' event on all dependent fields.
-   */
-  valueSourceBlurHandler: function (e) {
-    var value = $(e.target).val();
-    var targetIds = drupalSettings.copyFieldValue[e.target.id];
-    $('#' + targetIds.join(', #')).trigger('value:copy', value);
-  }
-};
+  };
 
 })(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/system/system.modules.js b/core/modules/system/system.modules.js
index 4d97652..5ccad36 100644
--- a/core/modules/system/system.modules.js
+++ b/core/modules/system/system.modules.js
@@ -1,60 +1,60 @@
 (function ($, Drupal) {
 
-"use strict";
-
-/**
- * Filters the module list table by a text input search string.
- *
- * Additionally accounts for multiple tables being wrapped in "package" details
- * elements.
- *
- * Text search input: input.table-filter-text
- * Target table:      input.table-filter-text[data-table]
- * Source text:       .table-filter-text-source
- */
-Drupal.behaviors.tableFilterByText = {
-  attach: function (context, settings) {
-    var $input = $('input.table-filter-text').once('table-filter-text');
-    var $table = $($input.attr('data-table'));
-    var $rowsAndDetails, $rows, $details;
-
-    function hidePackageDetails(index, element) {
-      var $details = $(element);
-      var $visibleRows = $details.find('table:not(.sticky-header)').find('tbody tr:visible');
-      $details.toggle($visibleRows.length > 0);
-    }
-
-    function filterModuleList (e) {
-      var query = $(e.target).val().toLowerCase();
-
-      function showModuleRow (index, row) {
-        var $row = $(row);
-        var $sources = $row.find('.table-filter-text-source');
-        var textMatch = $sources.text().toLowerCase().indexOf(query) !== -1;
-        $row.closest('tr').toggle(textMatch);
+  "use strict";
+
+  /**
+   * Filters the module list table by a text input search string.
+   *
+   * Additionally accounts for multiple tables being wrapped in "package" details
+   * elements.
+   *
+   * Text search input: input.table-filter-text
+   * Target table:      input.table-filter-text[data-table]
+   * Source text:       .table-filter-text-source
+   */
+  Drupal.behaviors.tableFilterByText = {
+    attach: function (context, settings) {
+      var $input = $('input.table-filter-text').once('table-filter-text');
+      var $table = $($input.attr('data-table'));
+      var $rowsAndDetails, $rows, $details;
+
+      function hidePackageDetails(index, element) {
+        var $details = $(element);
+        var $visibleRows = $details.find('table:not(.sticky-header)').find('tbody tr:visible');
+        $details.toggle($visibleRows.length > 0);
       }
 
-      // Filter if the length of the query is at least 2 characters.
-      if (query.length >= 2) {
-        $rows.each(showModuleRow);
-
-        // Hide the package <details> if they don't have any visible rows.
-        // Note that we first show() all <details> to be able to use ':visible'.
-        $details.show().each(hidePackageDetails);
+      function filterModuleList(e) {
+        var query = $(e.target).val().toLowerCase();
+
+        function showModuleRow(index, row) {
+          var $row = $(row);
+          var $sources = $row.find('.table-filter-text-source');
+          var textMatch = $sources.text().toLowerCase().indexOf(query) !== -1;
+          $row.closest('tr').toggle(textMatch);
+        }
+
+        // Filter if the length of the query is at least 2 characters.
+        if (query.length >= 2) {
+          $rows.each(showModuleRow);
+
+          // Hide the package <details> if they don't have any visible rows.
+          // Note that we first show() all <details> to be able to use ':visible'.
+          $details.show().each(hidePackageDetails);
+        }
+        else {
+          $rowsAndDetails.show();
+        }
       }
-      else {
-        $rowsAndDetails.show();
-      }
-    }
 
-    if ($table.length) {
-      $rowsAndDetails = $table.find('tr, details');
-      $rows = $table.find('tbody tr');
-      $details = $rowsAndDetails.filter('.package-listing');
+      if ($table.length) {
+        $rowsAndDetails = $table.find('tr, details');
+        $rows = $table.find('tbody tr');
+        $details = $rowsAndDetails.filter('.package-listing');
 
-      $input.on('keyup', filterModuleList);
+        $input.on('keyup', filterModuleList);
+      }
     }
-  }
-};
+  };
 
 }(jQuery, Drupal));
diff --git a/core/modules/taxonomy/taxonomy.js b/core/modules/taxonomy/taxonomy.js
index 19d42df..8ffe502 100644
--- a/core/modules/taxonomy/taxonomy.js
+++ b/core/modules/taxonomy/taxonomy.js
@@ -1,45 +1,45 @@
 (function ($) {
 
-"use strict";
+  "use strict";
 
-/**
- * Move a block in the blocks table from one region to another via select list.
- *
- * This behavior is dependent on the tableDrag behavior, since it uses the
- * objects initialized in that behavior to update the row.
- */
-Drupal.behaviors.termDrag = {
-  attach: function (context, settings) {
-    var backStep = settings.taxonomy.backStep;
-    var forwardStep = settings.taxonomy.forwardStep;
-    var tableDrag = Drupal.tableDrag.taxonomy; // Get the blocks tableDrag object.
-    var $table = $('#taxonomy');
-    var rows = $table.find('tr').length;
+  /**
+   * Move a block in the blocks table from one region to another via select list.
+   *
+   * This behavior is dependent on the tableDrag behavior, since it uses the
+   * objects initialized in that behavior to update the row.
+   */
+  Drupal.behaviors.termDrag = {
+    attach: function (context, settings) {
+      var backStep = settings.taxonomy.backStep;
+      var forwardStep = settings.taxonomy.forwardStep;
+      var tableDrag = Drupal.tableDrag.taxonomy; // Get the blocks tableDrag object.
+      var $table = $('#taxonomy');
+      var rows = $table.find('tr').length;
 
-    // When a row is swapped, keep previous and next page classes set.
-    tableDrag.row.prototype.onSwap = function (swappedRow) {
-      $table.find('tr.taxonomy-term-preview').removeClass('taxonomy-term-preview');
-      $table.find('tr.taxonomy-term-divider-top').removeClass('taxonomy-term-divider-top');
-      $table.find('tr.taxonomy-term-divider-bottom').removeClass('taxonomy-term-divider-bottom');
+      // When a row is swapped, keep previous and next page classes set.
+      tableDrag.row.prototype.onSwap = function (swappedRow) {
+        $table.find('tr.taxonomy-term-preview').removeClass('taxonomy-term-preview');
+        $table.find('tr.taxonomy-term-divider-top').removeClass('taxonomy-term-divider-top');
+        $table.find('tr.taxonomy-term-divider-bottom').removeClass('taxonomy-term-divider-bottom');
 
-      var tableBody = $table[0].tBodies[0];
-      if (backStep) {
-        for (var n = 0; n < backStep; n++) {
-          $(tableBody.rows[n]).addClass('taxonomy-term-preview');
+        var tableBody = $table[0].tBodies[0];
+        if (backStep) {
+          for (var n = 0; n < backStep; n++) {
+            $(tableBody.rows[n]).addClass('taxonomy-term-preview');
+          }
+          $(tableBody.rows[backStep - 1]).addClass('taxonomy-term-divider-top');
+          $(tableBody.rows[backStep]).addClass('taxonomy-term-divider-bottom');
         }
-        $(tableBody.rows[backStep - 1]).addClass('taxonomy-term-divider-top');
-        $(tableBody.rows[backStep]).addClass('taxonomy-term-divider-bottom');
-      }
 
-      if (forwardStep) {
-        for (var k = rows - forwardStep - 1; k < rows - 1; k++) {
-          $(tableBody.rows[k]).addClass('taxonomy-term-preview');
+        if (forwardStep) {
+          for (var k = rows - forwardStep - 1; k < rows - 1; k++) {
+            $(tableBody.rows[k]).addClass('taxonomy-term-preview');
+          }
+          $(tableBody.rows[rows - forwardStep - 2]).addClass('taxonomy-term-divider-top');
+          $(tableBody.rows[rows - forwardStep - 1]).addClass('taxonomy-term-divider-bottom');
         }
-        $(tableBody.rows[rows - forwardStep - 2]).addClass('taxonomy-term-divider-top');
-        $(tableBody.rows[rows - forwardStep - 1]).addClass('taxonomy-term-divider-bottom');
-      }
-    };
-  }
-};
+      };
+    }
+  };
 
 })(jQuery);
diff --git a/core/modules/text/text.js b/core/modules/text/text.js
index 78a180a..4aec0c7 100644
--- a/core/modules/text/text.js
+++ b/core/modules/text/text.js
@@ -1,51 +1,51 @@
 (function ($) {
 
-"use strict";
+  "use strict";
 
-/**
- * Auto-hide summary textarea if empty and show hide and unhide links.
- */
-Drupal.behaviors.textSummary = {
-  attach: function (context, settings) {
-    $(context).find('.text-summary').once('text-summary', function () {
-      var $widget = $(this).closest('.text-format-wrapper');
+  /**
+   * Auto-hide summary textarea if empty and show hide and unhide links.
+   */
+  Drupal.behaviors.textSummary = {
+    attach: function (context, settings) {
+      $(context).find('.text-summary').once('text-summary', function () {
+        var $widget = $(this).closest('.text-format-wrapper');
 
-      var $summary = $widget.find('.text-summary-wrapper');
-      var $summaryLabel = $summary.find('label').first();
-      var $full = $widget.find('.text-full').closest('.form-item');
-      var $fullLabel = $full.find('label').first();
+        var $summary = $widget.find('.text-summary-wrapper');
+        var $summaryLabel = $summary.find('label').first();
+        var $full = $widget.find('.text-full').closest('.form-item');
+        var $fullLabel = $full.find('label').first();
 
-      // Create a placeholder label when the field cardinality is greater
-      // than 1.
-      if ($fullLabel.length === 0) {
-        $fullLabel = $('<label></label>').prependTo($full);
-      }
-
-      // Set up the edit/hide summary link.
-      var $link = $('<span class="field-edit-link"> (<button type="button" class="link link-edit-summary">' + Drupal.t('Hide summary') + '</button>)</span>');
-      var $button = $link.find('button');
-      var toggleClick = true;
-      $link.on('click', function (e) {
-        if (toggleClick) {
-          $summary.hide();
-          $button.html(Drupal.t('Edit summary'));
-          $link.appendTo($fullLabel);
-        }
-        else {
-          $summary.show();
-          $button.html(Drupal.t('Hide summary'));
-          $link.appendTo($summaryLabel);
+        // Create a placeholder label when the field cardinality is greater
+        // than 1.
+        if ($fullLabel.length === 0) {
+          $fullLabel = $('<label></label>').prependTo($full);
         }
-        e.preventDefault();
-        toggleClick = !toggleClick;
-      }).appendTo($summaryLabel);
 
-      // If no summary is set, hide the summary field.
-      if ($widget.find('.text-summary').val() === '') {
-        $link.trigger('click');
-      }
-    });
-  }
-};
+        // Set up the edit/hide summary link.
+        var $link = $('<span class="field-edit-link"> (<button type="button" class="link link-edit-summary">' + Drupal.t('Hide summary') + '</button>)</span>');
+        var $button = $link.find('button');
+        var toggleClick = true;
+        $link.on('click',function (e) {
+          if (toggleClick) {
+            $summary.hide();
+            $button.html(Drupal.t('Edit summary'));
+            $link.appendTo($fullLabel);
+          }
+          else {
+            $summary.show();
+            $button.html(Drupal.t('Hide summary'));
+            $link.appendTo($summaryLabel);
+          }
+          e.preventDefault();
+          toggleClick = !toggleClick;
+        }).appendTo($summaryLabel);
+
+        // If no summary is set, hide the summary field.
+        if ($widget.find('.text-summary').val() === '') {
+          $link.trigger('click');
+        }
+      });
+    }
+  };
 
 })(jQuery);
diff --git a/core/modules/toolbar/js/escapeAdmin.js b/core/modules/toolbar/js/escapeAdmin.js
index 72a9fb8..4553bb7 100644
--- a/core/modules/toolbar/js/escapeAdmin.js
+++ b/core/modules/toolbar/js/escapeAdmin.js
@@ -5,35 +5,35 @@
  */
 (function ($, Drupal, drupalSettings) {
 
-"use strict";
+  "use strict";
 
-var pathInfo = drupalSettings.path;
-var escapeAdminPath = sessionStorage.getItem('escapeAdminPath');
+  var pathInfo = drupalSettings.path;
+  var escapeAdminPath = sessionStorage.getItem('escapeAdminPath');
 
-// Saves the last non-administrative page in the browser to be able to link back
-// to it when browsing administrative pages. If there is a destination parameter
-// there is not need to save the current path because the page is loaded within
-// an existing "workflow".
-if (!pathInfo.currentPathIsAdmin && !/destination=/.test(window.location.search)) {
-  sessionStorage.setItem('escapeAdminPath', pathInfo.currentPath);
-}
+  // Saves the last non-administrative page in the browser to be able to link back
+  // to it when browsing administrative pages. If there is a destination parameter
+  // there is not need to save the current path because the page is loaded within
+  // an existing "workflow".
+  if (!pathInfo.currentPathIsAdmin && !/destination=/.test(window.location.search)) {
+    sessionStorage.setItem('escapeAdminPath', pathInfo.currentPath);
+  }
 
-/**
- * Replaces the "Home" link with "Back to site" link.
- *
- * Back to site link points to the last non-administrative page the user visited
- * within the same browser tab.
- */
-Drupal.behaviors.escapeAdmin = {
-  attach: function () {
-    var $toolbarEscape = $('[data-toolbar-escape-admin]').once('escapeAdmin');
-    if ($toolbarEscape.length) {
-      if (pathInfo.currentPathIsAdmin && escapeAdminPath) {
-        $toolbarEscape.attr('href', Drupal.url(escapeAdminPath));
-        $toolbarEscape.closest('.toolbar-tab').removeClass('hidden');
+  /**
+   * Replaces the "Home" link with "Back to site" link.
+   *
+   * Back to site link points to the last non-administrative page the user visited
+   * within the same browser tab.
+   */
+  Drupal.behaviors.escapeAdmin = {
+    attach: function () {
+      var $toolbarEscape = $('[data-toolbar-escape-admin]').once('escapeAdmin');
+      if ($toolbarEscape.length) {
+        if (pathInfo.currentPathIsAdmin && escapeAdminPath) {
+          $toolbarEscape.attr('href', Drupal.url(escapeAdminPath));
+          $toolbarEscape.closest('.toolbar-tab').removeClass('hidden');
+        }
       }
     }
-  }
-};
+  };
 
 })(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/toolbar/js/models/MenuModel.js b/core/modules/toolbar/js/models/MenuModel.js
index 0b05575..07bc942 100644
--- a/core/modules/toolbar/js/models/MenuModel.js
+++ b/core/modules/toolbar/js/models/MenuModel.js
@@ -5,15 +5,15 @@
 
 (function (Backbone, Drupal) {
 
-"use strict";
+  "use strict";
 
-/**
- * Backbone Model for collapsible menus.
- */
-Drupal.toolbar.MenuModel = Backbone.Model.extend({
-  defaults: {
-    subtrees: {}
-  }
-});
+  /**
+   * Backbone Model for collapsible menus.
+   */
+  Drupal.toolbar.MenuModel = Backbone.Model.extend({
+    defaults: {
+      subtrees: {}
+    }
+  });
 
 }(Backbone, Drupal));
diff --git a/core/modules/toolbar/js/models/ToolbarModel.js b/core/modules/toolbar/js/models/ToolbarModel.js
index 4384fbf..77433bf 100644
--- a/core/modules/toolbar/js/models/ToolbarModel.js
+++ b/core/modules/toolbar/js/models/ToolbarModel.js
@@ -5,65 +5,65 @@
 
 (function (Backbone, Drupal) {
 
-"use strict";
-
-/**
- * Backbone model for the toolbar.
- */
-Drupal.toolbar.ToolbarModel = Backbone.Model.extend({
-  defaults: {
-    // The active toolbar tab. All other tabs should be inactive under
-    // normal circumstances. It will remain active across page loads. The
-    // active item is stored as an ID selector e.g. '#toolbar-item--1'.
-    activeTab: null,
-    // Represents whether a tray is open or not. Stored as an ID selector e.g.
-    // '#toolbar-item--1-tray'.
-    activeTray: null,
-    // Indicates whether the toolbar is displayed in an oriented fashion,
-    // either horizontal or vertical.
-    isOriented: false,
-    // Indicates whether the toolbar is positioned absolute (false) or fixed
-    // (true).
-    isFixed: false,
-    // Menu subtrees are loaded through an AJAX request only when the Toolbar
-    // is set to a vertical orientation.
-    areSubtreesLoaded: false,
-    // If the viewport overflow becomes constrained, isFixed must be true so
-    // that elements in the trays aren't lost off-screen and impossible to
-    // get to.
-    isViewportOverflowConstrained: false,
-    // The orientation of the active tray.
-    orientation: 'vertical',
-    // A tray is locked if a user toggled it to vertical. Otherwise a tray
-    // will switch between vertical and horizontal orientation based on the
-    // configured breakpoints. The locked state will be maintained across page
-    // loads.
-    locked: false,
-    // Indicates whether the tray orientation toggle is visible.
-    isTrayToggleVisible: false,
-    // The height of the toolbar.
-    height: null,
-    // The current viewport offsets determined by Drupal.displace(). The
-    // offsets suggest how a module might position is components relative to
-    // the viewport.
-    offsets: {
-      top: 0,
-      right: 0,
-      bottom: 0,
-      left: 0
-    }
-  },
+  "use strict";
 
   /**
-   * {@inheritdoc}
+   * Backbone model for the toolbar.
    */
-  validate: function (attributes, options) {
-    // Prevent the orientation being set to horizontal if it is locked, unless
-    // override has not been passed as an option.
-    if (attributes.orientation === 'horizontal' && this.get('locked') && !options.override) {
-      return Drupal.t('The toolbar cannot be set to a horizontal orientation when it is locked.');
+  Drupal.toolbar.ToolbarModel = Backbone.Model.extend({
+    defaults: {
+      // The active toolbar tab. All other tabs should be inactive under
+      // normal circumstances. It will remain active across page loads. The
+      // active item is stored as an ID selector e.g. '#toolbar-item--1'.
+      activeTab: null,
+      // Represents whether a tray is open or not. Stored as an ID selector e.g.
+      // '#toolbar-item--1-tray'.
+      activeTray: null,
+      // Indicates whether the toolbar is displayed in an oriented fashion,
+      // either horizontal or vertical.
+      isOriented: false,
+      // Indicates whether the toolbar is positioned absolute (false) or fixed
+      // (true).
+      isFixed: false,
+      // Menu subtrees are loaded through an AJAX request only when the Toolbar
+      // is set to a vertical orientation.
+      areSubtreesLoaded: false,
+      // If the viewport overflow becomes constrained, isFixed must be true so
+      // that elements in the trays aren't lost off-screen and impossible to
+      // get to.
+      isViewportOverflowConstrained: false,
+      // The orientation of the active tray.
+      orientation: 'vertical',
+      // A tray is locked if a user toggled it to vertical. Otherwise a tray
+      // will switch between vertical and horizontal orientation based on the
+      // configured breakpoints. The locked state will be maintained across page
+      // loads.
+      locked: false,
+      // Indicates whether the tray orientation toggle is visible.
+      isTrayToggleVisible: false,
+      // The height of the toolbar.
+      height: null,
+      // The current viewport offsets determined by Drupal.displace(). The
+      // offsets suggest how a module might position is components relative to
+      // the viewport.
+      offsets: {
+        top: 0,
+        right: 0,
+        bottom: 0,
+        left: 0
+      }
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    validate: function (attributes, options) {
+      // Prevent the orientation being set to horizontal if it is locked, unless
+      // override has not been passed as an option.
+      if (attributes.orientation === 'horizontal' && this.get('locked') && !options.override) {
+        return Drupal.t('The toolbar cannot be set to a horizontal orientation when it is locked.');
+      }
     }
-  }
-});
+  });
 
 }(Backbone, Drupal));
diff --git a/core/modules/toolbar/js/toolbar.js b/core/modules/toolbar/js/toolbar.js
index 452abe7..909708d 100644
--- a/core/modules/toolbar/js/toolbar.js
+++ b/core/modules/toolbar/js/toolbar.js
@@ -5,199 +5,199 @@
  */
 (function ($, Drupal, drupalSettings) {
 
-"use strict";
-
-// Merge run-time settings with the defaults.
-var options = $.extend(
-  {
-    breakpoints: {
-      'module.toolbar.narrow': '',
-      'module.toolbar.standard': '',
-      'module.toolbar.wide': ''
-    }
-  },
-  drupalSettings.toolbar,
-  // Merge strings on top of drupalSettings so that they are not mutable.
-  {
-    strings: {
-      horizontal: Drupal.t('Horizontal orientation'),
-      vertical: Drupal.t('Vertical orientation')
-    }
-  }
-);
-
-/**
- * Registers tabs with the toolbar.
- *
- * The Drupal toolbar allows modules to register top-level tabs. These may point
- * directly to a resource or toggle the visibility of a tray.
- *
- * Modules register tabs with hook_toolbar().
- */
-Drupal.behaviors.toolbar = {
-
-  attach: function (context) {
-    // Verify that the user agent understands media queries. Complex admin
-    // toolbar layouts require media query support.
-    if (!window.matchMedia('only screen').matches) {
-      return;
+  "use strict";
+
+  // Merge run-time settings with the defaults.
+  var options = $.extend(
+    {
+      breakpoints: {
+        'module.toolbar.narrow': '',
+        'module.toolbar.standard': '',
+        'module.toolbar.wide': ''
+      }
+    },
+    drupalSettings.toolbar,
+    // Merge strings on top of drupalSettings so that they are not mutable.
+    {
+      strings: {
+        horizontal: Drupal.t('Horizontal orientation'),
+        vertical: Drupal.t('Vertical orientation')
+      }
     }
-    // Process the administrative toolbar.
-    $(context).find('#toolbar-administration').once('toolbar', function () {
-
-      // Establish the toolbar models and views.
-      var model = Drupal.toolbar.models.toolbarModel = new Drupal.toolbar.ToolbarModel({
-        locked: JSON.parse(localStorage.getItem('Drupal.toolbar.trayVerticalLocked')) || false,
-        activeTab: document.getElementById(JSON.parse(localStorage.getItem('Drupal.toolbar.activeTabID')))
-      });
-      Drupal.toolbar.views.toolbarVisualView = new Drupal.toolbar.ToolbarVisualView({
-        el: this,
-        model: model,
-        strings: options.strings
-      });
-      Drupal.toolbar.views.toolbarAuralView = new Drupal.toolbar.ToolbarAuralView({
-        el: this,
-        model: model,
-        strings: options.strings
-      });
-      Drupal.toolbar.views.bodyVisualView = new Drupal.toolbar.BodyVisualView({
-        el: this,
-        model: model
-      });
+  );
 
-      // Render collapsible menus.
-      var menuModel = Drupal.toolbar.models.menuModel = new Drupal.toolbar.MenuModel();
-      Drupal.toolbar.views.menuVisualView = new Drupal.toolbar.MenuVisualView({
-        el: $(this).find('.toolbar-menu-administration').get(0),
-        model: menuModel,
-        strings: options.strings
-      });
-
-      // Handle the resolution of Drupal.toolbar.setSubtrees.
-      // This is handled with a deferred so that the function may be invoked
-      // asynchronously.
-      Drupal.toolbar.setSubtrees.done(function (subtrees) {
-        menuModel.set('subtrees', subtrees);
-        localStorage.setItem('Drupal.toolbar.subtrees', JSON.stringify(subtrees));
-        // Indicate on the toolbarModel that subtrees are now loaded.
-        model.set('areSubtreesLoaded', true);
-      });
+  /**
+   * Registers tabs with the toolbar.
+   *
+   * The Drupal toolbar allows modules to register top-level tabs. These may point
+   * directly to a resource or toggle the visibility of a tray.
+   *
+   * Modules register tabs with hook_toolbar().
+   */
+  Drupal.behaviors.toolbar = {
 
-      // Attach a listener to the configured media query breakpoints.
-      for (var label in options.breakpoints) {
-        if (options.breakpoints.hasOwnProperty(label)) {
-          var mq = options.breakpoints[label];
-          var mql = Drupal.toolbar.mql[label] = window.matchMedia(mq);
-          // Curry the model and the label of the media query breakpoint to the
-          // mediaQueryChangeHandler function.
-          mql.addListener(Drupal.toolbar.mediaQueryChangeHandler.bind(null, model, label));
-          // Fire the mediaQueryChangeHandler for each configured breakpoint
-          // so that they process once.
-          Drupal.toolbar.mediaQueryChangeHandler.call(null, model, label, mql);
-        }
+    attach: function (context) {
+      // Verify that the user agent understands media queries. Complex admin
+      // toolbar layouts require media query support.
+      if (!window.matchMedia('only screen').matches) {
+        return;
       }
+      // Process the administrative toolbar.
+      $(context).find('#toolbar-administration').once('toolbar', function () {
 
-      // Trigger an initial attempt to load menu subitems. This first attempt
-      // is made after the media query handlers have had an opportunity to
-      // process. The toolbar starts in the vertical orientation by default,
-      // unless the viewport is wide enough to accommodate a horizontal
-      // orientation. Thus we give the Toolbar a chance to determine if it
-      // should be set to horizontal orientation before attempting to load menu
-      // subtrees.
-      Drupal.toolbar.views.toolbarVisualView.loadSubtrees();
-
-      $(document)
-        // Update the model when the viewport offset changes.
-        .on('drupalViewportOffsetChange.toolbar', function (event, offsets) {
-          model.set('offsets', offsets);
+        // Establish the toolbar models and views.
+        var model = Drupal.toolbar.models.toolbarModel = new Drupal.toolbar.ToolbarModel({
+          locked: JSON.parse(localStorage.getItem('Drupal.toolbar.trayVerticalLocked')) || false,
+          activeTab: document.getElementById(JSON.parse(localStorage.getItem('Drupal.toolbar.activeTabID')))
         });
-
-      // Broadcast model changes to other modules.
-      model
-        .on('change:orientation', function (model, orientation) {
-          $(document).trigger('drupalToolbarOrientationChange', orientation);
-        })
-        .on('change:activeTab', function (model, tab) {
-          $(document).trigger('drupalToolbarTabChange', tab);
-        })
-        .on('change:activeTray', function (model, tray) {
-          $(document).trigger('drupalToolbarTrayChange', tray);
+        Drupal.toolbar.views.toolbarVisualView = new Drupal.toolbar.ToolbarVisualView({
+          el: this,
+          model: model,
+          strings: options.strings
+        });
+        Drupal.toolbar.views.toolbarAuralView = new Drupal.toolbar.ToolbarAuralView({
+          el: this,
+          model: model,
+          strings: options.strings
+        });
+        Drupal.toolbar.views.bodyVisualView = new Drupal.toolbar.BodyVisualView({
+          el: this,
+          model: model
         });
-    });
-  }
-};
 
-/**
- * Toolbar methods of Backbone objects.
- */
-Drupal.toolbar = {
+        // Render collapsible menus.
+        var menuModel = Drupal.toolbar.models.menuModel = new Drupal.toolbar.MenuModel();
+        Drupal.toolbar.views.menuVisualView = new Drupal.toolbar.MenuVisualView({
+          el: $(this).find('.toolbar-menu-administration').get(0),
+          model: menuModel,
+          strings: options.strings
+        });
 
-  // A hash of View instances.
-  views: {},
+        // Handle the resolution of Drupal.toolbar.setSubtrees.
+        // This is handled with a deferred so that the function may be invoked
+        // asynchronously.
+        Drupal.toolbar.setSubtrees.done(function (subtrees) {
+          menuModel.set('subtrees', subtrees);
+          localStorage.setItem('Drupal.toolbar.subtrees', JSON.stringify(subtrees));
+          // Indicate on the toolbarModel that subtrees are now loaded.
+          model.set('areSubtreesLoaded', true);
+        });
 
-  // A hash of Model instances.
-  models: {},
+        // Attach a listener to the configured media query breakpoints.
+        for (var label in options.breakpoints) {
+          if (options.breakpoints.hasOwnProperty(label)) {
+            var mq = options.breakpoints[label];
+            var mql = Drupal.toolbar.mql[label] = window.matchMedia(mq);
+            // Curry the model and the label of the media query breakpoint to the
+            // mediaQueryChangeHandler function.
+            mql.addListener(Drupal.toolbar.mediaQueryChangeHandler.bind(null, model, label));
+            // Fire the mediaQueryChangeHandler for each configured breakpoint
+            // so that they process once.
+            Drupal.toolbar.mediaQueryChangeHandler.call(null, model, label, mql);
+          }
+        }
 
-  // A hash of MediaQueryList objects tracked by the toolbar.
-  mql: {},
+        // Trigger an initial attempt to load menu subitems. This first attempt
+        // is made after the media query handlers have had an opportunity to
+        // process. The toolbar starts in the vertical orientation by default,
+        // unless the viewport is wide enough to accommodate a horizontal
+        // orientation. Thus we give the Toolbar a chance to determine if it
+        // should be set to horizontal orientation before attempting to load menu
+        // subtrees.
+        Drupal.toolbar.views.toolbarVisualView.loadSubtrees();
+
+        $(document)
+          // Update the model when the viewport offset changes.
+          .on('drupalViewportOffsetChange.toolbar', function (event, offsets) {
+            model.set('offsets', offsets);
+          });
+
+        // Broadcast model changes to other modules.
+        model
+          .on('change:orientation', function (model, orientation) {
+            $(document).trigger('drupalToolbarOrientationChange', orientation);
+          })
+          .on('change:activeTab', function (model, tab) {
+            $(document).trigger('drupalToolbarTabChange', tab);
+          })
+          .on('change:activeTray', function (model, tray) {
+            $(document).trigger('drupalToolbarTrayChange', tray);
+          });
+      });
+    }
+  };
 
   /**
-   * Accepts a list of subtree menu elements.
-   *
-   * A deferred object that is resolved by an inlined JavaScript callback.
-   *
-   * JSONP callback.
-   * @see toolbar_subtrees_jsonp().
+   * Toolbar methods of Backbone objects.
    */
-  setSubtrees: new $.Deferred(),
+  Drupal.toolbar = {
+
+    // A hash of View instances.
+    views: {},
+
+    // A hash of Model instances.
+    models: {},
+
+    // A hash of MediaQueryList objects tracked by the toolbar.
+    mql: {},
+
+    /**
+     * Accepts a list of subtree menu elements.
+     *
+     * A deferred object that is resolved by an inlined JavaScript callback.
+     *
+     * JSONP callback.
+     * @see toolbar_subtrees_jsonp().
+     */
+    setSubtrees: new $.Deferred(),
+
+    /**
+     * Respond to configured narrow media query changes.
+     */
+    mediaQueryChangeHandler: function (model, label, mql) {
+      switch (label) {
+        case 'module.toolbar.narrow':
+          model.set({
+            'isOriented': mql.matches,
+            'isTrayToggleVisible': false
+          });
+          // If the toolbar doesn't have an explicit orientation yet, or if the
+          // narrow media query doesn't match then set the orientation to
+          // vertical.
+          if (!mql.matches || !model.get('orientation')) {
+            model.set({'orientation': 'vertical'}, {validate: true});
+          }
+          break;
+        case 'module.toolbar.standard':
+          model.set({
+            'isFixed': mql.matches
+          });
+          break;
+        case 'module.toolbar.wide':
+          model.set({
+            'orientation': ((mql.matches) ? 'horizontal' : 'vertical')
+          }, {validate: true});
+          // The tray orientation toggle visibility does not need to be validated.
+          model.set({
+            'isTrayToggleVisible': mql.matches
+          });
+          break;
+        default:
+          break;
+      }
+    }
+  };
 
   /**
-   * Respond to configured narrow media query changes.
+   * A toggle is an interactive element often bound to a click handler.
+   *
+   * @return {String}
+   *   A string representing a DOM fragment.
    */
-  mediaQueryChangeHandler: function (model, label, mql) {
-    switch (label) {
-      case 'module.toolbar.narrow':
-        model.set({
-          'isOriented': mql.matches,
-          'isTrayToggleVisible': false
-        });
-        // If the toolbar doesn't have an explicit orientation yet, or if the
-        // narrow media query doesn't match then set the orientation to
-        // vertical.
-        if (!mql.matches || !model.get('orientation')) {
-          model.set({'orientation': 'vertical'}, {validate: true});
-        }
-        break;
-      case 'module.toolbar.standard':
-        model.set({
-          'isFixed': mql.matches
-        });
-        break;
-      case 'module.toolbar.wide':
-        model.set({
-          'orientation': ((mql.matches) ? 'horizontal' : 'vertical')
-        }, {validate: true});
-        // The tray orientation toggle visibility does not need to be validated.
-        model.set({
-          'isTrayToggleVisible': mql.matches
-        });
-        break;
-      default:
-        break;
-    }
-  }
-};
-
-/**
- * A toggle is an interactive element often bound to a click handler.
- *
- * @return {String}
- *   A string representing a DOM fragment.
- */
-Drupal.theme.toolbarOrientationToggle = function () {
-  return '<div class="toolbar-toggle-orientation"><div class="toolbar-lining">' +
-    '<button class="toolbar-icon" type="button"></button>' +
-    '</div></div>';
-};
+  Drupal.theme.toolbarOrientationToggle = function () {
+    return '<div class="toolbar-toggle-orientation"><div class="toolbar-lining">' +
+      '<button class="toolbar-icon" type="button"></button>' +
+      '</div></div>';
+  };
 
 }(jQuery, Drupal, drupalSettings));
diff --git a/core/modules/toolbar/js/toolbar.menu.js b/core/modules/toolbar/js/toolbar.menu.js
index 7e5c38a..1100278 100644
--- a/core/modules/toolbar/js/toolbar.menu.js
+++ b/core/modules/toolbar/js/toolbar.menu.js
@@ -7,12 +7,12 @@
 
 (function ($, Drupal, drupalSettings) {
 
-"use strict";
+  "use strict";
 
-/**
- * Store the open menu tray.
- */
-var activeItem = Drupal.url(drupalSettings.path.currentPath);
+  /**
+   * Store the open menu tray.
+   */
+  var activeItem = Drupal.url(drupalSettings.path.currentPath);
 
   $.fn.drupalToolbarMenu = function () {
 
@@ -26,7 +26,7 @@ var activeItem = Drupal.url(drupalSettings.path.currentPath);
      * @param {Object} event
      *   A jQuery Event object.
      */
-    function toggleClickHandler (event) {
+    function toggleClickHandler(event) {
       var $toggle = $(event.target);
       var $item = $toggle.closest('li');
       // Toggle the list item.
@@ -45,7 +45,7 @@ var activeItem = Drupal.url(drupalSettings.path.currentPath);
      *   A flag that forces toggleClass to add or a remove a class, rather than
      *   simply toggling its presence.
      */
-    function toggleList ($item, switcher) {
+    function toggleList($item, switcher) {
       var $toggle = $item.children('.toolbar-box').children('.toolbar-handle');
       switcher = (typeof switcher !== 'undefined') ? switcher : !$item.hasClass('open');
       // Toggle the item open state.
@@ -56,7 +56,7 @@ var activeItem = Drupal.url(drupalSettings.path.currentPath);
       $toggle
         .find('.action')
         // Expand Structure, Collapse Structure
-        .text((switcher) ?  ui.handleClose : ui.handleOpen);
+        .text((switcher) ? ui.handleClose : ui.handleOpen);
     }
     /**
      * Add markup to the menu elements.
@@ -69,7 +69,7 @@ var activeItem = Drupal.url(drupalSettings.path.currentPath);
      * @param {jQuery} $menu
      *   The root of the menu to be initialized.
      */
-    function initItems ($menu) {
+    function initItems($menu) {
       var options = {
         'class': 'toolbar-icon toolbar-handle',
         'action': ui.handleOpen,
@@ -77,16 +77,16 @@ var activeItem = Drupal.url(drupalSettings.path.currentPath);
       };
       // Initialize items and their links.
       $menu.find('li > a').wrap('<div class="toolbar-box">');
-        // Add a handle to each list item if it has a menu.
+      // Add a handle to each list item if it has a menu.
       $menu.find('li').each(function (index, element) {
-          var $item = $(element);
-          if ($item.children('ul.menu').length) {
-            var $box = $item.children('.toolbar-box');
-            options.text = Drupal.t('@label', {'@label': $box.find('a').text()});
-            $item.children('.toolbar-box')
-              .append(Drupal.theme('toolbarMenuItemToggle', options));
-          }
-        });
+        var $item = $(element);
+        if ($item.children('ul.menu').length) {
+          var $box = $item.children('.toolbar-box');
+          options.text = Drupal.t('@label', {'@label': $box.find('a').text()});
+          $item.children('.toolbar-box')
+            .append(Drupal.theme('toolbarMenuItemToggle', options));
+        }
+      });
     }
     /**
      * Adds a level class to each list based on its depth in the menu.
@@ -100,7 +100,7 @@ var activeItem = Drupal.url(drupalSettings.path.currentPath);
      * @param {Integer} level
      *   The current level number to be assigned to the list elements.
      */
-    function markListLevels ($lists, level) {
+    function markListLevels($lists, level) {
       level = (!level) ? 1 : level;
       var $lis = $lists.children('li').addClass('level-' + level);
       $lists = $lis.children('ul');
@@ -117,7 +117,7 @@ var activeItem = Drupal.url(drupalSettings.path.currentPath);
      * @param {jQuery} $menu
      *   The root of the menu.
      */
-    function openActiveItem ($menu) {
+    function openActiveItem($menu) {
       var pathItem = $menu.find('a[href="' + location.pathname + '"]');
       if (pathItem.length && !activeItem) {
         activeItem = location.pathname;
diff --git a/core/modules/toolbar/js/views/BodyVisualView.js b/core/modules/toolbar/js/views/BodyVisualView.js
index 515b363..00abcf2 100644
--- a/core/modules/toolbar/js/views/BodyVisualView.js
+++ b/core/modules/toolbar/js/views/BodyVisualView.js
@@ -5,48 +5,48 @@
 
 (function ($, Drupal, Backbone) {
 
-"use strict";
-
-/**
- * Adjusts the body element with the toolbar position and dimension changes.
- */
-Drupal.toolbar.BodyVisualView = Backbone.View.extend({
+  "use strict";
 
   /**
-   * {@inheritdoc}
+   * Adjusts the body element with the toolbar position and dimension changes.
    */
-  initialize: function () {
-    this.listenTo(this.model, 'change:orientation change:offsets change:activeTray change:isOriented change:isFixed change:isViewportOverflowConstrained', this.render);
-  },
+  Drupal.toolbar.BodyVisualView = Backbone.View.extend({
 
-  /**
-   * {@inheritdoc}
-   */
-  render: function () {
-    var $body = $('body');
-    var orientation = this.model.get('orientation');
-    var isOriented = this.model.get('isOriented');
-    var isViewportOverflowConstrained = this.model.get('isViewportOverflowConstrained');
+    /**
+     * {@inheritdoc}
+     */
+    initialize: function () {
+      this.listenTo(this.model, 'change:orientation change:offsets change:activeTray change:isOriented change:isFixed change:isViewportOverflowConstrained', this.render);
+    },
+
+    /**
+     * {@inheritdoc}
+     */
+    render: function () {
+      var $body = $('body');
+      var orientation = this.model.get('orientation');
+      var isOriented = this.model.get('isOriented');
+      var isViewportOverflowConstrained = this.model.get('isViewportOverflowConstrained');
 
-    $body
-      // We are using JavaScript to control media-query handling for two
-      // reasons: (1) Using JavaScript let's us leverage the breakpoint
-      // configurations and (2) the CSS is really complex if we try to hide
-      // some styling from browsers that don't understand CSS media queries.
-      // If we drive the CSS from classes added through JavaScript,
-      // then the CSS becomes simpler and more robust.
-      .toggleClass('toolbar-vertical', (orientation === 'vertical'))
-      .toggleClass('toolbar-horizontal', (isOriented && orientation === 'horizontal'))
-      // When the toolbar is fixed, it will not scroll with page scrolling.
-      .toggleClass('toolbar-fixed', (isViewportOverflowConstrained || this.model.get('isFixed')))
-      // Toggle the toolbar-tray-open class on the body element. The class is
-      // applied when a toolbar tray is active. Padding might be applied to
-      // the body element to prevent the tray from overlapping content.
-      .toggleClass('toolbar-tray-open', !!this.model.get('activeTray'))
-      // Apply padding to the top of the body to offset the placement of the
-      // toolbar bar element.
-      .css('padding-top', this.model.get('offsets').top);
-  }
-});
+      $body
+        // We are using JavaScript to control media-query handling for two
+        // reasons: (1) Using JavaScript let's us leverage the breakpoint
+        // configurations and (2) the CSS is really complex if we try to hide
+        // some styling from browsers that don't understand CSS media queries.
+        // If we drive the CSS from classes added through JavaScript,
+        // then the CSS becomes simpler and more robust.
+        .toggleClass('toolbar-vertical', (orientation === 'vertical'))
+        .toggleClass('toolbar-horizontal', (isOriented && orientation === 'horizontal'))
+        // When the toolbar is fixed, it will not scroll with page scrolling.
+        .toggleClass('toolbar-fixed', (isViewportOverflowConstrained || this.model.get('isFixed')))
+        // Toggle the toolbar-tray-open class on the body element. The class is
+        // applied when a toolbar tray is active. Padding might be applied to
+        // the body element to prevent the tray from overlapping content.
+        .toggleClass('toolbar-tray-open', !!this.model.get('activeTray'))
+        // Apply padding to the top of the body to offset the placement of the
+        // toolbar bar element.
+        .css('padding-top', this.model.get('offsets').top);
+    }
+  });
 
 }(jQuery, Drupal, Backbone));
diff --git a/core/modules/toolbar/js/views/MenuVisualView.js b/core/modules/toolbar/js/views/MenuVisualView.js
index 42ec983..d90b6d0 100644
--- a/core/modules/toolbar/js/views/MenuVisualView.js
+++ b/core/modules/toolbar/js/views/MenuVisualView.js
@@ -3,42 +3,42 @@
  * A Backbone view for the collapsible menus.
  */
 
-(function ($,Backbone, Drupal) {
+(function ($, Backbone, Drupal) {
 
-"use strict";
+  "use strict";
 
-/**
- * Backbone View for collapsible menus.
- */
-Drupal.toolbar.MenuVisualView = Backbone.View.extend({
   /**
-   * {@inheritdoc}
+   * Backbone View for collapsible menus.
    */
-  initialize: function () {
-    this.listenTo(this.model, 'change:subtrees', this.render);
-  },
+  Drupal.toolbar.MenuVisualView = Backbone.View.extend({
+    /**
+     * {@inheritdoc}
+     */
+    initialize: function () {
+      this.listenTo(this.model, 'change:subtrees', this.render);
+    },
 
-  /**
-   * {@inheritdoc}
-   */
-  render: function () {
-    var subtrees = this.model.get('subtrees');
-    // Add subtrees.
-    for (var id in subtrees) {
-      if (subtrees.hasOwnProperty(id)) {
+    /**
+     * {@inheritdoc}
+     */
+    render: function () {
+      var subtrees = this.model.get('subtrees');
+      // Add subtrees.
+      for (var id in subtrees) {
+        if (subtrees.hasOwnProperty(id)) {
+          this.$el
+            .find('#toolbar-link-' + id)
+            .once('toolbar-subtrees')
+            .after(subtrees[id]);
+        }
+      }
+      // Render the main menu as a nested, collapsible accordion.
+      if ('drupalToolbarMenu' in $.fn) {
         this.$el
-          .find('#toolbar-link-' + id)
-          .once('toolbar-subtrees')
-          .after(subtrees[id]);
+          .children('.menu')
+          .drupalToolbarMenu();
       }
     }
-    // Render the main menu as a nested, collapsible accordion.
-    if ('drupalToolbarMenu' in $.fn) {
-      this.$el
-        .children('.menu')
-        .drupalToolbarMenu();
-    }
-  }
-});
+  });
 
 }(jQuery, Backbone, Drupal));
diff --git a/core/modules/toolbar/js/views/ToolbarAuralView.js b/core/modules/toolbar/js/views/ToolbarAuralView.js
index 2f312e2..f46bd86 100644
--- a/core/modules/toolbar/js/views/ToolbarAuralView.js
+++ b/core/modules/toolbar/js/views/ToolbarAuralView.js
@@ -5,55 +5,55 @@
 
 (function (Backbone, Drupal) {
 
-"use strict";
-
-/**
- * Backbone view for the aural feedback of the toolbar.
- */
-Drupal.toolbar.ToolbarAuralView = Backbone.View.extend({
+  "use strict";
 
   /**
-   * {@inheritdoc}
+   * Backbone view for the aural feedback of the toolbar.
    */
-  initialize: function (options) {
-    this.strings = options.strings;
-
-    this.listenTo(this.model, 'change:orientation', this.onOrientationChange);
-    this.listenTo(this.model, 'change:activeTray', this.onActiveTrayChange);
-  },
-
-  /**
-   * Announces an orientation change.
-   *
-   * @param Drupal.Toolbar.ToolbarModel model
-   * @param String orientation
-   *   The new value of the orientation attribute in the model.
-   */
-  onOrientationChange: function (model, orientation) {
-    Drupal.announce(Drupal.t('Tray orientation changed to @orientation.', {
-      '@orientation': orientation
-    }));
-  },
-
-  /**
-   * Announces a changed active tray.
-   *
-   * @param Drupal.Toolbar.ToolbarModel model
-   * @param Element orientation
-   *   The new value of the tray attribute in the model.
-   */
-  onActiveTrayChange: function (model, tray) {
-    var relevantTray = (tray === null) ? model.previous('activeTray') : tray;
-    var trayName = relevantTray.querySelector('.toolbar-tray-name').textContent;
-    var text;
-    if (tray === null) {
-      text = Drupal.t('Tray "@tray" closed.', { '@tray': trayName });
-    }
-    else {
-      text = Drupal.t('Tray "@tray" opened.', { '@tray': trayName });
+  Drupal.toolbar.ToolbarAuralView = Backbone.View.extend({
+
+    /**
+     * {@inheritdoc}
+     */
+    initialize: function (options) {
+      this.strings = options.strings;
+
+      this.listenTo(this.model, 'change:orientation', this.onOrientationChange);
+      this.listenTo(this.model, 'change:activeTray', this.onActiveTrayChange);
+    },
+
+    /**
+     * Announces an orientation change.
+     *
+     * @param Drupal.Toolbar.ToolbarModel model
+     * @param String orientation
+     *   The new value of the orientation attribute in the model.
+     */
+    onOrientationChange: function (model, orientation) {
+      Drupal.announce(Drupal.t('Tray orientation changed to @orientation.', {
+        '@orientation': orientation
+      }));
+    },
+
+    /**
+     * Announces a changed active tray.
+     *
+     * @param Drupal.Toolbar.ToolbarModel model
+     * @param Element orientation
+     *   The new value of the tray attribute in the model.
+     */
+    onActiveTrayChange: function (model, tray) {
+      var relevantTray = (tray === null) ? model.previous('activeTray') : tray;
+      var trayName = relevantTray.querySelector('.toolbar-tray-name').textContent;
+      var text;
+      if (tray === null) {
+        text = Drupal.t('Tray "@tray" closed.', { '@tray': trayName });
+      }
+      else {
+        text = Drupal.t('Tray "@tray" opened.', { '@tray': trayName });
+      }
+      Drupal.announce(text);
     }
-    Drupal.announce(text);
-  }
-});
+  });
 
 }(Backbone, Drupal));
diff --git a/core/modules/toolbar/js/views/ToolbarVisualView.js b/core/modules/toolbar/js/views/ToolbarVisualView.js
index be2264b..10c762d 100644
--- a/core/modules/toolbar/js/views/ToolbarVisualView.js
+++ b/core/modules/toolbar/js/views/ToolbarVisualView.js
@@ -5,270 +5,270 @@
 
 (function ($, Drupal, drupalSettings, Backbone) {
 
-"use strict";
-
-/**
- * Backbone view for the toolbar element.
- */
-Drupal.toolbar.ToolbarVisualView = Backbone.View.extend({
-
-  events: {
-    'click .toolbar-bar .toolbar-tab': 'onTabClick',
-    'click .toolbar-toggle-orientation button': 'onOrientationToggleClick'
-  },
+  "use strict";
 
   /**
-   * {@inheritdoc}
+   * Backbone view for the toolbar element.
    */
-  initialize: function (options) {
-    this.strings = options.strings;
+  Drupal.toolbar.ToolbarVisualView = Backbone.View.extend({
 
-    this.listenTo(this.model, 'change:activeTab change:orientation change:isOriented change:isTrayToggleVisible', this.render);
-    this.listenTo(this.model, 'change:mqMatches', this.onMediaQueryChange);
-    this.listenTo(this.model, 'change:offsets', this.adjustPlacement);
+    events: {
+      'click .toolbar-bar .toolbar-tab': 'onTabClick',
+      'click .toolbar-toggle-orientation button': 'onOrientationToggleClick'
+    },
 
-    // Add the tray orientation toggles.
-    this.$el
-      .find('.toolbar-tray .toolbar-lining')
-      .append(Drupal.theme('toolbarOrientationToggle'));
+    /**
+     * {@inheritdoc}
+     */
+    initialize: function (options) {
+      this.strings = options.strings;
 
-    // Trigger an activeTab change so that listening scripts can respond on
-    // page load. This will call render.
-    this.model.trigger('change:activeTab');
-  },
+      this.listenTo(this.model, 'change:activeTab change:orientation change:isOriented change:isTrayToggleVisible', this.render);
+      this.listenTo(this.model, 'change:mqMatches', this.onMediaQueryChange);
+      this.listenTo(this.model, 'change:offsets', this.adjustPlacement);
 
-  /**
-   * {@inheritdoc}
-   */
-  render: function () {
-    this.updateTabs();
-    this.updateTrayOrientation();
-    this.updateBarAttributes();
-    // Load the subtrees if the orientation of the toolbar is changed to
-    // vertical. This condition responds to the case that the toolbar switches
-    // from horizontal to vertical orientation. The toolbar starts in a
-    // vertical orientation by default and then switches to horizontal during
-    // initialization if the media query conditions are met. Simply checking
-    // that the orientation is vertical here would result in the subtrees
-    // always being loaded, even when the toolbar initialization ultimately
-    // results in a horizontal orientation.
-    //
-    // @see Drupal.behaviors.toolbar.attach() where admin menu subtrees
-    // loading is invoked during initialization after media query conditions
-    // have been processed.
-    if (this.model.changed.orientation === 'vertical' || this.model.changed.activeTab) {
-      this.loadSubtrees();
-    }
-    // Trigger a recalculation of viewport displacing elements. Use setTimeout
-    // to ensure this recalculation happens after changes to visual elements
-    // have processed.
-    window.setTimeout(function () {
-      Drupal.displace(true);
-    }, 0);
-    return this;
-  },
+      // Add the tray orientation toggles.
+      this.$el
+        .find('.toolbar-tray .toolbar-lining')
+        .append(Drupal.theme('toolbarOrientationToggle'));
 
-  /**
-   * Responds to a toolbar tab click.
-   *
-   * @param jQuery.Event event
-   */
-  onTabClick: function (event) {
-    // If this tab has a tray associated with it, it is considered an
-    // activatable tab.
-    if (event.target.hasAttribute('data-toolbar-tray')) {
-      var tab = this.model.get('activeTab');
-      var id = '#' + event.target.id;
-      // Set the event target as the active item if it is not already.
-      this.model.set('activeTab', (!tab || id !== tab) ? id : null);
-
-      event.preventDefault();
-      event.stopPropagation();
-    }
-  },
+      // Trigger an activeTab change so that listening scripts can respond on
+      // page load. This will call render.
+      this.model.trigger('change:activeTab');
+    },
 
-  /**
-   * Toggles the orientation of a toolbar tray.
-   *
-   * @param jQuery.Event event
-   */
-  onOrientationToggleClick: function (event) {
-    var orientation = this.model.get('orientation');
-    // Determine the toggle-to orientation.
-    var antiOrientation = (orientation === 'vertical') ? 'horizontal' : 'vertical';
-    var locked = (antiOrientation === 'vertical') ? true : false;
-    // Remember the locked state.
-    if (locked) {
-      localStorage.setItem('Drupal.toolbar.trayVerticalLocked', 'true');
-    }
-    else {
-      localStorage.removeItem('Drupal.toolbar.trayVerticalLocked');
-    }
-    // Update the model.
-    this.model.set({
-      locked: locked,
-      orientation: antiOrientation
-    }, {
-      validate: true,
-      override: true
-    });
+    /**
+     * {@inheritdoc}
+     */
+    render: function () {
+      this.updateTabs();
+      this.updateTrayOrientation();
+      this.updateBarAttributes();
+      // Load the subtrees if the orientation of the toolbar is changed to
+      // vertical. This condition responds to the case that the toolbar switches
+      // from horizontal to vertical orientation. The toolbar starts in a
+      // vertical orientation by default and then switches to horizontal during
+      // initialization if the media query conditions are met. Simply checking
+      // that the orientation is vertical here would result in the subtrees
+      // always being loaded, even when the toolbar initialization ultimately
+      // results in a horizontal orientation.
+      //
+      // @see Drupal.behaviors.toolbar.attach() where admin menu subtrees
+      // loading is invoked during initialization after media query conditions
+      // have been processed.
+      if (this.model.changed.orientation === 'vertical' || this.model.changed.activeTab) {
+        this.loadSubtrees();
+      }
+      // Trigger a recalculation of viewport displacing elements. Use setTimeout
+      // to ensure this recalculation happens after changes to visual elements
+      // have processed.
+      window.setTimeout(function () {
+        Drupal.displace(true);
+      }, 0);
+      return this;
+    },
 
-    event.preventDefault();
-    event.stopPropagation();
-  },
+    /**
+     * Responds to a toolbar tab click.
+     *
+     * @param jQuery.Event event
+     */
+    onTabClick: function (event) {
+      // If this tab has a tray associated with it, it is considered an
+      // activatable tab.
+      if (event.target.hasAttribute('data-toolbar-tray')) {
+        var tab = this.model.get('activeTab');
+        var id = '#' + event.target.id;
+        // Set the event target as the active item if it is not already.
+        this.model.set('activeTab', (!tab || id !== tab) ? id : null);
 
-  /**
-   * Updates the display of the tabs: toggles a tab and the associated tray.
-   */
-  updateTabs: function () {
-    var $tab = $(this.model.get('activeTab'));
-    // Deactivate the previous tab.
-    $(this.model.previous('activeTab'))
-      .removeClass('active')
-      .prop('aria-pressed', false);
-    // Deactivate the previous tray.
-    $(this.model.previous('activeTray'))
-      .removeClass('active');
+        event.preventDefault();
+        event.stopPropagation();
+      }
+    },
 
-    // Activate the selected tab.
-    if ($tab.length > 0) {
-      $tab
-        .addClass('active')
-        // Mark the tab as pressed.
-        .prop('aria-pressed', true);
-      var name = $tab.attr('data-toolbar-tray');
-      // Store the active tab name or remove the setting.
-      var id = $tab.get(0).id;
-      if (id) {
-        localStorage.setItem('Drupal.toolbar.activeTabID', JSON.stringify(id));
+    /**
+     * Toggles the orientation of a toolbar tray.
+     *
+     * @param jQuery.Event event
+     */
+    onOrientationToggleClick: function (event) {
+      var orientation = this.model.get('orientation');
+      // Determine the toggle-to orientation.
+      var antiOrientation = (orientation === 'vertical') ? 'horizontal' : 'vertical';
+      var locked = (antiOrientation === 'vertical') ? true : false;
+      // Remember the locked state.
+      if (locked) {
+        localStorage.setItem('Drupal.toolbar.trayVerticalLocked', 'true');
       }
-      // Activate the associated tray.
-      var $tray = this.$el.find('[data-toolbar-tray="' + name + '"].toolbar-tray');
-      if ($tray.length) {
-        $tray.addClass('active');
-        this.model.set('activeTray', $tray.get(0));
+      else {
+        localStorage.removeItem('Drupal.toolbar.trayVerticalLocked');
+      }
+      // Update the model.
+      this.model.set({
+        locked: locked,
+        orientation: antiOrientation
+      }, {
+        validate: true,
+        override: true
+      });
+
+      event.preventDefault();
+      event.stopPropagation();
+    },
+
+    /**
+     * Updates the display of the tabs: toggles a tab and the associated tray.
+     */
+    updateTabs: function () {
+      var $tab = $(this.model.get('activeTab'));
+      // Deactivate the previous tab.
+      $(this.model.previous('activeTab'))
+        .removeClass('active')
+        .prop('aria-pressed', false);
+      // Deactivate the previous tray.
+      $(this.model.previous('activeTray'))
+        .removeClass('active');
+
+      // Activate the selected tab.
+      if ($tab.length > 0) {
+        $tab
+          .addClass('active')
+          // Mark the tab as pressed.
+          .prop('aria-pressed', true);
+        var name = $tab.attr('data-toolbar-tray');
+        // Store the active tab name or remove the setting.
+        var id = $tab.get(0).id;
+        if (id) {
+          localStorage.setItem('Drupal.toolbar.activeTabID', JSON.stringify(id));
+        }
+        // Activate the associated tray.
+        var $tray = this.$el.find('[data-toolbar-tray="' + name + '"].toolbar-tray');
+        if ($tray.length) {
+          $tray.addClass('active');
+          this.model.set('activeTray', $tray.get(0));
+        }
+        else {
+          // There is no active tray.
+          this.model.set('activeTray', null);
+        }
       }
       else {
         // There is no active tray.
         this.model.set('activeTray', null);
+        localStorage.removeItem('Drupal.toolbar.activeTabID');
       }
-    }
-    else {
-      // There is no active tray.
-      this.model.set('activeTray', null);
-      localStorage.removeItem('Drupal.toolbar.activeTabID');
-    }
-  },
-
-  /**
-   * Update the attributes of the toolbar bar element.
-   */
-  updateBarAttributes: function () {
-    var isOriented = this.model.get('isOriented');
-    if (isOriented) {
-      this.$el.find('.toolbar-bar').attr('data-offset-top', '');
-    }
-    else {
-      this.$el.find('.toolbar-bar').removeAttr('data-offset-top');
-    }
-    // Toggle between a basic vertical view and a more sophisticated
-    // horizontal and vertical display of the toolbar bar and trays.
-    this.$el.toggleClass('toolbar-oriented', isOriented);
-  },
+    },
 
-  /**
-   * Updates the orientation of the active tray if necessary.
-   */
-  updateTrayOrientation: function () {
-    var orientation = this.model.get('orientation');
-    // The antiOrientation is used to render the view of action buttons like
-    // the tray orientation toggle.
-    var antiOrientation = (orientation === 'vertical') ? 'horizontal' : 'vertical';
-    // Update the orientation of the trays.
-    var $trays = this.$el.find('.toolbar-tray')
-      .removeClass('toolbar-tray-horizontal toolbar-tray-vertical')
-      .addClass('toolbar-tray-' + orientation);
+    /**
+     * Update the attributes of the toolbar bar element.
+     */
+    updateBarAttributes: function () {
+      var isOriented = this.model.get('isOriented');
+      if (isOriented) {
+        this.$el.find('.toolbar-bar').attr('data-offset-top', '');
+      }
+      else {
+        this.$el.find('.toolbar-bar').removeAttr('data-offset-top');
+      }
+      // Toggle between a basic vertical view and a more sophisticated
+      // horizontal and vertical display of the toolbar bar and trays.
+      this.$el.toggleClass('toolbar-oriented', isOriented);
+    },
 
-    // Update the tray orientation toggle button.
-    var iconClass = 'toolbar-icon-toggle-' + orientation;
-    var iconAntiClass = 'toolbar-icon-toggle-' + antiOrientation;
-    var $orientationToggle = this.$el.find('.toolbar-toggle-orientation')
-      .toggle(this.model.get('isTrayToggleVisible'));
-    $orientationToggle.find('button')
-      .val(antiOrientation)
-      .text(this.strings[antiOrientation])
-      .removeClass(iconClass)
-      .addClass(iconAntiClass);
+    /**
+     * Updates the orientation of the active tray if necessary.
+     */
+    updateTrayOrientation: function () {
+      var orientation = this.model.get('orientation');
+      // The antiOrientation is used to render the view of action buttons like
+      // the tray orientation toggle.
+      var antiOrientation = (orientation === 'vertical') ? 'horizontal' : 'vertical';
+      // Update the orientation of the trays.
+      var $trays = this.$el.find('.toolbar-tray')
+        .removeClass('toolbar-tray-horizontal toolbar-tray-vertical')
+        .addClass('toolbar-tray-' + orientation);
 
-    // Update data offset attributes for the trays.
-    var dir = document.documentElement.dir;
-    var edge = (dir === 'rtl') ? 'right' : 'left';
-    // Remove data-offset attributes from the trays so they can be refreshed.
-    $trays.removeAttr('data-offset-left data-offset-right data-offset-top');
-    // If an active vertical tray exists, mark it as an offset element.
-    $trays.filter('.toolbar-tray-vertical.active').attr('data-offset-' + edge, '');
-    // If an active horizontal tray exists, mark it as an offset element.
-    $trays.filter('.toolbar-tray-horizontal.active').attr('data-offset-top', '');
-  },
+      // Update the tray orientation toggle button.
+      var iconClass = 'toolbar-icon-toggle-' + orientation;
+      var iconAntiClass = 'toolbar-icon-toggle-' + antiOrientation;
+      var $orientationToggle = this.$el.find('.toolbar-toggle-orientation')
+        .toggle(this.model.get('isTrayToggleVisible'));
+      $orientationToggle.find('button')
+        .val(antiOrientation)
+        .text(this.strings[antiOrientation])
+        .removeClass(iconClass)
+        .addClass(iconAntiClass);
 
-  /**
-   * Sets the tops of the trays so that they align with the bottom of the bar.
-   */
-  adjustPlacement: function () {
-    var $trays = this.$el.find('.toolbar-tray');
-    if (!this.model.get('isOriented')) {
-      $trays.css('padding-top', 0);
-      $trays.removeClass('toolbar-tray-horizontal').addClass('toolbar-tray-vertical');
-    }
-    else {
-      // The toolbar container is invisible. Its placement is used to
-      // determine the container for the trays.
-      $trays.css('padding-top', this.$el.find('.toolbar-bar').outerHeight());
-    }
-  },
+      // Update data offset attributes for the trays.
+      var dir = document.documentElement.dir;
+      var edge = (dir === 'rtl') ? 'right' : 'left';
+      // Remove data-offset attributes from the trays so they can be refreshed.
+      $trays.removeAttr('data-offset-left data-offset-right data-offset-top');
+      // If an active vertical tray exists, mark it as an offset element.
+      $trays.filter('.toolbar-tray-vertical.active').attr('data-offset-' + edge, '');
+      // If an active horizontal tray exists, mark it as an offset element.
+      $trays.filter('.toolbar-tray-horizontal.active').attr('data-offset-top', '');
+    },
 
-  /**
-   * Calls the endpoint URI that will return rendered subtrees with JSONP.
-   *
-   * The rendered admin menu subtrees HTML is cached on the client in
-   * localStorage until the cache of the admin menu subtrees on the server-
-   * side is invalidated. The subtreesHash is stored in localStorage as well
-   * and compared to the subtreesHash in drupalSettings to determine when the
-   * admin menu subtrees cache has been invalidated.
-   */
-  loadSubtrees: function () {
-    var $activeTab = $(this.model.get('activeTab'));
-    var orientation = this.model.get('orientation');
-    // Only load and render the admin menu subtrees if:
-    //   (1) They have not been loaded yet.
-    //   (2) The active tab is the administration menu tab, indicated by the
-    //       presence of the data-drupal-subtrees attribute.
-    //   (3) The orientation of the tray is vertical.
-    if (!this.model.get('areSubtreesLoaded') && $activeTab.data('drupal-subtrees') !== undefined && orientation === 'vertical') {
-      var subtreesHash = drupalSettings.toolbar.subtreesHash;
-      var endpoint = Drupal.url('toolbar/subtrees/' + subtreesHash);
-      var cachedSubtreesHash = localStorage.getItem('Drupal.toolbar.subtreesHash');
-      var cachedSubtrees = JSON.parse(localStorage.getItem('Drupal.toolbar.subtrees'));
-      var isVertical = this.model.get('orientation') === 'vertical';
-      // If we have the subtrees in localStorage and the subtree hash has not
-      // changed, then use the cached data.
-      if (isVertical && subtreesHash === cachedSubtreesHash && cachedSubtrees) {
-        Drupal.toolbar.setSubtrees.resolve(cachedSubtrees);
+    /**
+     * Sets the tops of the trays so that they align with the bottom of the bar.
+     */
+    adjustPlacement: function () {
+      var $trays = this.$el.find('.toolbar-tray');
+      if (!this.model.get('isOriented')) {
+        $trays.css('padding-top', 0);
+        $trays.removeClass('toolbar-tray-horizontal').addClass('toolbar-tray-vertical');
       }
-      // Only make the call to get the subtrees if the orientation of the
-      // toolbar is vertical.
-      else if (isVertical) {
-        // Remove the cached menu information.
-        localStorage.removeItem('Drupal.toolbar.subtreesHash');
-        localStorage.removeItem('Drupal.toolbar.subtrees');
-        // The response from the server will call the resolve method of the
-        // Drupal.toolbar.setSubtrees Promise.
-        $.ajax(endpoint);
-        // Cache the hash for the subtrees locally.
-        localStorage.setItem('Drupal.toolbar.subtreesHash', subtreesHash);
+      else {
+        // The toolbar container is invisible. Its placement is used to
+        // determine the container for the trays.
+        $trays.css('padding-top', this.$el.find('.toolbar-bar').outerHeight());
+      }
+    },
+
+    /**
+     * Calls the endpoint URI that will return rendered subtrees with JSONP.
+     *
+     * The rendered admin menu subtrees HTML is cached on the client in
+     * localStorage until the cache of the admin menu subtrees on the server-
+     * side is invalidated. The subtreesHash is stored in localStorage as well
+     * and compared to the subtreesHash in drupalSettings to determine when the
+     * admin menu subtrees cache has been invalidated.
+     */
+    loadSubtrees: function () {
+      var $activeTab = $(this.model.get('activeTab'));
+      var orientation = this.model.get('orientation');
+      // Only load and render the admin menu subtrees if:
+      //   (1) They have not been loaded yet.
+      //   (2) The active tab is the administration menu tab, indicated by the
+      //       presence of the data-drupal-subtrees attribute.
+      //   (3) The orientation of the tray is vertical.
+      if (!this.model.get('areSubtreesLoaded') && $activeTab.data('drupal-subtrees') !== undefined && orientation === 'vertical') {
+        var subtreesHash = drupalSettings.toolbar.subtreesHash;
+        var endpoint = Drupal.url('toolbar/subtrees/' + subtreesHash);
+        var cachedSubtreesHash = localStorage.getItem('Drupal.toolbar.subtreesHash');
+        var cachedSubtrees = JSON.parse(localStorage.getItem('Drupal.toolbar.subtrees'));
+        var isVertical = this.model.get('orientation') === 'vertical';
+        // If we have the subtrees in localStorage and the subtree hash has not
+        // changed, then use the cached data.
+        if (isVertical && subtreesHash === cachedSubtreesHash && cachedSubtrees) {
+          Drupal.toolbar.setSubtrees.resolve(cachedSubtrees);
+        }
+        // Only make the call to get the subtrees if the orientation of the
+        // toolbar is vertical.
+        else if (isVertical) {
+          // Remove the cached menu information.
+          localStorage.removeItem('Drupal.toolbar.subtreesHash');
+          localStorage.removeItem('Drupal.toolbar.subtrees');
+          // The response from the server will call the resolve method of the
+          // Drupal.toolbar.setSubtrees Promise.
+          $.ajax(endpoint);
+          // Cache the hash for the subtrees locally.
+          localStorage.setItem('Drupal.toolbar.subtreesHash', subtreesHash);
+        }
       }
     }
-  }
-});
+  });
 
 }(jQuery, Drupal, drupalSettings, Backbone));
diff --git a/core/modules/tour/js/tour.js b/core/modules/tour/js/tour.js
index 0f7a8fd..5275561 100644
--- a/core/modules/tour/js/tour.js
+++ b/core/modules/tour/js/tour.js
@@ -5,208 +5,208 @@
 
 (function ($, Backbone, Drupal, document) {
 
-"use strict";
+  "use strict";
 
-var queryString = decodeURI(window.location.search);
-
-/**
- * Attaches the tour's toolbar tab behavior.
- *
- * It uses the query string for:
- * - tour: When ?tour=1 is present, the tour will start automatically
- *         after the page has loaded.
- * - tips: Pass ?tips=class in the url to filter the available tips to
- *         the subset which match the given class.
- *
- * Example:
- *   http://example.com/foo?tour=1&tips=bar
- */
-Drupal.behaviors.tour = {
-  attach: function (context) {
-    $('body').once('tour', function (index, element) {
-      var model = new Drupal.tour.models.StateModel();
-      new Drupal.tour.views.ToggleTourView({
-        el: $(context).find('#toolbar-tab-tour'),
-        model: model
-      });
-
-      model
-        // Allow other scripts to respond to tour events.
-        .on('change:isActive', function (model, isActive) {
-          $(document).trigger((isActive) ? 'drupalTourStarted' : 'drupalTourStopped');
-        })
-        // Initialization: check whether a tour is available on the current page.
-        .set('tour', $(context).find('ol#tour'));
-
-      // Start the tour immediately if toggled via query string.
-      if (/tour=?/i.test(queryString)) {
-        model.set('isActive', true);
-      }
-
-    });
-  }
-};
-
-Drupal.tour = Drupal.tour || { models: {}, views: {}};
-
-/**
- * Backbone Model for tours.
- */
-Drupal.tour.models.StateModel = Backbone.Model.extend({
-  defaults: {
-    // Indicates whether the Drupal root window has a tour.
-    tour: [],
-    // Indicates whether the tour is currently running.
-    isActive: false,
-    // Indicates which tour is the active one (necessary to cleanly stop).
-    activeTour: []
-  }
-});
-
-/**
- * Handles edit mode toggle interactions.
- */
-Drupal.tour.views.ToggleTourView = Backbone.View.extend({
-
-  events: { 'click': 'onClick' },
+  var queryString = decodeURI(window.location.search);
 
   /**
-   * Implements Backbone Views' initialize().
+   * Attaches the tour's toolbar tab behavior.
+   *
+   * It uses the query string for:
+   * - tour: When ?tour=1 is present, the tour will start automatically
+   *         after the page has loaded.
+   * - tips: Pass ?tips=class in the url to filter the available tips to
+   *         the subset which match the given class.
+   *
+   * Example:
+   *   http://example.com/foo?tour=1&tips=bar
    */
-  initialize: function () {
-    this.listenTo(this.model, 'change:tour change:isActive', this.render);
-    this.listenTo(this.model, 'change:isActive', this.toggleTour);
-  },
+  Drupal.behaviors.tour = {
+    attach: function (context) {
+      $('body').once('tour', function (index, element) {
+        var model = new Drupal.tour.models.StateModel();
+        new Drupal.tour.views.ToggleTourView({
+          el: $(context).find('#toolbar-tab-tour'),
+          model: model
+        });
 
-  /**
-   * Implements Backbone Views' render().
-   */
-  render: function () {
-    // Render the visibility.
-    this.$el.toggleClass('hidden', this._getTour().length === 0);
-    // Render the state.
-    var isActive = this.model.get('isActive');
-    this.$el.find('button')
-      .toggleClass('active', isActive)
-      .prop('aria-pressed', isActive);
-    return this;
-  },
+        model
+          // Allow other scripts to respond to tour events.
+          .on('change:isActive', function (model, isActive) {
+            $(document).trigger((isActive) ? 'drupalTourStarted' : 'drupalTourStopped');
+          })
+          // Initialization: check whether a tour is available on the current page.
+          .set('tour', $(context).find('ol#tour'));
+
+        // Start the tour immediately if toggled via query string.
+        if (/tour=?/i.test(queryString)) {
+          model.set('isActive', true);
+        }
 
-  /**
-   * Model change handler; starts or stops the tour.
-   */
-  toggleTour: function() {
-    if (this.model.get('isActive')) {
-      var $tour = this._getTour();
-      this._removeIrrelevantTourItems($tour, this._getDocument());
-      var that = this;
-      if ($tour.find('li').length) {
-        $tour.joyride({
-          postRideCallback: function () { that.model.set('isActive', false); }
-        });
-        this.model.set({ isActive: true, activeTour: $tour });
-      }
-    }
-    else {
-      this.model.get('activeTour').joyride('destroy');
-      this.model.set({ isActive: false, activeTour: [] });
+      });
     }
-  },
+  };
 
-  /**
-   * Toolbar tab click event handler; toggles isActive.
-   */
-  onClick: function (event) {
-    this.model.set('isActive', !this.model.get('isActive'));
-    event.preventDefault();
-    event.stopPropagation();
-  },
+  Drupal.tour = Drupal.tour || { models: {}, views: {}};
 
   /**
-   * Gets the tour.
-   *
-   * @return jQuery
-   *   A jQuery element pointing to a <ol> containing tour items.
-   */
-  _getTour: function () {
-    return this.model.get('tour');
-  },
-
-  /**
-   * Gets the relevant document as a jQuery element.
-   *
-   * @return jQuery
-   *   A jQuery element pointing to the document within which a tour would be
-   *   started given the current state.
+   * Backbone Model for tours.
    */
-  _getDocument: function () {
-    return $(document);
-  },
+  Drupal.tour.models.StateModel = Backbone.Model.extend({
+    defaults: {
+      // Indicates whether the Drupal root window has a tour.
+      tour: [],
+      // Indicates whether the tour is currently running.
+      isActive: false,
+      // Indicates which tour is the active one (necessary to cleanly stop).
+      activeTour: []
+    }
+  });
 
   /**
-   * Removes tour items for elements that don't have matching page elements or
-   * are explicitly filtered out via the 'tips' query string.
-   *
-   * Example:
-   *   http://example.com/foo?tips=bar
-   *
-   *   The above will filter out tips that do not have a matching page element or
-   *   don't have the "bar" class.
-   *
-   * @param jQuery $tour
-   *   A jQuery element pointing to a <ol> containing tour items.
-   * @param jQuery $document
-   *   A jQuery element pointing to the document within which the elements
-   *   should be sought.
-   *
-   * @see _getDocument()
+   * Handles edit mode toggle interactions.
    */
-  _removeIrrelevantTourItems: function ($tour, $document) {
-    var removals = false;
-    var tips = /tips=([^&]+)/.exec(queryString);
-    $tour
-      .find('li')
-      .each(function () {
-        var $this = $(this);
-        var itemId = $this.attr('data-id');
-        var itemClass = $this.attr('data-class');
-        // If the query parameter 'tips' is set, remove all tips that don't
-        // have the matching class.
-        if (tips && !$(this).hasClass(tips[1])) {
-          removals = true;
-          $this.remove();
-          return;
+  Drupal.tour.views.ToggleTourView = Backbone.View.extend({
+
+    events: { 'click': 'onClick' },
+
+    /**
+     * Implements Backbone Views' initialize().
+     */
+    initialize: function () {
+      this.listenTo(this.model, 'change:tour change:isActive', this.render);
+      this.listenTo(this.model, 'change:isActive', this.toggleTour);
+    },
+
+    /**
+     * Implements Backbone Views' render().
+     */
+    render: function () {
+      // Render the visibility.
+      this.$el.toggleClass('hidden', this._getTour().length === 0);
+      // Render the state.
+      var isActive = this.model.get('isActive');
+      this.$el.find('button')
+        .toggleClass('active', isActive)
+        .prop('aria-pressed', isActive);
+      return this;
+    },
+
+    /**
+     * Model change handler; starts or stops the tour.
+     */
+    toggleTour: function () {
+      if (this.model.get('isActive')) {
+        var $tour = this._getTour();
+        this._removeIrrelevantTourItems($tour, this._getDocument());
+        var that = this;
+        if ($tour.find('li').length) {
+          $tour.joyride({
+            postRideCallback: function () { that.model.set('isActive', false); }
+          });
+          this.model.set({ isActive: true, activeTour: $tour });
         }
-        // Remove tip from the DOM if there is no corresponding page element.
-        if ((!itemId && !itemClass) ||
+      }
+      else {
+        this.model.get('activeTour').joyride('destroy');
+        this.model.set({ isActive: false, activeTour: [] });
+      }
+    },
+
+    /**
+     * Toolbar tab click event handler; toggles isActive.
+     */
+    onClick: function (event) {
+      this.model.set('isActive', !this.model.get('isActive'));
+      event.preventDefault();
+      event.stopPropagation();
+    },
+
+    /**
+     * Gets the tour.
+     *
+     * @return jQuery
+     *   A jQuery element pointing to a <ol> containing tour items.
+     */
+    _getTour: function () {
+      return this.model.get('tour');
+    },
+
+    /**
+     * Gets the relevant document as a jQuery element.
+     *
+     * @return jQuery
+     *   A jQuery element pointing to the document within which a tour would be
+     *   started given the current state.
+     */
+    _getDocument: function () {
+      return $(document);
+    },
+
+    /**
+     * Removes tour items for elements that don't have matching page elements or
+     * are explicitly filtered out via the 'tips' query string.
+     *
+     * Example:
+     *   http://example.com/foo?tips=bar
+     *
+     *   The above will filter out tips that do not have a matching page element or
+     *   don't have the "bar" class.
+     *
+     * @param jQuery $tour
+     *   A jQuery element pointing to a <ol> containing tour items.
+     * @param jQuery $document
+     *   A jQuery element pointing to the document within which the elements
+     *   should be sought.
+     *
+     * @see _getDocument()
+     */
+    _removeIrrelevantTourItems: function ($tour, $document) {
+      var removals = false;
+      var tips = /tips=([^&]+)/.exec(queryString);
+      $tour
+        .find('li')
+        .each(function () {
+          var $this = $(this);
+          var itemId = $this.attr('data-id');
+          var itemClass = $this.attr('data-class');
+          // If the query parameter 'tips' is set, remove all tips that don't
+          // have the matching class.
+          if (tips && !$(this).hasClass(tips[1])) {
+            removals = true;
+            $this.remove();
+            return;
+          }
+          // Remove tip from the DOM if there is no corresponding page element.
+          if ((!itemId && !itemClass) ||
             (itemId && $document.find('#' + itemId).length) ||
-           (itemClass && $document.find('.' + itemClass).length)){
-          return;
+            (itemClass && $document.find('.' + itemClass).length)) {
+            return;
+          }
+          removals = true;
+          $this.remove();
+        });
+
+      // If there were removals, we'll have to do some clean-up.
+      if (removals) {
+        var total = $tour.find('li').length;
+        if (!total) {
+          this.model.set({ tour: [] });
         }
-        removals = true;
-        $this.remove();
-      });
 
-    // If there were removals, we'll have to do some clean-up.
-    if (removals) {
-      var total = $tour.find('li').length;
-      if (!total) {
-        this.model.set({ tour: [] });
+        $tour
+          .find('li')
+          // Rebuild the progress data.
+          .each(function (index) {
+            var progress = Drupal.t('!tour_item of !total', { '!tour_item': index + 1, '!total': total });
+            $(this).find('.tour-progress').text(progress);
+          })
+          // Update the last item to have "End tour" as the button.
+          .last()
+          .attr('data-text', Drupal.t('End tour'));
       }
-
-      $tour
-        .find('li')
-        // Rebuild the progress data.
-        .each(function (index) {
-          var progress = Drupal.t('!tour_item of !total', { '!tour_item': index + 1, '!total': total });
-          $(this).find('.tour-progress').text(progress);
-        })
-        // Update the last item to have "End tour" as the button.
-        .last()
-        .attr('data-text', Drupal.t('End tour'));
     }
-  }
 
-});
+  });
 
 })(jQuery, Backbone, Drupal, document);
diff --git a/core/modules/user/user.js b/core/modules/user/user.js
index 7b1ccfa..1748a2c 100644
--- a/core/modules/user/user.js
+++ b/core/modules/user/user.js
@@ -1,194 +1,194 @@
 (function ($) {
 
-"use strict";
-
-/**
- * Attach handlers to evaluate the strength of any password fields and to check
- * that its confirmation is correct.
- */
-Drupal.behaviors.password = {
-  attach: function (context, settings) {
-    var translate = settings.password;
-    $(context).find('input.password-field').once('password', function () {
-      var passwordInput = $(this);
-      var innerWrapper = $(this).parent();
-      var outerWrapper = $(this).parent().parent();
-
-      // Add identifying class to password element parent.
-      innerWrapper.addClass('password-parent');
-
-      // Add the password confirmation layer.
-      outerWrapper.find('input.password-confirm').parent().append('<div class="password-confirm">' + translate.confirmTitle + ' <span></span></div>').addClass('confirm-parent');
-      var confirmInput = outerWrapper.find('input.password-confirm');
-      var confirmResult = outerWrapper.find('div.password-confirm');
-      var confirmChild = confirmResult.find('span');
-
-      // If the password strength indicator is enabled, add its markup.
-      if (settings.password.showStrengthIndicator) {
-        var passwordMeter = '<div class="password-strength"><div class="password-strength-text" aria-live="assertive"></div><div class="password-strength-title">' + translate.strengthTitle + '</div><div class="password-indicator"><div class="indicator"></div></div></div>';
-        confirmInput.parent().after('<div class="password-suggestions description"></div>');
-        innerWrapper.append(passwordMeter);
-        var passwordDescription = outerWrapper.find('div.password-suggestions').hide();
-      }
+  "use strict";
+
+  /**
+   * Attach handlers to evaluate the strength of any password fields and to check
+   * that its confirmation is correct.
+   */
+  Drupal.behaviors.password = {
+    attach: function (context, settings) {
+      var translate = settings.password;
+      $(context).find('input.password-field').once('password', function () {
+        var passwordInput = $(this);
+        var innerWrapper = $(this).parent();
+        var outerWrapper = $(this).parent().parent();
+
+        // Add identifying class to password element parent.
+        innerWrapper.addClass('password-parent');
+
+        // Add the password confirmation layer.
+        outerWrapper.find('input.password-confirm').parent().append('<div class="password-confirm">' + translate.confirmTitle + ' <span></span></div>').addClass('confirm-parent');
+        var confirmInput = outerWrapper.find('input.password-confirm');
+        var confirmResult = outerWrapper.find('div.password-confirm');
+        var confirmChild = confirmResult.find('span');
+
+        // If the password strength indicator is enabled, add its markup.
+        if (settings.password.showStrengthIndicator) {
+          var passwordMeter = '<div class="password-strength"><div class="password-strength-text" aria-live="assertive"></div><div class="password-strength-title">' + translate.strengthTitle + '</div><div class="password-indicator"><div class="indicator"></div></div></div>';
+          confirmInput.parent().after('<div class="password-suggestions description"></div>');
+          innerWrapper.append(passwordMeter);
+          var passwordDescription = outerWrapper.find('div.password-suggestions').hide();
+        }
 
-      // Check that password and confirmation inputs match.
-      var passwordCheckMatch = function (confirmInputVal) {
-        var success = passwordInput.val() === confirmInputVal;
-        var confirmClass = success ? 'ok' : 'error';
+        // Check that password and confirmation inputs match.
+        var passwordCheckMatch = function (confirmInputVal) {
+          var success = passwordInput.val() === confirmInputVal;
+          var confirmClass = success ? 'ok' : 'error';
+
+          // Fill in the success message and set the class accordingly.
+          confirmChild.html(translate['confirm' + (success ? 'Success' : 'Failure')])
+            .removeClass('ok error').addClass(confirmClass);
+        };
+
+        // Check the password strength.
+        var passwordCheck = function () {
+          if (settings.password.showStrengthIndicator) {
+            // Evaluate the password strength.
+            var result = Drupal.evaluatePasswordStrength(passwordInput.val(), settings.password);
+
+            // Update the suggestions for how to improve the password.
+            if (passwordDescription.html() !== result.message) {
+              passwordDescription.html(result.message);
+            }
+
+            // Only show the description box if a weakness exists in the password.
+            passwordDescription.toggle(result.strength !== 100);
+
+            // Adjust the length of the strength indicator.
+            innerWrapper.find('.indicator')
+              .css('width', result.strength + '%')
+              .css('background-color', result.indicatorColor);
+
+            // Update the strength indication text.
+            innerWrapper.find('.password-strength-text').html(result.indicatorText);
+          }
 
-        // Fill in the success message and set the class accordingly.
-        confirmChild.html(translate['confirm' + (success ? 'Success' : 'Failure')])
-          .removeClass('ok error').addClass(confirmClass);
-      };
+          // Check the value in the confirm input and show results.
+          if (confirmInput.val()) {
+            passwordCheckMatch(confirmInput.val());
+            confirmResult.css({ visibility: 'visible' });
+          }
+          else {
+            confirmResult.css({ visibility: 'hidden' });
+          }
+        };
 
-      // Check the password strength.
-      var passwordCheck = function () {
-        if (settings.password.showStrengthIndicator) {
-          // Evaluate the password strength.
-          var result = Drupal.evaluatePasswordStrength(passwordInput.val(), settings.password);
+        // Monitor input events.
+        passwordInput.on('input', passwordCheck);
+        confirmInput.on('input', passwordCheck);
+      });
+    }
+  };
+
+  /**
+   * Evaluate the strength of a user's password.
+   *
+   * Returns the estimated strength and the relevant output message.
+   */
+  Drupal.evaluatePasswordStrength = function (password, translate) {
+    var indicatorText, indicatorColor, weaknesses = 0, strength = 100, msg = [];
+
+    var hasLowercase = /[a-z]+/.test(password);
+    var hasUppercase = /[A-Z]+/.test(password);
+    var hasNumbers = /[0-9]+/.test(password);
+    var hasPunctuation = /[^a-zA-Z0-9]+/.test(password);
+
+    // If there is a username edit box on the page, compare password to that, otherwise
+    // use value from the database.
+    var usernameBox = $('input.username');
+    var username = (usernameBox.length > 0) ? usernameBox.val() : translate.username;
+
+    // Lose 5 points for every character less than 6, plus a 30 point penalty.
+    if (password.length < 6) {
+      msg.push(translate.tooShort);
+      strength -= ((6 - password.length) * 5) + 30;
+    }
 
-          // Update the suggestions for how to improve the password.
-          if (passwordDescription.html() !== result.message) {
-            passwordDescription.html(result.message);
-          }
+    // Count weaknesses.
+    if (!hasLowercase) {
+      msg.push(translate.addLowerCase);
+      weaknesses++;
+    }
+    if (!hasUppercase) {
+      msg.push(translate.addUpperCase);
+      weaknesses++;
+    }
+    if (!hasNumbers) {
+      msg.push(translate.addNumbers);
+      weaknesses++;
+    }
+    if (!hasPunctuation) {
+      msg.push(translate.addPunctuation);
+      weaknesses++;
+    }
 
-          // Only show the description box if a weakness exists in the password.
-          passwordDescription.toggle(result.strength !== 100);
+    // Apply penalty for each weakness (balanced against length penalty).
+    switch (weaknesses) {
+      case 1:
+        strength -= 12.5;
+        break;
 
-          // Adjust the length of the strength indicator.
-          innerWrapper.find('.indicator')
-            .css('width', result.strength + '%')
-            .css('background-color', result.indicatorColor);
+      case 2:
+        strength -= 25;
+        break;
 
-          // Update the strength indication text.
-          innerWrapper.find('.password-strength-text').html(result.indicatorText);
-        }
+      case 3:
+        strength -= 40;
+        break;
 
-        // Check the value in the confirm input and show results.
-        if (confirmInput.val()) {
-          passwordCheckMatch(confirmInput.val());
-          confirmResult.css({ visibility: 'visible' });
-        }
-        else {
-          confirmResult.css({ visibility: 'hidden' });
-        }
-      };
-
-      // Monitor input events.
-      passwordInput.on('input', passwordCheck);
-      confirmInput.on('input', passwordCheck);
-    });
-  }
-};
-
-/**
- * Evaluate the strength of a user's password.
- *
- * Returns the estimated strength and the relevant output message.
- */
-Drupal.evaluatePasswordStrength = function (password, translate) {
-  var indicatorText, indicatorColor, weaknesses = 0, strength = 100, msg = [];
-
-  var hasLowercase = /[a-z]+/.test(password);
-  var hasUppercase = /[A-Z]+/.test(password);
-  var hasNumbers = /[0-9]+/.test(password);
-  var hasPunctuation = /[^a-zA-Z0-9]+/.test(password);
-
-  // If there is a username edit box on the page, compare password to that, otherwise
-  // use value from the database.
-  var usernameBox = $('input.username');
-  var username = (usernameBox.length > 0) ? usernameBox.val() : translate.username;
-
-  // Lose 5 points for every character less than 6, plus a 30 point penalty.
-  if (password.length < 6) {
-    msg.push(translate.tooShort);
-    strength -= ((6 - password.length) * 5) + 30;
-  }
-
-  // Count weaknesses.
-  if (!hasLowercase) {
-    msg.push(translate.addLowerCase);
-    weaknesses++;
-  }
-  if (!hasUppercase) {
-    msg.push(translate.addUpperCase);
-    weaknesses++;
-  }
-  if (!hasNumbers) {
-    msg.push(translate.addNumbers);
-    weaknesses++;
-  }
-  if (!hasPunctuation) {
-    msg.push(translate.addPunctuation);
-    weaknesses++;
-  }
-
-  // Apply penalty for each weakness (balanced against length penalty).
-  switch (weaknesses) {
-    case 1:
-      strength -= 12.5;
-      break;
-
-    case 2:
-      strength -= 25;
-      break;
-
-    case 3:
-      strength -= 40;
-      break;
-
-    case 4:
-      strength -= 40;
-      break;
-  }
-
-  // Check if password is the same as the username.
-  if (password !== '' && password.toLowerCase() === username.toLowerCase()) {
-    msg.push(translate.sameAsUsername);
-    // Passwords the same as username are always very weak.
-    strength = 5;
-  }
-
-  // Based on the strength, work out what text should be shown by the password strength meter.
-  if (strength < 60) {
-    indicatorText = translate.weak;
-    indicatorColor = '#bb5555';
-  } else if (strength < 70) {
-    indicatorText = translate.fair;
-    indicatorColor = '#bbbb55';
-  } else if (strength < 80) {
-    indicatorText = translate.good;
-    indicatorColor = '#4863a0';
-  } else if (strength <= 100) {
-    indicatorText = translate.strong;
-    indicatorColor = '#47c965';
-  }
-
-  // Assemble the final message.
-  msg = translate.hasWeaknesses + '<ul><li>' + msg.join('</li><li>') + '</li></ul>';
-  return { strength: strength, message: msg, indicatorText: indicatorText, indicatorColor: indicatorColor };
-
-};
-
-/**
- * Field instance settings screen: force the 'Display on registration form'
- * checkbox checked whenever 'Required' is checked.
- */
-Drupal.behaviors.fieldUserRegistration = {
-  attach: function (context, settings) {
-    var $checkbox = $('form#field-ui-field-edit-form input#edit-instance-settings-user-register-form');
-
-    if ($checkbox.length) {
-      $(context).find('input#edit-instance-required').once('user-register-form-checkbox', function () {
-        $(this).on('change', function (e) {
-          if ($(this).prop('checked')) {
-            $checkbox.prop('checked', true);
-          }
+      case 4:
+        strength -= 40;
+        break;
+    }
+
+    // Check if password is the same as the username.
+    if (password !== '' && password.toLowerCase() === username.toLowerCase()) {
+      msg.push(translate.sameAsUsername);
+      // Passwords the same as username are always very weak.
+      strength = 5;
+    }
+
+    // Based on the strength, work out what text should be shown by the password strength meter.
+    if (strength < 60) {
+      indicatorText = translate.weak;
+      indicatorColor = '#bb5555';
+    } else if (strength < 70) {
+      indicatorText = translate.fair;
+      indicatorColor = '#bbbb55';
+    } else if (strength < 80) {
+      indicatorText = translate.good;
+      indicatorColor = '#4863a0';
+    } else if (strength <= 100) {
+      indicatorText = translate.strong;
+      indicatorColor = '#47c965';
+    }
+
+    // Assemble the final message.
+    msg = translate.hasWeaknesses + '<ul><li>' + msg.join('</li><li>') + '</li></ul>';
+    return { strength: strength, message: msg, indicatorText: indicatorText, indicatorColor: indicatorColor };
+
+  };
+
+  /**
+   * Field instance settings screen: force the 'Display on registration form'
+   * checkbox checked whenever 'Required' is checked.
+   */
+  Drupal.behaviors.fieldUserRegistration = {
+    attach: function (context, settings) {
+      var $checkbox = $('form#field-ui-field-edit-form input#edit-instance-settings-user-register-form');
+
+      if ($checkbox.length) {
+        $(context).find('input#edit-instance-required').once('user-register-form-checkbox', function () {
+          $(this).on('change', function (e) {
+            if ($(this).prop('checked')) {
+              $checkbox.prop('checked', true);
+            }
+          });
         });
-      });
 
+      }
     }
-  }
-};
+  };
 
 })(jQuery);
diff --git a/core/modules/user/user.permissions.js b/core/modules/user/user.permissions.js
index 850dd88..415fc53 100644
--- a/core/modules/user/user.permissions.js
+++ b/core/modules/user/user.permissions.js
@@ -1,74 +1,74 @@
 (function ($) {
 
-"use strict";
+  "use strict";
 
-/**
- * Shows checked and disabled checkboxes for inherited permissions.
- */
-Drupal.behaviors.permissions = {
-  attach: function (context) {
-    var self = this;
-    $('table#permissions').once('permissions', function () {
-      // On a site with many roles and permissions, this behavior initially has
-      // to perform thousands of DOM manipulations to inject checkboxes and hide
-      // them. By detaching the table from the DOM, all operations can be
-      // performed without triggering internal layout and re-rendering processes
-      // in the browser.
-      var $table = $(this);
-      var $ancestor, method;
-      if ($table.prev().length) {
-        $ancestor = $table.prev();
-        method = 'after';
-      }
-      else {
-        $ancestor = $table.parent();
-        method = 'append';
-      }
-      $table.detach();
+  /**
+   * Shows checked and disabled checkboxes for inherited permissions.
+   */
+  Drupal.behaviors.permissions = {
+    attach: function (context) {
+      var self = this;
+      $('table#permissions').once('permissions', function () {
+        // On a site with many roles and permissions, this behavior initially has
+        // to perform thousands of DOM manipulations to inject checkboxes and hide
+        // them. By detaching the table from the DOM, all operations can be
+        // performed without triggering internal layout and re-rendering processes
+        // in the browser.
+        var $table = $(this);
+        var $ancestor, method;
+        if ($table.prev().length) {
+          $ancestor = $table.prev();
+          method = 'after';
+        }
+        else {
+          $ancestor = $table.parent();
+          method = 'append';
+        }
+        $table.detach();
 
-      // Create dummy checkboxes. We use dummy checkboxes instead of reusing
-      // the existing checkboxes here because new checkboxes don't alter the
-      // submitted form. If we'd automatically check existing checkboxes, the
-      // permission table would be polluted with redundant entries. This
-      // is deliberate, but desirable when we automatically check them.
-      var $dummy = $('<input type="checkbox" class="dummy-checkbox" disabled="disabled" checked="checked" />')
-        .attr('title', Drupal.t("This permission is inherited from the authenticated user role."))
-        .hide();
+        // Create dummy checkboxes. We use dummy checkboxes instead of reusing
+        // the existing checkboxes here because new checkboxes don't alter the
+        // submitted form. If we'd automatically check existing checkboxes, the
+        // permission table would be polluted with redundant entries. This
+        // is deliberate, but desirable when we automatically check them.
+        var $dummy = $('<input type="checkbox" class="dummy-checkbox" disabled="disabled" checked="checked" />')
+          .attr('title', Drupal.t("This permission is inherited from the authenticated user role."))
+          .hide();
 
-      $table.find('input[type=checkbox]').not('.rid-anonymous, .rid-authenticated').addClass('real-checkbox').each(function () {
-        $dummy.clone().insertAfter(this);
-      });
+        $table.find('input[type=checkbox]').not('.rid-anonymous, .rid-authenticated').addClass('real-checkbox').each(function () {
+          $dummy.clone().insertAfter(this);
+        });
 
-      // Initialize the authenticated user checkbox.
-      $table.find('input[type=checkbox].rid-authenticated')
-        .on('click.permissions', self.toggle)
-        // .triggerHandler() cannot be used here, as it only affects the first
-        // element.
-        .each(self.toggle);
+        // Initialize the authenticated user checkbox.
+        $table.find('input[type=checkbox].rid-authenticated')
+          .on('click.permissions', self.toggle)
+          // .triggerHandler() cannot be used here, as it only affects the first
+          // element.
+          .each(self.toggle);
 
-      // Re-insert the table into the DOM.
-      $ancestor[method]($table);
-    });
-  },
+        // Re-insert the table into the DOM.
+        $ancestor[method]($table);
+      });
+    },
 
-  /**
-   * Toggles all dummy checkboxes based on the checkboxes' state.
-   *
-   * If the "authenticated user" checkbox is checked, the checked and disabled
-   * checkboxes are shown, the real checkboxes otherwise.
-   */
-  toggle: function () {
-    var authCheckbox = this, $row = $(this).closest('tr');
-    // jQuery performs too many layout calculations for .hide() and .show(),
-    // leading to a major page rendering lag on sites with many roles and
-    // permissions. Therefore, we toggle visibility directly.
-    $row.find('.real-checkbox').each(function () {
-      this.style.display = (authCheckbox.checked ? 'none' : '');
-    });
-    $row.find('.dummy-checkbox').each(function () {
-      this.style.display = (authCheckbox.checked ? '' : 'none');
-    });
-  }
-};
+    /**
+     * Toggles all dummy checkboxes based on the checkboxes' state.
+     *
+     * If the "authenticated user" checkbox is checked, the checked and disabled
+     * checkboxes are shown, the real checkboxes otherwise.
+     */
+    toggle: function () {
+      var authCheckbox = this, $row = $(this).closest('tr');
+      // jQuery performs too many layout calculations for .hide() and .show(),
+      // leading to a major page rendering lag on sites with many roles and
+      // permissions. Therefore, we toggle visibility directly.
+      $row.find('.real-checkbox').each(function () {
+        this.style.display = (authCheckbox.checked ? 'none' : '');
+      });
+      $row.find('.dummy-checkbox').each(function () {
+        this.style.display = (authCheckbox.checked ? '' : 'none');
+      });
+    }
+  };
 
 })(jQuery);
diff --git a/core/modules/views/js/ajax_view.js b/core/modules/views/js/ajax_view.js
index c8a3d7b..0a96abe 100644
--- a/core/modules/views/js/ajax_view.js
+++ b/core/modules/views/js/ajax_view.js
@@ -4,148 +4,148 @@
  */
 (function ($, Drupal, drupalSettings) {
 
-"use strict";
-
-/**
- * Attaches the AJAX behavior to Views exposed filter forms and key View links.
- */
-Drupal.behaviors.ViewsAjaxView = {};
-Drupal.behaviors.ViewsAjaxView.attach = function() {
-  if (drupalSettings && drupalSettings.views && drupalSettings.views.ajaxViews) {
-    var ajaxViews = drupalSettings.views.ajaxViews;
-    for (var i in ajaxViews) {
-      if (ajaxViews.hasOwnProperty(i)) {
-        Drupal.views.instances[i] = new Drupal.views.ajaxView(ajaxViews[i]);
+  "use strict";
+
+  /**
+   * Attaches the AJAX behavior to Views exposed filter forms and key View links.
+   */
+  Drupal.behaviors.ViewsAjaxView = {};
+  Drupal.behaviors.ViewsAjaxView.attach = function () {
+    if (drupalSettings && drupalSettings.views && drupalSettings.views.ajaxViews) {
+      var ajaxViews = drupalSettings.views.ajaxViews;
+      for (var i in ajaxViews) {
+        if (ajaxViews.hasOwnProperty(i)) {
+          Drupal.views.instances[i] = new Drupal.views.ajaxView(ajaxViews[i]);
+        }
       }
     }
-  }
-};
+  };
 
-Drupal.views = {};
-Drupal.views.instances = {};
+  Drupal.views = {};
+  Drupal.views.instances = {};
 
-/**
- * Javascript object for a certain view.
- */
-Drupal.views.ajaxView = function(settings) {
-  var selector = '.view-dom-id-' + settings.view_dom_id;
-  this.$view = $(selector);
-
-  // Retrieve the path to use for views' ajax.
-  var ajax_path = drupalSettings.views.ajax_path;
-
-  // If there are multiple views this might've ended up showing up multiple times.
-  if (ajax_path.constructor.toString().indexOf("Array") !== -1) {
-    ajax_path = ajax_path[0];
-  }
-
-  // Check if there are any GET parameters to send to views.
-  var queryString = window.location.search || '';
-  if (queryString !== '') {
-    // Remove the question mark and Drupal path component if any.
-    queryString = queryString.slice(1).replace(/q=[^&]+&?|&?render=[^&]+/, '');
+  /**
+   * Javascript object for a certain view.
+   */
+  Drupal.views.ajaxView = function (settings) {
+    var selector = '.view-dom-id-' + settings.view_dom_id;
+    this.$view = $(selector);
+
+    // Retrieve the path to use for views' ajax.
+    var ajax_path = drupalSettings.views.ajax_path;
+
+    // If there are multiple views this might've ended up showing up multiple times.
+    if (ajax_path.constructor.toString().indexOf("Array") !== -1) {
+      ajax_path = ajax_path[0];
+    }
+
+    // Check if there are any GET parameters to send to views.
+    var queryString = window.location.search || '';
     if (queryString !== '') {
-      // If there is a '?' in ajax_path, clean url are on and & should be used to add parameters.
-      queryString = ((/\?/.test(ajax_path)) ? '&' : '?') + queryString;
+      // Remove the question mark and Drupal path component if any.
+      queryString = queryString.slice(1).replace(/q=[^&]+&?|&?render=[^&]+/, '');
+      if (queryString !== '') {
+        // If there is a '?' in ajax_path, clean url are on and & should be used to add parameters.
+        queryString = ((/\?/.test(ajax_path)) ? '&' : '?') + queryString;
+      }
     }
-  }
-
-  this.element_settings = {
-    url: ajax_path + queryString,
-    submit: settings,
-    setClick: true,
-    event: 'click',
-    selector: selector,
-    progress: { type: 'throbber' }
+
+    this.element_settings = {
+      url: ajax_path + queryString,
+      submit: settings,
+      setClick: true,
+      event: 'click',
+      selector: selector,
+      progress: { type: 'throbber' }
+    };
+
+    this.settings = settings;
+
+    // Add the ajax to exposed forms.
+    this.$exposed_form = $('form#views-exposed-form-' + settings.view_name.replace(/_/g, '-') + '-' + settings.view_display_id.replace(/_/g, '-'));
+    this.$exposed_form.once('exposed-form', jQuery.proxy(this.attachExposedFormAjax, this));
+
+    // Add the ajax to pagers.
+    this.$view
+      // Don't attach to nested views. Doing so would attach multiple behaviors
+      // to a given element.
+      .filter(jQuery.proxy(this.filterNestedViews, this))
+      .once('ajax-pager', jQuery.proxy(this.attachPagerAjax, this));
+
+    // Add a trigger to update this view specifically. In order to trigger a
+    // refresh use the following code.
+    //
+    // @code
+    // jQuery('.view-name').trigger('RefreshView');
+    // @endcode
+    var self_settings = this.element_settings;
+    self_settings.event = 'RefreshView';
+    this.refreshViewAjax = new Drupal.ajax(this.selector, this.$view, self_settings);
   };
 
-  this.settings = settings;
-
-  // Add the ajax to exposed forms.
-  this.$exposed_form = $('form#views-exposed-form-'+ settings.view_name.replace(/_/g, '-') + '-' + settings.view_display_id.replace(/_/g, '-'));
-  this.$exposed_form.once('exposed-form', jQuery.proxy(this.attachExposedFormAjax, this));
-
-  // Add the ajax to pagers.
-  this.$view
-    // Don't attach to nested views. Doing so would attach multiple behaviors
-    // to a given element.
-    .filter(jQuery.proxy(this.filterNestedViews, this))
-    .once('ajax-pager', jQuery.proxy(this.attachPagerAjax, this));
-
-  // Add a trigger to update this view specifically. In order to trigger a
-  // refresh use the following code.
-  //
-  // @code
-  // jQuery('.view-name').trigger('RefreshView');
-  // @endcode
-  var self_settings = this.element_settings;
-  self_settings.event = 'RefreshView';
-  this.refreshViewAjax = new Drupal.ajax(this.selector, this.$view, self_settings);
-};
-
-Drupal.views.ajaxView.prototype.attachExposedFormAjax = function() {
-  var button = $('input[type=submit], input[type=image]', this.$exposed_form);
-  button = button[0];
-
-  this.exposedFormAjax = new Drupal.ajax($(button).attr('id'), button, this.element_settings);
-};
-
-Drupal.views.ajaxView.prototype.filterNestedViews= function() {
-  // If there is at least one parent with a view class, this view
-  // is nested (e.g., an attachment). Bail.
-  return !this.$view.parents('.view').size();
-};
+  Drupal.views.ajaxView.prototype.attachExposedFormAjax = function () {
+    var button = $('input[type=submit], input[type=image]', this.$exposed_form);
+    button = button[0];
 
-/**
- * Attach the ajax behavior to each link.
- */
-Drupal.views.ajaxView.prototype.attachPagerAjax = function() {
-  this.$view.find('ul.pager > li > a, th.views-field a, .attachment .views-summary a')
-  .each(jQuery.proxy(this.attachPagerLinkAjax, this));
-};
+    this.exposedFormAjax = new Drupal.ajax($(button).attr('id'), button, this.element_settings);
+  };
 
-/**
- * Attach the ajax behavior to a singe link.
- */
-Drupal.views.ajaxView.prototype.attachPagerLinkAjax = function(id, link) {
-  var $link = $(link);
-  var viewData = {};
-  var href = $link.attr('href');
-  // Construct an object using the settings defaults and then overriding
-  // with data specific to the link.
-  $.extend(
-    viewData,
-    this.settings,
-    Drupal.Views.parseQueryString(href),
-    // Extract argument data from the URL.
-    Drupal.Views.parseViewArgs(href, this.settings.view_base_path)
-  );
-
-  // For anchor tags, these will go to the target of the anchor rather
-  // than the usual location.
-  $.extend(viewData, Drupal.Views.parseViewArgs(href, this.settings.view_base_path));
-
-  this.element_settings.submit = viewData;
-  this.pagerAjax = new Drupal.ajax(false, $link, this.element_settings);
-};
-
-Drupal.AjaxCommands.prototype.viewsScrollTop = function (ajax, response) {
-  // Scroll to the top of the view. This will allow users
-  // to browse newly loaded content after e.g. clicking a pager
-  // link.
-  var offset = $(response.selector).offset();
-  // We can't guarantee that the scrollable object should be
-  // the body, as the view could be embedded in something
-  // more complex such as a modal popup. Recurse up the DOM
-  // and scroll the first element that has a non-zero top.
-  var scrollTarget = response.selector;
-  while ($(scrollTarget).scrollTop() === 0 && $(scrollTarget).parent()) {
-    scrollTarget = $(scrollTarget).parent();
-  }
-  // Only scroll upward
-  if (offset.top - 10 < $(scrollTarget).scrollTop()) {
-    $(scrollTarget).animate({scrollTop: (offset.top - 10)}, 500);
-  }
-};
+  Drupal.views.ajaxView.prototype.filterNestedViews = function () {
+    // If there is at least one parent with a view class, this view
+    // is nested (e.g., an attachment). Bail.
+    return !this.$view.parents('.view').size();
+  };
+
+  /**
+   * Attach the ajax behavior to each link.
+   */
+  Drupal.views.ajaxView.prototype.attachPagerAjax = function () {
+    this.$view.find('ul.pager > li > a, th.views-field a, .attachment .views-summary a')
+      .each(jQuery.proxy(this.attachPagerLinkAjax, this));
+  };
+
+  /**
+   * Attach the ajax behavior to a singe link.
+   */
+  Drupal.views.ajaxView.prototype.attachPagerLinkAjax = function (id, link) {
+    var $link = $(link);
+    var viewData = {};
+    var href = $link.attr('href');
+    // Construct an object using the settings defaults and then overriding
+    // with data specific to the link.
+    $.extend(
+      viewData,
+      this.settings,
+      Drupal.Views.parseQueryString(href),
+      // Extract argument data from the URL.
+      Drupal.Views.parseViewArgs(href, this.settings.view_base_path)
+    );
+
+    // For anchor tags, these will go to the target of the anchor rather
+    // than the usual location.
+    $.extend(viewData, Drupal.Views.parseViewArgs(href, this.settings.view_base_path));
+
+    this.element_settings.submit = viewData;
+    this.pagerAjax = new Drupal.ajax(false, $link, this.element_settings);
+  };
+
+  Drupal.AjaxCommands.prototype.viewsScrollTop = function (ajax, response) {
+    // Scroll to the top of the view. This will allow users
+    // to browse newly loaded content after e.g. clicking a pager
+    // link.
+    var offset = $(response.selector).offset();
+    // We can't guarantee that the scrollable object should be
+    // the body, as the view could be embedded in something
+    // more complex such as a modal popup. Recurse up the DOM
+    // and scroll the first element that has a non-zero top.
+    var scrollTarget = response.selector;
+    while ($(scrollTarget).scrollTop() === 0 && $(scrollTarget).parent()) {
+      scrollTarget = $(scrollTarget).parent();
+    }
+    // Only scroll upward
+    if (offset.top - 10 < $(scrollTarget).scrollTop()) {
+      $(scrollTarget).animate({scrollTop: (offset.top - 10)}, 500);
+    }
+  };
 
 })(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/views/js/views-contextual.js b/core/modules/views/js/views-contextual.js
index e90283d..e5eb14b 100644
--- a/core/modules/views/js/views-contextual.js
+++ b/core/modules/views/js/views-contextual.js
@@ -4,16 +4,16 @@
  */
 (function ($) {
 
-"use strict";
+  "use strict";
 
-Drupal.behaviors.viewsContextualLinks = {
-  attach: function (context) {
-    var id = $('body').attr('data-views-page-contextual-id');
+  Drupal.behaviors.viewsContextualLinks = {
+    attach: function (context) {
+      var id = $('body').attr('data-views-page-contextual-id');
 
-    $('[data-contextual-id="' + id + '"]')
-      .closest(':has(.view)')
-      .addClass('contextual-region');
-  }
-};
+      $('[data-contextual-id="' + id + '"]')
+        .closest(':has(.view)')
+        .addClass('contextual-region');
+    }
+  };
 
 })(jQuery);
diff --git a/core/modules/views_ui/js/ajax.js b/core/modules/views_ui/js/ajax.js
index f926330..0d133e0 100644
--- a/core/modules/views_ui/js/ajax.js
+++ b/core/modules/views_ui/js/ajax.js
@@ -92,48 +92,48 @@
 
       $('div#views-live-preview a')
         .once('views-ajax').each(function () {
-        // We don't bind to links without a URL.
-        if (!$(this).attr('href')) {
-          return true;
-        }
-
-        var element_settings = base_element_settings;
-        // Set the URL to go to the anchor.
-        element_settings.url = $(this).attr('href');
-        if (Drupal.Views.getPath(element_settings.url).substring(0, 21) !== 'admin/structure/views') {
-          return true;
-        }
+          // We don't bind to links without a URL.
+          if (!$(this).attr('href')) {
+            return true;
+          }
 
-        element_settings.wrapper = 'views-preview-wrapper';
-        element_settings.method = 'replaceWith';
-        var base = $(this).attr('id');
-        Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
-      });
+          var element_settings = base_element_settings;
+          // Set the URL to go to the anchor.
+          element_settings.url = $(this).attr('href');
+          if (Drupal.Views.getPath(element_settings.url).substring(0, 21) !== 'admin/structure/views') {
+            return true;
+          }
+
+          element_settings.wrapper = 'views-preview-wrapper';
+          element_settings.method = 'replaceWith';
+          var base = $(this).attr('id');
+          Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
+        });
 
       // Within a live preview, make exposed widget form buttons re-trigger the
       // Preview button.
       // @todo Revisit this after fixing Views UI to display a Preview outside
       //   of the main Edit form.
       $('div#views-live-preview input[type=submit]')
-        .once('views-ajax').each(function(event) {
-        $(this).on('click', function () {
-          this.form.clk = this;
-          return true;
+        .once('views-ajax').each(function (event) {
+          $(this).on('click', function () {
+            this.form.clk = this;
+            return true;
+          });
+          var element_settings = base_element_settings;
+          // Set the URL to go to the anchor.
+          element_settings.url = $(this.form).attr('action');
+          if (Drupal.Views.getPath(element_settings.url).substring(0, 21) !== 'admin/structure/views') {
+            return true;
+          }
+
+          element_settings.wrapper = 'views-preview-wrapper';
+          element_settings.method = 'replaceWith';
+          element_settings.event = 'click';
+
+          var base = $(this).attr('id');
+          Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
         });
-        var element_settings = base_element_settings;
-        // Set the URL to go to the anchor.
-        element_settings.url = $(this.form).attr('action');
-        if (Drupal.Views.getPath(element_settings.url).substring(0, 21) !== 'admin/structure/views') {
-          return true;
-        }
-
-        element_settings.wrapper = 'views-preview-wrapper';
-        element_settings.method = 'replaceWith';
-        element_settings.event = 'click';
-
-        var base = $(this).attr('id');
-        Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
-      });
 
     }
   };
diff --git a/core/modules/views_ui/js/dialog.views.js b/core/modules/views_ui/js/dialog.views.js
index 818d2d5..ad68de7 100644
--- a/core/modules/views_ui/js/dialog.views.js
+++ b/core/modules/views_ui/js/dialog.views.js
@@ -2,7 +2,7 @@
 
   "use strict";
 
-  function handleDialogResize (e) {
+  function handleDialogResize(e) {
     var $modal = $(e.currentTarget);
     var $viewsOverride = $modal.find('[data-drupal-views-offset]');
     var $scroll = $modal.find('[data-drupal-views-scroll]');
diff --git a/core/modules/views_ui/js/views-admin.js b/core/modules/views_ui/js/views-admin.js
index bb54d50..34c1afb 100644
--- a/core/modules/views_ui/js/views-admin.js
+++ b/core/modules/views_ui/js/views-admin.js
@@ -4,923 +4,923 @@
  */
 (function ($, Drupal, drupalSettings) {
 
-"use strict";
+  "use strict";
 
-Drupal.viewsUi = {};
+  Drupal.viewsUi = {};
 
-/**
- * Improve the user experience of the views edit interface.
- */
-Drupal.behaviors.viewsUiEditView = {
-  attach: function () {
-    // Only show the SQL rewrite warning when the user has chosen the
-    // corresponding checkbox.
-    $('#edit-query-options-disable-sql-rewrite').on('click', function () {
-      $('.sql-rewrite-warning').toggleClass('js-hide');
-    });
-  }
-};
-
-/**
- * In the add view wizard, use the view name to prepopulate form fields such as
- * page title and menu link.
- */
-Drupal.behaviors.viewsUiAddView = {
-  attach: function (context) {
-    var $context = $(context);
-    // Set up regular expressions to allow only numbers, letters, and dashes.
-    var exclude = new RegExp('[^a-z0-9\\-]+', 'g');
-    var replace = '-';
-    var suffix;
-
-    // The page title, block title, and menu link fields can all be prepopulated
-    // with the view name - no regular expression needed.
-    var $fields = $context.find('[id^="edit-page-title"], [id^="edit-block-title"], [id^="edit-page-link-properties-title"]');
-    if ($fields.length) {
-      if (!this.fieldsFiller) {
-        this.fieldsFiller = new Drupal.viewsUi.FormFieldFiller($fields);
-      }
-      else {
-        // After an AJAX response, this.fieldsFiller will still have event
-        // handlers bound to the old version of the form fields (which don't exist
-        // anymore). The event handlers need to be unbound and then rebound to the
-        // new markup. Note that jQuery.live is difficult to make work in this
-        // case because the IDs of the form fields change on every AJAX response.
-        this.fieldsFiller.rebind($fields);
-      }
+  /**
+   * Improve the user experience of the views edit interface.
+   */
+  Drupal.behaviors.viewsUiEditView = {
+    attach: function () {
+      // Only show the SQL rewrite warning when the user has chosen the
+      // corresponding checkbox.
+      $('#edit-query-options-disable-sql-rewrite').on('click', function () {
+        $('.sql-rewrite-warning').toggleClass('js-hide');
+      });
     }
+  };
 
-    // Prepopulate the path field with a URLified version of the view name.
-    var $pathField = $context.find('[id^="edit-page-path"]');
-    if ($pathField.length) {
-      if (!this.pathFiller) {
-        this.pathFiller = new Drupal.viewsUi.FormFieldFiller($pathField, exclude, replace);
-      }
-      else {
-        this.pathFiller.rebind($pathField);
+  /**
+   * In the add view wizard, use the view name to prepopulate form fields such as
+   * page title and menu link.
+   */
+  Drupal.behaviors.viewsUiAddView = {
+    attach: function (context) {
+      var $context = $(context);
+      // Set up regular expressions to allow only numbers, letters, and dashes.
+      var exclude = new RegExp('[^a-z0-9\\-]+', 'g');
+      var replace = '-';
+      var suffix;
+
+      // The page title, block title, and menu link fields can all be prepopulated
+      // with the view name - no regular expression needed.
+      var $fields = $context.find('[id^="edit-page-title"], [id^="edit-block-title"], [id^="edit-page-link-properties-title"]');
+      if ($fields.length) {
+        if (!this.fieldsFiller) {
+          this.fieldsFiller = new Drupal.viewsUi.FormFieldFiller($fields);
+        }
+        else {
+          // After an AJAX response, this.fieldsFiller will still have event
+          // handlers bound to the old version of the form fields (which don't exist
+          // anymore). The event handlers need to be unbound and then rebound to the
+          // new markup. Note that jQuery.live is difficult to make work in this
+          // case because the IDs of the form fields change on every AJAX response.
+          this.fieldsFiller.rebind($fields);
+        }
       }
-    }
 
-    // Populate the RSS feed field with a URLified version of the view name, and
-    // an .xml suffix (to make it unique).
-    var $feedField = $context.find('[id^="edit-page-feed-properties-path"]');
-    if ($feedField.length) {
-      if (!this.feedFiller) {
-        suffix = '.xml';
-        this.feedFiller = new Drupal.viewsUi.FormFieldFiller($feedField, exclude, replace, suffix);
+      // Prepopulate the path field with a URLified version of the view name.
+      var $pathField = $context.find('[id^="edit-page-path"]');
+      if ($pathField.length) {
+        if (!this.pathFiller) {
+          this.pathFiller = new Drupal.viewsUi.FormFieldFiller($pathField, exclude, replace);
+        }
+        else {
+          this.pathFiller.rebind($pathField);
+        }
       }
-      else {
-        this.feedFiller.rebind($feedField);
+
+      // Populate the RSS feed field with a URLified version of the view name, and
+      // an .xml suffix (to make it unique).
+      var $feedField = $context.find('[id^="edit-page-feed-properties-path"]');
+      if ($feedField.length) {
+        if (!this.feedFiller) {
+          suffix = '.xml';
+          this.feedFiller = new Drupal.viewsUi.FormFieldFiller($feedField, exclude, replace, suffix);
+        }
+        else {
+          this.feedFiller.rebind($feedField);
+        }
       }
     }
-  }
-};
+  };
 
-/**
- * Constructor for the Drupal.viewsUi.FormFieldFiller object.
- *
- * Prepopulates a form field based on the view name.
- *
- * @param $target
- *   A jQuery object representing the form field to prepopulate.
- * @param exclude
- *   Optional. A regular expression representing characters to exclude from the
- *   target field.
- * @param replace
- *   Optional. A string to use as the replacement value for disallowed
- *   characters.
- * @param suffix
- *   Optional. A suffix to append at the end of the target field content.
- */
-Drupal.viewsUi.FormFieldFiller = function ($target, exclude, replace, suffix) {
-  this.source = $('#edit-label');
-  this.target = $target;
-  this.exclude = exclude || false;
-  this.replace = replace || '';
-  this.suffix = suffix || '';
-
-  // Create bound versions of this instance's object methods to use as event
-  // handlers. This will let us easily unbind those specific handlers later on.
-  // NOTE: jQuery.proxy will not work for this because it assumes we want only
-  // one bound version of an object method, whereas we need one version per
-  // object instance.
-  var self = this;
-  this.populate = function () {return self._populate.call(self);};
-  this.unbind = function () {return self._unbind.call(self);};
-
-  this.bind();
-  // Object constructor; no return value.
-};
-
-$.extend(Drupal.viewsUi.FormFieldFiller.prototype, {
   /**
-   * Bind the form-filling behavior.
+   * Constructor for the Drupal.viewsUi.FormFieldFiller object.
+   *
+   * Prepopulates a form field based on the view name.
+   *
+   * @param $target
+   *   A jQuery object representing the form field to prepopulate.
+   * @param exclude
+   *   Optional. A regular expression representing characters to exclude from the
+   *   target field.
+   * @param replace
+   *   Optional. A string to use as the replacement value for disallowed
+   *   characters.
+   * @param suffix
+   *   Optional. A suffix to append at the end of the target field content.
    */
-  bind: function () {
-    this.unbind();
-    // Populate the form field when the source changes.
-    this.source.on('keyup.viewsUi change.viewsUi', this.populate);
-    // Quit populating the field as soon as it gets focus.
-    this.target.on('focus.viewsUi', this.unbind);
-  },
+  Drupal.viewsUi.FormFieldFiller = function ($target, exclude, replace, suffix) {
+    this.source = $('#edit-label');
+    this.target = $target;
+    this.exclude = exclude || false;
+    this.replace = replace || '';
+    this.suffix = suffix || '';
+
+    // Create bound versions of this instance's object methods to use as event
+    // handlers. This will let us easily unbind those specific handlers later on.
+    // NOTE: jQuery.proxy will not work for this because it assumes we want only
+    // one bound version of an object method, whereas we need one version per
+    // object instance.
+    var self = this;
+    this.populate = function () { return self._populate.call(self); };
+    this.unbind = function () { return self._unbind.call(self); };
 
-  /**
-   * Get the source form field value as altered by the passed-in parameters.
-   */
-  getTransliterated: function () {
-    var from = this.source.val();
-    if (this.exclude) {
-      from = from.toLowerCase().replace(this.exclude, this.replace);
-    }
-    return from + this.suffix;
-  },
+    this.bind();
+    // Object constructor; no return value.
+  };
 
-  /**
-   * Populate the target form field with the altered source field value.
-   */
-  _populate: function () {
-    var transliterated = this.getTransliterated();
-    this.target.val(transliterated);
-  },
+  $.extend(Drupal.viewsUi.FormFieldFiller.prototype, {
+    /**
+     * Bind the form-filling behavior.
+     */
+    bind: function () {
+      this.unbind();
+      // Populate the form field when the source changes.
+      this.source.on('keyup.viewsUi change.viewsUi', this.populate);
+      // Quit populating the field as soon as it gets focus.
+      this.target.on('focus.viewsUi', this.unbind);
+    },
 
-  /**
-   * Stop prepopulating the form fields.
-   */
-  _unbind: function () {
-    this.source.off('keyup.viewsUi change.viewsUi', this.populate);
-    this.target.off('focus.viewsUi', this.unbind);
-  },
+    /**
+     * Get the source form field value as altered by the passed-in parameters.
+     */
+    getTransliterated: function () {
+      var from = this.source.val();
+      if (this.exclude) {
+        from = from.toLowerCase().replace(this.exclude, this.replace);
+      }
+      return from + this.suffix;
+    },
 
-  /**
-   * Bind event handlers to the new form fields, after they're replaced via AJAX.
-   */
-  rebind: function ($fields) {
-    this.target = $fields;
-    this.bind();
-  }});
+    /**
+     * Populate the target form field with the altered source field value.
+     */
+    _populate: function () {
+      var transliterated = this.getTransliterated();
+      this.target.val(transliterated);
+    },
 
+    /**
+     * Stop prepopulating the form fields.
+     */
+    _unbind: function () {
+      this.source.off('keyup.viewsUi change.viewsUi', this.populate);
+      this.target.off('focus.viewsUi', this.unbind);
+    },
 
-Drupal.behaviors.addItemForm = {
-  attach: function (context) {
-    var $context = $(context);
-    var $form = $context;
-    // The add handler form may have an id of views-ui-add-handler-form--n.
-    if (!$context.is('form[id^="views-ui-add-handler-form"]')) {
-      $form = $context.find('form[id^="views-ui-add-handler-form"]');
-    }
-    if ($form.once('views-ui-add-handler-form').length) {
-      // If we we have an unprocessed views-ui-add-handler-form, let's instantiate.
-      new Drupal.viewsUi.AddItemForm($form);
-    }
-  }
-};
-
-Drupal.viewsUi.AddItemForm = function ($form) {
-  this.$form = $form;
-  this.$form.find('.views-filterable-options :checkbox').on('click', $.proxy(this.handleCheck, this));
-  // Find the wrapper of the displayed text.
-  this.$selected_div = this.$form.find('.views-selected-options').parent();
-  this.$selected_div.hide();
-  this.checkedItems = [];
-};
-
-Drupal.viewsUi.AddItemForm.prototype.handleCheck = function (event) {
-  var $target = $(event.target);
-  var label = $.trim($target.next().text());
-  // Add/remove the checked item to the list.
-  if ($target.is(':checked')) {
-    this.$selected_div.show().css('display', 'block');
-    this.checkedItems.push(label);
-  }
-  else {
-    var position = $.inArray(label, this.checkedItems);
-    // Delete the item from the list and make sure that the list doesn't have undefined items left.
-    for (var i = 0; i < this.checkedItems.length; i++) {
-      if (i === position) {
-        this.checkedItems.splice(i, 1);
-        i--;
-        break;
+    /**
+     * Bind event handlers to the new form fields, after they're replaced via AJAX.
+     */
+    rebind: function ($fields) {
+      this.target = $fields;
+      this.bind();
+    }});
+
+
+  Drupal.behaviors.addItemForm = {
+    attach: function (context) {
+      var $context = $(context);
+      var $form = $context;
+      // The add handler form may have an id of views-ui-add-handler-form--n.
+      if (!$context.is('form[id^="views-ui-add-handler-form"]')) {
+        $form = $context.find('form[id^="views-ui-add-handler-form"]');
+      }
+      if ($form.once('views-ui-add-handler-form').length) {
+        // If we we have an unprocessed views-ui-add-handler-form, let's instantiate.
+        new Drupal.viewsUi.AddItemForm($form);
       }
     }
-    // Hide it again if none item is selected.
-    if (this.checkedItems.length === 0) {
-      this.$selected_div.hide();
+  };
+
+  Drupal.viewsUi.AddItemForm = function ($form) {
+    this.$form = $form;
+    this.$form.find('.views-filterable-options :checkbox').on('click', $.proxy(this.handleCheck, this));
+    // Find the wrapper of the displayed text.
+    this.$selected_div = this.$form.find('.views-selected-options').parent();
+    this.$selected_div.hide();
+    this.checkedItems = [];
+  };
+
+  Drupal.viewsUi.AddItemForm.prototype.handleCheck = function (event) {
+    var $target = $(event.target);
+    var label = $.trim($target.next().text());
+    // Add/remove the checked item to the list.
+    if ($target.is(':checked')) {
+      this.$selected_div.show().css('display', 'block');
+      this.checkedItems.push(label);
     }
-  }
-  this.refreshCheckedItems();
-};
-
-/**
- * Refresh the display of the checked items.
- */
-Drupal.viewsUi.AddItemForm.prototype.refreshCheckedItems = function () {
-  // Perhaps we should precache the text div, too.
-  this.$selected_div.find('.views-selected-options')
-    .html(this.checkedItems.join(', '))
-    .trigger('dialogContentResize');
-};
-
-/**
- * The input field items that add displays must be rendered as <input> elements.
- * The following behavior detaches the <input> elements from the DOM, wraps them
- * in an unordered list, then appends them to the list of tabs.
- */
-Drupal.behaviors.viewsUiRenderAddViewButton = {
-  attach: function (context) {
-    // Build the add display menu and pull the display input buttons into it.
-    var $menu = $(context).find('#views-display-menu-tabs').once('views-ui-render-add-view-button-processed');
-    if (!$menu.length) {
-      return;
+    else {
+      var position = $.inArray(label, this.checkedItems);
+      // Delete the item from the list and make sure that the list doesn't have undefined items left.
+      for (var i = 0; i < this.checkedItems.length; i++) {
+        if (i === position) {
+          this.checkedItems.splice(i, 1);
+          i--;
+          break;
+        }
+      }
+      // Hide it again if none item is selected.
+      if (this.checkedItems.length === 0) {
+        this.$selected_div.hide();
+      }
     }
+    this.refreshCheckedItems();
+  };
 
-    var $addDisplayDropdown = $('<li class="add"><a href="#"><span class="icon add"></span>' + Drupal.t('Add') + '</a><ul class="action-list" style="display:none;"></ul></li>');
-    var $displayButtons = $menu.nextAll('input.add-display').detach();
-    $displayButtons.appendTo($addDisplayDropdown.find('.action-list')).wrap('<li>')
-      .parent().first().addClass('first').end().last().addClass('last');
-    // Remove the 'Add ' prefix from the button labels since they're being palced
-    // in an 'Add' dropdown.
-    // @todo This assumes English, but so does $addDisplayDropdown above. Add
-    //   support for translation.
-    $displayButtons.each(function () {
-      var label = $(this).val();
-      if (label.substr(0, 4) === 'Add ') {
-        $(this).val(label.substr(4));
-      }
-    });
-    $addDisplayDropdown.appendTo($menu);
+  /**
+   * Refresh the display of the checked items.
+   */
+  Drupal.viewsUi.AddItemForm.prototype.refreshCheckedItems = function () {
+    // Perhaps we should precache the text div, too.
+    this.$selected_div.find('.views-selected-options')
+      .html(this.checkedItems.join(', '))
+      .trigger('dialogContentResize');
+  };
 
-    // Add the click handler for the add display button
-    $menu.find('li.add > a').on('click', function (event) {
-      event.preventDefault();
-      var $trigger = $(this);
-      Drupal.behaviors.viewsUiRenderAddViewButton.toggleMenu($trigger);
-    });
-    // Add a mouseleave handler to close the dropdown when the user mouses
-    // away from the item. We use mouseleave instead of mouseout because
-    // the user is going to trigger mouseout when she moves from the trigger
-    // link to the sub menu items.
-    // We use the live binder because the open class on this item will be
-    // toggled on and off and we want the handler to take effect in the cases
-    // that the class is present, but not when it isn't.
-    $('li.add', $menu).on('mouseleave', function (event) {
-      var $this = $(this);
-      var $trigger = $this.children('a[href="#"]');
-      if ($this.children('.action-list').is(':visible')) {
-        Drupal.behaviors.viewsUiRenderAddViewButton.toggleMenu($trigger);
+  /**
+   * The input field items that add displays must be rendered as <input> elements.
+   * The following behavior detaches the <input> elements from the DOM, wraps them
+   * in an unordered list, then appends them to the list of tabs.
+   */
+  Drupal.behaviors.viewsUiRenderAddViewButton = {
+    attach: function (context) {
+      // Build the add display menu and pull the display input buttons into it.
+      var $menu = $(context).find('#views-display-menu-tabs').once('views-ui-render-add-view-button-processed');
+      if (!$menu.length) {
+        return;
       }
-    });
-  }
-};
 
-/**
- * @note [@jessebeach] I feel like the following should be a more generic function and
- * not written specifically for this UI, but I'm not sure where to put it.
- */
-Drupal.behaviors.viewsUiRenderAddViewButton.toggleMenu = function ($trigger) {
-  $trigger.parent().toggleClass('open');
-  $trigger.next().slideToggle('fast');
-};
-
-Drupal.behaviors.viewsUiSearchOptions = {
-  attach: function (context) {
-    var $context = $(context);
-    var $form = $context;
-    // The add handler form may have an id of views-ui-add-handler-form--n.
-    if (!$context.is('form[id^="views-ui-add-handler-form"]')) {
-      $form = $context.find('form[id^="views-ui-add-handler-form"]');
-    }
-    // Make sure we don't add more than one event handler to the same form.
-    if ($form.once('views-ui-filter-options').length) {
-      new Drupal.viewsUi.OptionsSearch($form);
-    }
-  }
-};
+      var $addDisplayDropdown = $('<li class="add"><a href="#"><span class="icon add"></span>' + Drupal.t('Add') + '</a><ul class="action-list" style="display:none;"></ul></li>');
+      var $displayButtons = $menu.nextAll('input.add-display').detach();
+      $displayButtons.appendTo($addDisplayDropdown.find('.action-list')).wrap('<li>')
+        .parent().first().addClass('first').end().last().addClass('last');
+      // Remove the 'Add ' prefix from the button labels since they're being palced
+      // in an 'Add' dropdown.
+      // @todo This assumes English, but so does $addDisplayDropdown above. Add
+      //   support for translation.
+      $displayButtons.each(function () {
+        var label = $(this).val();
+        if (label.substr(0, 4) === 'Add ') {
+          $(this).val(label.substr(4));
+        }
+      });
+      $addDisplayDropdown.appendTo($menu);
 
-/**
- * Constructor for the viewsUi.OptionsSearch object.
- *
- * The OptionsSearch object filters the available options on a form according
- * to the user's search term. Typing in "taxonomy" will show only those options
- * containing "taxonomy" in their label.
- */
-Drupal.viewsUi.OptionsSearch = function ($form) {
-  this.$form = $form;
-  // Add a keyup handler to the search box.
-  this.$searchBox = this.$form.find('#edit-override-controls-options-search');
-  this.$searchBox.on('keyup', $.proxy(this.handleKeyup, this));
-  // Get a list of option labels and their corresponding divs and maintain it
-  // in memory, so we have as little overhead as possible at keyup time.
-  this.options = this.getOptions(this.$form.find('.filterable-option'));
-  // Restripe on initial loading.
-  this.handleKeyup();
-  // Trap the ENTER key in the search box so that it doesn't submit the form.
-  this.$searchBox.on('keypress', function (event) {
-    if (event.which === 13) {
-      event.preventDefault();
+      // Add the click handler for the add display button
+      $menu.find('li.add > a').on('click', function (event) {
+        event.preventDefault();
+        var $trigger = $(this);
+        Drupal.behaviors.viewsUiRenderAddViewButton.toggleMenu($trigger);
+      });
+      // Add a mouseleave handler to close the dropdown when the user mouses
+      // away from the item. We use mouseleave instead of mouseout because
+      // the user is going to trigger mouseout when she moves from the trigger
+      // link to the sub menu items.
+      // We use the live binder because the open class on this item will be
+      // toggled on and off and we want the handler to take effect in the cases
+      // that the class is present, but not when it isn't.
+      $('li.add', $menu).on('mouseleave', function (event) {
+        var $this = $(this);
+        var $trigger = $this.children('a[href="#"]');
+        if ($this.children('.action-list').is(':visible')) {
+          Drupal.behaviors.viewsUiRenderAddViewButton.toggleMenu($trigger);
+        }
+      });
     }
-  });
-};
+  };
 
-$.extend(Drupal.viewsUi.OptionsSearch.prototype, {
   /**
-   * Assemble a list of all the filterable options on the form.
-   *
-   * @param $allOptions
-   *   A $ object representing the rows of filterable options to be
-   *   shown and hidden depending on the user's search terms.
+   * @note [@jessebeach] I feel like the following should be a more generic function and
+   * not written specifically for this UI, but I'm not sure where to put it.
    */
-  getOptions: function ($allOptions) {
-    var i, $label, $description, $option;
-    var options = [];
-    var length = $allOptions.length;
-    for (i = 0; i < length; i++) {
-      $option = $($allOptions[i]);
-      $label = $option.find('label');
-      $description = $option.find('div.description');
-      options[i] = {
-        // Search on the lowercase version of the label text + description.
-        'searchText': $label.text().toLowerCase() + " " + $description.text().toLowerCase(),
-        // Maintain a reference to the jQuery object for each row, so we don't
-        // have to create a new object inside the performance-sensitive keyup
-        // handler.
-        '$div': $option
-      };
+  Drupal.behaviors.viewsUiRenderAddViewButton.toggleMenu = function ($trigger) {
+    $trigger.parent().toggleClass('open');
+    $trigger.next().slideToggle('fast');
+  };
+
+  Drupal.behaviors.viewsUiSearchOptions = {
+    attach: function (context) {
+      var $context = $(context);
+      var $form = $context;
+      // The add handler form may have an id of views-ui-add-handler-form--n.
+      if (!$context.is('form[id^="views-ui-add-handler-form"]')) {
+        $form = $context.find('form[id^="views-ui-add-handler-form"]');
+      }
+      // Make sure we don't add more than one event handler to the same form.
+      if ($form.once('views-ui-filter-options').length) {
+        new Drupal.viewsUi.OptionsSearch($form);
+      }
     }
-    return options;
-  },
+  };
 
   /**
-   * Keyup handler for the search box that hides or shows the relevant options.
+   * Constructor for the viewsUi.OptionsSearch object.
+   *
+   * The OptionsSearch object filters the available options on a form according
+   * to the user's search term. Typing in "taxonomy" will show only those options
+   * containing "taxonomy" in their label.
    */
-  handleKeyup: function (event) {
-    var found, i, j, option, search, words, wordsLength;
-
-    // Determine the user's search query. The search text has been converted to
-    // lowercase.
-    search = this.$searchBox.val().toLowerCase();
-    words = search.split(' ');
-    wordsLength = words.length;
-
-    // Search through the search texts in the form for matching text.
-    var length = this.options.length;
-    for (i = 0; i < length; i++) {
-      // Use a local variable for the option being searched, for performance.
-      option = this.options[i];
-      found = true;
-      // Each word in the search string has to match the item in order for the
-      // item to be shown.
-      for (j = 0; j < wordsLength; j++) {
-        if (option.searchText.indexOf(words[j]) === -1) {
-          found = false;
+  Drupal.viewsUi.OptionsSearch = function ($form) {
+    this.$form = $form;
+    // Add a keyup handler to the search box.
+    this.$searchBox = this.$form.find('#edit-override-controls-options-search');
+    this.$searchBox.on('keyup', $.proxy(this.handleKeyup, this));
+    // Get a list of option labels and their corresponding divs and maintain it
+    // in memory, so we have as little overhead as possible at keyup time.
+    this.options = this.getOptions(this.$form.find('.filterable-option'));
+    // Restripe on initial loading.
+    this.handleKeyup();
+    // Trap the ENTER key in the search box so that it doesn't submit the form.
+    this.$searchBox.on('keypress', function (event) {
+      if (event.which === 13) {
+        event.preventDefault();
+      }
+    });
+  };
+
+  $.extend(Drupal.viewsUi.OptionsSearch.prototype, {
+    /**
+     * Assemble a list of all the filterable options on the form.
+     *
+     * @param $allOptions
+     *   A $ object representing the rows of filterable options to be
+     *   shown and hidden depending on the user's search terms.
+     */
+    getOptions: function ($allOptions) {
+      var i, $label, $description, $option;
+      var options = [];
+      var length = $allOptions.length;
+      for (i = 0; i < length; i++) {
+        $option = $($allOptions[i]);
+        $label = $option.find('label');
+        $description = $option.find('div.description');
+        options[i] = {
+          // Search on the lowercase version of the label text + description.
+          'searchText': $label.text().toLowerCase() + " " + $description.text().toLowerCase(),
+          // Maintain a reference to the jQuery object for each row, so we don't
+          // have to create a new object inside the performance-sensitive keyup
+          // handler.
+          '$div': $option
+        };
+      }
+      return options;
+    },
+
+    /**
+     * Keyup handler for the search box that hides or shows the relevant options.
+     */
+    handleKeyup: function (event) {
+      var found, i, j, option, search, words, wordsLength;
+
+      // Determine the user's search query. The search text has been converted to
+      // lowercase.
+      search = this.$searchBox.val().toLowerCase();
+      words = search.split(' ');
+      wordsLength = words.length;
+
+      // Search through the search texts in the form for matching text.
+      var length = this.options.length;
+      for (i = 0; i < length; i++) {
+        // Use a local variable for the option being searched, for performance.
+        option = this.options[i];
+        found = true;
+        // Each word in the search string has to match the item in order for the
+        // item to be shown.
+        for (j = 0; j < wordsLength; j++) {
+          if (option.searchText.indexOf(words[j]) === -1) {
+            found = false;
+          }
         }
+        if (found) {
+          // Show the checkbox row, and restripe it.
+          option.$div.show();
+          option.$div.removeClass('even odd');
+        }
+        else {
+          // The search string wasn't found; hide this item.
+          option.$div.hide();
+        }
+      }
+    }
+  });
+
+  Drupal.behaviors.viewsUiPreview = {
+    attach: function (context) {
+      // Only act on the edit view form.
+      var $contextualFiltersBucket = $(context).find('.views-display-column .views-ui-display-tab-bucket.contextual-filters');
+      if ($contextualFiltersBucket.length === 0) {
+        return;
       }
-      if (found) {
-        // Show the checkbox row, and restripe it.
-        option.$div.show();
-        option.$div.removeClass('even odd');
+
+      // If the display has no contextual filters, hide the form where you enter
+      // the contextual filters for the live preview. If it has contextual filters,
+      // show the form.
+      var $contextualFilters = $contextualFiltersBucket.find('.views-display-setting a');
+      if ($contextualFilters.length) {
+        $('#preview-args').parent().show();
       }
       else {
-        // The search string wasn't found; hide this item.
-        option.$div.hide();
+        $('#preview-args').parent().hide();
       }
-    }
-  }
-});
-
-Drupal.behaviors.viewsUiPreview = {
-  attach: function (context) {
-    // Only act on the edit view form.
-    var $contextualFiltersBucket = $(context).find('.views-display-column .views-ui-display-tab-bucket.contextual-filters');
-    if ($contextualFiltersBucket.length === 0) {
-      return;
-    }
 
-    // If the display has no contextual filters, hide the form where you enter
-    // the contextual filters for the live preview. If it has contextual filters,
-    // show the form.
-    var $contextualFilters = $contextualFiltersBucket.find('.views-display-setting a');
-    if ($contextualFilters.length) {
-      $('#preview-args').parent().show();
-    }
-    else {
-      $('#preview-args').parent().hide();
+      // Executes an initial preview.
+      if ($('#edit-displays-live-preview').once('edit-displays-live-preview').is(':checked')) {
+        $('#preview-submit').once('edit-displays-live-preview').trigger('click');
+      }
     }
+  };
 
-    // Executes an initial preview.
-    if ($('#edit-displays-live-preview').once('edit-displays-live-preview').is(':checked')) {
-      $('#preview-submit').once('edit-displays-live-preview').trigger('click');
-    }
-  }
-};
-
-Drupal.behaviors.viewsUiRearrangeFilter = {
-  attach: function (context) {
-    // Only act on the rearrange filter form.
-    if (typeof Drupal.tableDrag === 'undefined' || typeof Drupal.tableDrag['views-rearrange-filters'] === 'undefined') {
-      return;
-    }
-    var $context = $(context);
-    var $table = $context.find('#views-rearrange-filters').once('views-rearrange-filters');
-    var $operator = $context.find('.form-item-filter-groups-operator').once('views-rearrange-filters');
-    if ($table.length) {
-      new Drupal.viewsUi.RearrangeFilterHandler($table, $operator);
+  Drupal.behaviors.viewsUiRearrangeFilter = {
+    attach: function (context) {
+      // Only act on the rearrange filter form.
+      if (typeof Drupal.tableDrag === 'undefined' || typeof Drupal.tableDrag['views-rearrange-filters'] === 'undefined') {
+        return;
+      }
+      var $context = $(context);
+      var $table = $context.find('#views-rearrange-filters').once('views-rearrange-filters');
+      var $operator = $context.find('.form-item-filter-groups-operator').once('views-rearrange-filters');
+      if ($table.length) {
+        new Drupal.viewsUi.RearrangeFilterHandler($table, $operator);
+      }
     }
-  }
-};
+  };
 
-/**
- * Improve the UI of the rearrange filters dialog box.
- */
-Drupal.viewsUi.RearrangeFilterHandler = function ($table, $operator) {
-  // Keep a reference to the <table> being altered and to the div containing
-  // the filter groups operator dropdown (if it exists).
-  this.table = $table;
-  this.operator = $operator;
-  this.hasGroupOperator = this.operator.length > 0;
-
-  // Keep a reference to all draggable rows within the table.
-  this.draggableRows = $table.find('.draggable');
-
-  // Keep a reference to the buttons for adding and removing filter groups.
-  this.addGroupButton = $('input#views-add-group');
-  this.removeGroupButtons = $table.find('input.views-remove-group');
-
-  // Add links that duplicate the functionality of the (hidden) add and remove
-  // buttons.
-  this.insertAddRemoveFilterGroupLinks();
-
-  // When there is a filter groups operator dropdown on the page, create
-  // duplicates of the dropdown between each pair of filter groups.
-  if (this.hasGroupOperator) {
-    this.dropdowns = this.duplicateGroupsOperator();
-    this.syncGroupsOperators();
-  }
-
-  // Add methods to the tableDrag instance to account for operator cells (which
-  // span multiple rows), the operator labels next to each filter (e.g., "And"
-  // or "Or"), the filter groups, and other special aspects of this tableDrag
-  // instance.
-  this.modifyTableDrag();
-
-  // Initialize the operator labels (e.g., "And" or "Or") that are displayed
-  // next to the filters in each group, and bind a handler so that they change
-  // based on the values of the operator dropdown within that group.
-  this.redrawOperatorLabels();
-  $table.find('.views-group-title select')
-    .once('views-rearrange-filter-handler')
-    .on('change.views-rearrange-filter-handler', $.proxy(this, 'redrawOperatorLabels'));
-
-  // Bind handlers so that when a "Remove" link is clicked, we:
-  // - Update the rowspans of cells containing an operator dropdown (since they
-  //   need to change to reflect the number of rows in each group).
-  // - Redraw the operator labels next to the filters in the group (since the
-  //   filter that is currently displayed last in each group is not supposed to
-  //   have a label display next to it).
-  $table.find('a.views-groups-remove-link')
-    .once('views-rearrange-filter-handler')
-    .on('click.views-rearrange-filter-handler', $.proxy(this, 'updateRowspans'))
-    .on('click.views-rearrange-filter-handler', $.proxy(this, 'redrawOperatorLabels'));
-};
-
-$.extend(Drupal.viewsUi.RearrangeFilterHandler.prototype, {
   /**
-   * Insert links that allow filter groups to be added and removed.
+   * Improve the UI of the rearrange filters dialog box.
    */
-  insertAddRemoveFilterGroupLinks: function () {
-
-    // Insert a link for adding a new group at the top of the page, and make it
-    // match the action link styling used in a typical page.html.twig. Since
-    // Drupal does not provide a theme function for this markup this is the best
-    // we can do.
-    $('<ul class="action-links"><li><a id="views-add-group-link" href="#">' + this.addGroupButton.val() + '</a></li></ul>')
-      .prependTo(this.table.parent())
-      // When the link is clicked, dynamically click the hidden form button for
-      // adding a new filter group.
+  Drupal.viewsUi.RearrangeFilterHandler = function ($table, $operator) {
+    // Keep a reference to the <table> being altered and to the div containing
+    // the filter groups operator dropdown (if it exists).
+    this.table = $table;
+    this.operator = $operator;
+    this.hasGroupOperator = this.operator.length > 0;
+
+    // Keep a reference to all draggable rows within the table.
+    this.draggableRows = $table.find('.draggable');
+
+    // Keep a reference to the buttons for adding and removing filter groups.
+    this.addGroupButton = $('input#views-add-group');
+    this.removeGroupButtons = $table.find('input.views-remove-group');
+
+    // Add links that duplicate the functionality of the (hidden) add and remove
+    // buttons.
+    this.insertAddRemoveFilterGroupLinks();
+
+    // When there is a filter groups operator dropdown on the page, create
+    // duplicates of the dropdown between each pair of filter groups.
+    if (this.hasGroupOperator) {
+      this.dropdowns = this.duplicateGroupsOperator();
+      this.syncGroupsOperators();
+    }
+
+    // Add methods to the tableDrag instance to account for operator cells (which
+    // span multiple rows), the operator labels next to each filter (e.g., "And"
+    // or "Or"), the filter groups, and other special aspects of this tableDrag
+    // instance.
+    this.modifyTableDrag();
+
+    // Initialize the operator labels (e.g., "And" or "Or") that are displayed
+    // next to the filters in each group, and bind a handler so that they change
+    // based on the values of the operator dropdown within that group.
+    this.redrawOperatorLabels();
+    $table.find('.views-group-title select')
+      .once('views-rearrange-filter-handler')
+      .on('change.views-rearrange-filter-handler', $.proxy(this, 'redrawOperatorLabels'));
+
+    // Bind handlers so that when a "Remove" link is clicked, we:
+    // - Update the rowspans of cells containing an operator dropdown (since they
+    //   need to change to reflect the number of rows in each group).
+    // - Redraw the operator labels next to the filters in the group (since the
+    //   filter that is currently displayed last in each group is not supposed to
+    //   have a label display next to it).
+    $table.find('a.views-groups-remove-link')
       .once('views-rearrange-filter-handler')
-      .on('click.views-rearrange-filter-handler', $.proxy(this, 'clickAddGroupButton'));
-
-    // Find each (visually hidden) button for removing a filter group and insert
-    // a link next to it.
-    var length = this.removeGroupButtons.length;
-    var i;
-    for (i = 0; i < length; i++) {
-      var $removeGroupButton = $(this.removeGroupButtons[i]);
-      var buttonId = $removeGroupButton.attr('id');
-      $('<a href="#" class="views-remove-group-link">' + Drupal.t('Remove group') + '</a>')
-        .insertBefore($removeGroupButton)
-        // When the link is clicked, dynamically click the corresponding form
-        // button.
+      .on('click.views-rearrange-filter-handler', $.proxy(this, 'updateRowspans'))
+      .on('click.views-rearrange-filter-handler', $.proxy(this, 'redrawOperatorLabels'));
+  };
+
+  $.extend(Drupal.viewsUi.RearrangeFilterHandler.prototype, {
+    /**
+     * Insert links that allow filter groups to be added and removed.
+     */
+    insertAddRemoveFilterGroupLinks: function () {
+
+      // Insert a link for adding a new group at the top of the page, and make it
+      // match the action link styling used in a typical page.html.twig. Since
+      // Drupal does not provide a theme function for this markup this is the best
+      // we can do.
+      $('<ul class="action-links"><li><a id="views-add-group-link" href="#">' + this.addGroupButton.val() + '</a></li></ul>')
+        .prependTo(this.table.parent())
+        // When the link is clicked, dynamically click the hidden form button for
+        // adding a new filter group.
         .once('views-rearrange-filter-handler')
-        .on('click.views-rearrange-filter-handler', { buttonId: buttonId }, $.proxy(this, 'clickRemoveGroupButton'));
-    }
-  },
+        .on('click.views-rearrange-filter-handler', $.proxy(this, 'clickAddGroupButton'));
+
+      // Find each (visually hidden) button for removing a filter group and insert
+      // a link next to it.
+      var length = this.removeGroupButtons.length;
+      var i;
+      for (i = 0; i < length; i++) {
+        var $removeGroupButton = $(this.removeGroupButtons[i]);
+        var buttonId = $removeGroupButton.attr('id');
+        $('<a href="#" class="views-remove-group-link">' + Drupal.t('Remove group') + '</a>')
+          .insertBefore($removeGroupButton)
+          // When the link is clicked, dynamically click the corresponding form
+          // button.
+          .once('views-rearrange-filter-handler')
+          .on('click.views-rearrange-filter-handler', { buttonId: buttonId }, $.proxy(this, 'clickRemoveGroupButton'));
+      }
+    },
 
-  /**
-   * Dynamically click the button that adds a new filter group.
-   */
-  clickAddGroupButton: function () {
-    // Due to conflicts between Drupal core's AJAX system and the Views AJAX
-    // system, the only way to get this to work seems to be to trigger both the
-    // mousedown and submit events.
-    this.addGroupButton
-      .trigger('mousedown')
-      .trigger('submit');
-    event.preventDefault();
-  },
+    /**
+     * Dynamically click the button that adds a new filter group.
+     */
+    clickAddGroupButton: function () {
+      // Due to conflicts between Drupal core's AJAX system and the Views AJAX
+      // system, the only way to get this to work seems to be to trigger both the
+      // mousedown and submit events.
+      this.addGroupButton
+        .trigger('mousedown')
+        .trigger('submit');
+      event.preventDefault();
+    },
 
-  /**
-   * Dynamically click a button for removing a filter group.
-   *
-   * @param event
-   *   Event being triggered, with event.data.buttonId set to the ID of the
-   *   form button that should be clicked.
-   */
-  clickRemoveGroupButton: function (event) {
-    // For some reason, here we only need to trigger .submit(), unlike for
-    // Drupal.viewsUi.RearrangeFilterHandler.prototype.clickAddGroupButton()
-    // where we had to trigger .mousedown() also.
-    this.table.find('#' + event.data.buttonId).trigger('submit');
-    event.preventDefault();
-  },
+    /**
+     * Dynamically click a button for removing a filter group.
+     *
+     * @param event
+     *   Event being triggered, with event.data.buttonId set to the ID of the
+     *   form button that should be clicked.
+     */
+    clickRemoveGroupButton: function (event) {
+      // For some reason, here we only need to trigger .submit(), unlike for
+      // Drupal.viewsUi.RearrangeFilterHandler.prototype.clickAddGroupButton()
+      // where we had to trigger .mousedown() also.
+      this.table.find('#' + event.data.buttonId).trigger('submit');
+      event.preventDefault();
+    },
 
-  /**
-   * Move the groups operator so that it's between the first two groups, and
-   * duplicate it between any subsequent groups.
-   */
-  duplicateGroupsOperator: function () {
-    var dropdowns, newRow, titleRow;
-
-    var titleRows = $('tr.views-group-title');
-
-    // Get rid of the explanatory text around the operator; its placement is
-    // explanatory enough.
-    this.operator.find('label').add('div.description').addClass('element-invisible');
-    this.operator.find('select').addClass('form-select');
-
-    // Keep a list of the operator dropdowns, so we can sync their behavior later.
-    dropdowns = this.operator;
-
-    // Move the operator to a new row just above the second group.
-    titleRow = $('tr#views-group-title-2');
-  this.operator.find('label').add('div.description').addClass('visually-hidden');
-  this.operator.find('select').addClass('form-select');
-
-  // Keep a list of the operator dropdowns, so we can sync their behavior later.
-  dropdowns = this.operator;
-
-  // Move the operator to a new row just above the second group.
-  titleRow = $('tr#views-group-title-2');
-  newRow = $('<tr class="filter-group-operator-row"><td colspan="5"></td></tr>');
-  newRow.find('td').append(this.operator);
-  newRow.insertBefore(titleRow);
-  var i, length = titleRows.length;
-  // Starting with the third group, copy the operator to a new row above the
-  // group title.
-  for (i = 2; i < length; i++) {
-    titleRow = $(titleRows[i]);
-    // Make a copy of the operator dropdown and put it in a new table row.
-    var fakeOperator = this.operator.clone();
-    fakeOperator.attr('id', '');
-    newRow = $('<tr class="filter-group-operator-row"><td colspan="5"></td></tr>');
-      newRow.find('td').append(fakeOperator);
-      newRow.insertBefore(titleRow);
-      dropdowns = dropdowns.add(fakeOperator);
-    }
+    /**
+     * Move the groups operator so that it's between the first two groups, and
+     * duplicate it between any subsequent groups.
+     */
+    duplicateGroupsOperator: function () {
+      var dropdowns, newRow, titleRow;
 
-    return dropdowns;
-  },
+      var titleRows = $('tr.views-group-title');
 
-  /**
-   * Make the duplicated groups operators change in sync with each other.
-   */
-  syncGroupsOperators: function () {
-    if (this.dropdowns.length < 2) {
-      // We only have one dropdown (or none at all), so there's nothing to sync.
-      return;
-    }
+      // Get rid of the explanatory text around the operator; its placement is
+      // explanatory enough.
+      this.operator.find('label').add('div.description').addClass('element-invisible');
+      this.operator.find('select').addClass('form-select');
 
-    this.dropdowns.on('change', $.proxy(this, 'operatorChangeHandler'));
-  },
+      // Keep a list of the operator dropdowns, so we can sync their behavior later.
+      dropdowns = this.operator;
 
-  /**
-   * Click handler for the operators that appear between filter groups.
-   *
-   * Forces all operator dropdowns to have the same value.
-   */
-  operatorChangeHandler: function (event) {
-    var $target = $(event.target);
-    var operators = this.dropdowns.find('select').not($target);
+      // Move the operator to a new row just above the second group.
+      titleRow = $('tr#views-group-title-2');
+      this.operator.find('label').add('div.description').addClass('visually-hidden');
+      this.operator.find('select').addClass('form-select');
 
-    // Change the other operators to match this new value.
-    operators.val($target.val());
-  },
+      // Keep a list of the operator dropdowns, so we can sync their behavior later.
+      dropdowns = this.operator;
 
-  modifyTableDrag: function () {
-    var tableDrag = Drupal.tableDrag['views-rearrange-filters'];
-    var filterHandler = this;
+      // Move the operator to a new row just above the second group.
+      titleRow = $('tr#views-group-title-2');
+      newRow = $('<tr class="filter-group-operator-row"><td colspan="5"></td></tr>');
+      newRow.find('td').append(this.operator);
+      newRow.insertBefore(titleRow);
+      var i, length = titleRows.length;
+      // Starting with the third group, copy the operator to a new row above the
+      // group title.
+      for (i = 2; i < length; i++) {
+        titleRow = $(titleRows[i]);
+        // Make a copy of the operator dropdown and put it in a new table row.
+        var fakeOperator = this.operator.clone();
+        fakeOperator.attr('id', '');
+        newRow = $('<tr class="filter-group-operator-row"><td colspan="5"></td></tr>');
+        newRow.find('td').append(fakeOperator);
+        newRow.insertBefore(titleRow);
+        dropdowns = dropdowns.add(fakeOperator);
+      }
+
+      return dropdowns;
+    },
 
     /**
-     * Override the row.onSwap method from tabledrag.js.
-     *
-     * When a row is dragged to another place in the table, several things need
-     * to occur.
-     * - The row needs to be moved so that it's within one of the filter groups.
-     * - The operator cells that span multiple rows need their rowspan attributes
-     *   updated to reflect the number of rows in each group.
-     * - The operator labels that are displayed next to each filter need to be
-     *   redrawn, to account for the row's new location.
+     * Make the duplicated groups operators change in sync with each other.
      */
-    tableDrag.row.prototype.onSwap = function () {
-      if (filterHandler.hasGroupOperator) {
-        // Make sure the row that just got moved (this.group) is inside one of
-        // the filter groups (i.e. below an empty marker row or a draggable). If
-        // it isn't, move it down one.
-        var thisRow = $(this.group);
-        var previousRow = thisRow.prev('tr');
-        if (previousRow.length && !previousRow.hasClass('group-message') && !previousRow.hasClass('draggable')) {
-          // Move the dragged row down one.
-          var next = thisRow.next();
-          if (next.is('tr')) {
-            this.swap('after', next);
-          }
-        }
-        filterHandler.updateRowspans();
+    syncGroupsOperators: function () {
+      if (this.dropdowns.length < 2) {
+        // We only have one dropdown (or none at all), so there's nothing to sync.
+        return;
       }
-      // Redraw the operator labels that are displayed next to each filter, to
-      // account for the row's new location.
-      filterHandler.redrawOperatorLabels();
-    };
+
+      this.dropdowns.on('change', $.proxy(this, 'operatorChangeHandler'));
+    },
 
     /**
-     * Override the onDrop method from tabledrag.js.
+     * Click handler for the operators that appear between filter groups.
+     *
+     * Forces all operator dropdowns to have the same value.
      */
-    tableDrag.onDrop = function () {
-      // If the tabledrag change marker (i.e., the "*") has been inserted inside
-      // a row after the operator label (i.e., "And" or "Or") rearrange the items
-      // so the operator label continues to appear last.
-      var changeMarker = $(this.oldRowElement).find('.tabledrag-changed');
-      if (changeMarker.length) {
-        // Search for occurrences of the operator label before the change marker,
-        // and reverse them.
-        var operatorLabel = changeMarker.prevAll('.views-operator-label');
-        if (operatorLabel.length) {
-          operatorLabel.insertAfter(changeMarker);
+    operatorChangeHandler: function (event) {
+      var $target = $(event.target);
+      var operators = this.dropdowns.find('select').not($target);
+
+      // Change the other operators to match this new value.
+      operators.val($target.val());
+    },
+
+    modifyTableDrag: function () {
+      var tableDrag = Drupal.tableDrag['views-rearrange-filters'];
+      var filterHandler = this;
+
+      /**
+       * Override the row.onSwap method from tabledrag.js.
+       *
+       * When a row is dragged to another place in the table, several things need
+       * to occur.
+       * - The row needs to be moved so that it's within one of the filter groups.
+       * - The operator cells that span multiple rows need their rowspan attributes
+       *   updated to reflect the number of rows in each group.
+       * - The operator labels that are displayed next to each filter need to be
+       *   redrawn, to account for the row's new location.
+       */
+      tableDrag.row.prototype.onSwap = function () {
+        if (filterHandler.hasGroupOperator) {
+          // Make sure the row that just got moved (this.group) is inside one of
+          // the filter groups (i.e. below an empty marker row or a draggable). If
+          // it isn't, move it down one.
+          var thisRow = $(this.group);
+          var previousRow = thisRow.prev('tr');
+          if (previousRow.length && !previousRow.hasClass('group-message') && !previousRow.hasClass('draggable')) {
+            // Move the dragged row down one.
+            var next = thisRow.next();
+            if (next.is('tr')) {
+              this.swap('after', next);
+            }
+          }
+          filterHandler.updateRowspans();
         }
-      }
+        // Redraw the operator labels that are displayed next to each filter, to
+        // account for the row's new location.
+        filterHandler.redrawOperatorLabels();
+      };
 
-      // Make sure the "group" dropdown is properly updated when rows are dragged
-      // into an empty filter group. This is borrowed heavily from the block.js
-      // implementation of tableDrag.onDrop().
-      var groupRow = $(this.rowObject.element).prevAll('tr.group-message').get(0);
-      var groupName = groupRow.className.replace(/([^ ]+[ ]+)*group-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
-      var groupField = $('select.views-group-select', this.rowObject.element);
-      if ($(this.rowObject.element).prev('tr').is('.group-message') && !groupField.is('.views-group-select-' + groupName)) {
-        var oldGroupName = groupField.attr('class').replace(/([^ ]+[ ]+)*views-group-select-([^ ]+)([ ]+[^ ]+)*/, '$2');
-        groupField.removeClass('views-group-select-' + oldGroupName).addClass('views-group-select-' + groupName);
-        groupField.val(groupName);
-      }
-    };
-  },
+      /**
+       * Override the onDrop method from tabledrag.js.
+       */
+      tableDrag.onDrop = function () {
+        // If the tabledrag change marker (i.e., the "*") has been inserted inside
+        // a row after the operator label (i.e., "And" or "Or") rearrange the items
+        // so the operator label continues to appear last.
+        var changeMarker = $(this.oldRowElement).find('.tabledrag-changed');
+        if (changeMarker.length) {
+          // Search for occurrences of the operator label before the change marker,
+          // and reverse them.
+          var operatorLabel = changeMarker.prevAll('.views-operator-label');
+          if (operatorLabel.length) {
+            operatorLabel.insertAfter(changeMarker);
+          }
+        }
 
-  /**
-   * Redraw the operator labels that are displayed next to each filter.
-   */
-  redrawOperatorLabels: function () {
-    for (var i = 0; i < this.draggableRows.length; i++) {
-      // Within the row, the operator labels are displayed inside the first table
-      // cell (next to the filter name).
-      var $draggableRow = $(this.draggableRows[i]);
-      var $firstCell = $draggableRow.find('td:first');
-      if ($firstCell.length) {
-        // The value of the operator label ("And" or "Or") is taken from the
-        // first operator dropdown we encounter, going backwards from the current
-        // row. This dropdown is the one associated with the current row's filter
-        // group.
-        var operatorValue = $draggableRow.prevAll('.views-group-title').find('option:selected').html();
-        var operatorLabel = '<span class="views-operator-label">' + operatorValue + '</span>';
-        // If the next visible row after this one is a draggable filter row,
-        // display the operator label next to the current row. (Checking for
-        // visibility is necessary here since the "Remove" links hide the removed
-        // row but don't actually remove it from the document).
-        var $nextRow = $draggableRow.nextAll(':visible').eq(0);
-        var $existingOperatorLabel = $firstCell.find('.views-operator-label');
-        if ($nextRow.hasClass('draggable')) {
-          // If an operator label was already there, replace it with the new one.
-          if ($existingOperatorLabel.length) {
-            $existingOperatorLabel.replaceWith(operatorLabel);
+        // Make sure the "group" dropdown is properly updated when rows are dragged
+        // into an empty filter group. This is borrowed heavily from the block.js
+        // implementation of tableDrag.onDrop().
+        var groupRow = $(this.rowObject.element).prevAll('tr.group-message').get(0);
+        var groupName = groupRow.className.replace(/([^ ]+[ ]+)*group-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
+        var groupField = $('select.views-group-select', this.rowObject.element);
+        if ($(this.rowObject.element).prev('tr').is('.group-message') && !groupField.is('.views-group-select-' + groupName)) {
+          var oldGroupName = groupField.attr('class').replace(/([^ ]+[ ]+)*views-group-select-([^ ]+)([ ]+[^ ]+)*/, '$2');
+          groupField.removeClass('views-group-select-' + oldGroupName).addClass('views-group-select-' + groupName);
+          groupField.val(groupName);
+        }
+      };
+    },
+
+    /**
+     * Redraw the operator labels that are displayed next to each filter.
+     */
+    redrawOperatorLabels: function () {
+      for (var i = 0; i < this.draggableRows.length; i++) {
+        // Within the row, the operator labels are displayed inside the first table
+        // cell (next to the filter name).
+        var $draggableRow = $(this.draggableRows[i]);
+        var $firstCell = $draggableRow.find('td:first');
+        if ($firstCell.length) {
+          // The value of the operator label ("And" or "Or") is taken from the
+          // first operator dropdown we encounter, going backwards from the current
+          // row. This dropdown is the one associated with the current row's filter
+          // group.
+          var operatorValue = $draggableRow.prevAll('.views-group-title').find('option:selected').html();
+          var operatorLabel = '<span class="views-operator-label">' + operatorValue + '</span>';
+          // If the next visible row after this one is a draggable filter row,
+          // display the operator label next to the current row. (Checking for
+          // visibility is necessary here since the "Remove" links hide the removed
+          // row but don't actually remove it from the document).
+          var $nextRow = $draggableRow.nextAll(':visible').eq(0);
+          var $existingOperatorLabel = $firstCell.find('.views-operator-label');
+          if ($nextRow.hasClass('draggable')) {
+            // If an operator label was already there, replace it with the new one.
+            if ($existingOperatorLabel.length) {
+              $existingOperatorLabel.replaceWith(operatorLabel);
+            }
+            // Otherwise, append the operator label to the end of the table cell.
+            else {
+              $firstCell.append(operatorLabel);
+            }
           }
-          // Otherwise, append the operator label to the end of the table cell.
+          // If the next row doesn't contain a filter, then this is the last row
+          // in the group. We don't want to display the operator there (since
+          // operators should only display between two related filters, e.g.
+          // "filter1 AND filter2 AND filter3"). So we remove any existing label
+          // that this row has.
           else {
-            $firstCell.append(operatorLabel);
+            $existingOperatorLabel.remove();
           }
         }
-        // If the next row doesn't contain a filter, then this is the last row
-        // in the group. We don't want to display the operator there (since
-        // operators should only display between two related filters, e.g.
-        // "filter1 AND filter2 AND filter3"). So we remove any existing label
-        // that this row has.
-        else {
-          $existingOperatorLabel.remove();
+      }
+    },
+
+    /**
+     * Update the rowspan attribute of each cell containing an operator dropdown.
+     */
+    updateRowspans: function () {
+      var i, $row, $currentEmptyRow, draggableCount, $operatorCell;
+      var rows = $(this.table).find('tr');
+      var length = rows.length;
+      for (i = 0; i < length; i++) {
+        $row = $(rows[i]);
+        if ($row.hasClass('views-group-title')) {
+          // This row is a title row.
+          // Keep a reference to the cell containing the dropdown operator.
+          $operatorCell = $row.find('td.group-operator');
+          // Assume this filter group is empty, until we find otherwise.
+          draggableCount = 0;
+          $currentEmptyRow = $row.next('tr');
+          $currentEmptyRow.removeClass('group-populated').addClass('group-empty');
+          // The cell with the dropdown operator should span the title row and
+          // the "this group is empty" row.
+          $operatorCell.attr('rowspan', 2);
+        }
+        else if ($row.hasClass('draggable') && $row.is(':visible')) {
+          // We've found a visible filter row, so we now know the group isn't empty.
+          draggableCount++;
+          $currentEmptyRow.removeClass('group-empty').addClass('group-populated');
+          // The operator cell should span all draggable rows, plus the title.
+          $operatorCell.attr('rowspan', draggableCount + 1);
         }
       }
-    }
-  },
+    }});
+
 
   /**
-   * Update the rowspan attribute of each cell containing an operator dropdown.
+   * Add a select all checkbox, which checks each checkbox at once.
    */
-  updateRowspans: function () {
-    var i, $row, $currentEmptyRow, draggableCount, $operatorCell;
-    var rows = $(this.table).find('tr');
-    var length = rows.length;
-    for (i = 0; i < length; i++) {
-      $row = $(rows[i]);
-      if ($row.hasClass('views-group-title')) {
-        // This row is a title row.
-        // Keep a reference to the cell containing the dropdown operator.
-        $operatorCell = $row.find('td.group-operator');
-        // Assume this filter group is empty, until we find otherwise.
-        draggableCount = 0;
-        $currentEmptyRow = $row.next('tr');
-        $currentEmptyRow.removeClass('group-populated').addClass('group-empty');
-        // The cell with the dropdown operator should span the title row and
-        // the "this group is empty" row.
-        $operatorCell.attr('rowspan', 2);
-      }
-      else if ($row.hasClass('draggable') && $row.is(':visible')) {
-        // We've found a visible filter row, so we now know the group isn't empty.
-        draggableCount++;
-        $currentEmptyRow.removeClass('group-empty').addClass('group-populated');
-        // The operator cell should span all draggable rows, plus the title.
-        $operatorCell.attr('rowspan', draggableCount + 1);
-      }
+  Drupal.behaviors.viewsFilterConfigSelectAll = {
+    attach: function (context) {
+      // Show the select all checkbox.
+      $(context).find('#views-ui-handler-form div.form-item-options-value-all').once('filterConfigSelectAll')
+        .show()
+        .find('input[type=checkbox]')
+        .on('click', function () {
+          var checked = $(this).is(':checked');
+          // Update all checkbox beside the select all checkbox.
+          $(this).parents('.form-checkboxes').find('input[type=checkbox]').each(function () {
+            $(this).attr('checked', checked);
+          });
+        });
+      // Uncheck the select all checkbox if any of the others are unchecked.
+      $('#views-ui-handler-form').find('div.form-type-checkbox').not($('.form-item-options-value-all'))
+        .find('input[type=checkbox]')
+        .on('click', function () {
+          if ($(this).is('checked') === false) {
+            $('#edit-options-value-all').prop('checked', false);
+          }
+        });
     }
-  }});
+  };
 
-
-/**
- * Add a select all checkbox, which checks each checkbox at once.
- */
-Drupal.behaviors.viewsFilterConfigSelectAll = {
-  attach: function (context) {
-    // Show the select all checkbox.
-    $(context).find('#views-ui-handler-form div.form-item-options-value-all').once('filterConfigSelectAll')
-      .show()
-      .find('input[type=checkbox]')
-      .on('click', function () {
-        var checked = $(this).is(':checked');
-        // Update all checkbox beside the select all checkbox.
-        $(this).parents('.form-checkboxes').find('input[type=checkbox]').each(function () {
-          $(this).attr('checked', checked);
-        });
-      });
-    // Uncheck the select all checkbox if any of the others are unchecked.
-    $('#views-ui-handler-form').find('div.form-type-checkbox').not($('.form-item-options-value-all'))
-      .find('input[type=checkbox]')
-      .on('click', function () {
-        if ($(this).is('checked') === false) {
-          $('#edit-options-value-all').prop('checked', false);
-        }
+  /**
+   * Remove icon class from elements that are themed as buttons or dropbuttons.
+   */
+  Drupal.behaviors.viewsRemoveIconClass = {
+    attach: function (context) {
+      $(context).find('.dropbutton').once('dropbutton-icon', function () {
+        $(this).find('.icon').removeClass('icon');
       });
-  }
-};
-
-/**
- * Remove icon class from elements that are themed as buttons or dropbuttons.
- */
-Drupal.behaviors.viewsRemoveIconClass = {
-  attach: function (context) {
-    $(context).find('.dropbutton').once('dropbutton-icon', function () {
-      $(this).find('.icon').removeClass('icon');
-    });
-  }
-};
-
-/**
- * Change "Expose filter" buttons into checkboxes.
- */
-Drupal.behaviors.viewsUiCheckboxify = {
-  attach: function (context, settings) {
-    var $buttons = $('#edit-options-expose-button-button, #edit-options-group-button-button').once('views-ui-checkboxify');
-    var length = $buttons.length;
-    var i;
-    for (i = 0; i < length; i++) {
-      new Drupal.viewsUi.Checkboxifier($buttons[i]);
     }
-  }
-};
+  };
 
-/**
- * Change the default widget to select the default group according to the
- * selected widget for the exposed group.
- */
-Drupal.behaviors.viewsUiChangeDefaultWidget = {
-  attach: function () {
-    function changeDefaultWidget (event) {
-      if ($(event.target).prop('checked')) {
-        $('input.default-radios').hide();
-        $('td.any-default-radios-row').parent().hide();
-        $('input.default-checkboxes').show();
+  /**
+   * Change "Expose filter" buttons into checkboxes.
+   */
+  Drupal.behaviors.viewsUiCheckboxify = {
+    attach: function (context, settings) {
+      var $buttons = $('#edit-options-expose-button-button, #edit-options-group-button-button').once('views-ui-checkboxify');
+      var length = $buttons.length;
+      var i;
+      for (i = 0; i < length; i++) {
+        new Drupal.viewsUi.Checkboxifier($buttons[i]);
       }
-      else {
-        $('input.default-checkboxes').hide();
-        $('td.any-default-radios-row').parent().show();
-        $('input.default-radios').show();
+    }
+  };
+
+  /**
+   * Change the default widget to select the default group according to the
+   * selected widget for the exposed group.
+   */
+  Drupal.behaviors.viewsUiChangeDefaultWidget = {
+    attach: function () {
+      function changeDefaultWidget(event) {
+        if ($(event.target).prop('checked')) {
+          $('input.default-radios').hide();
+          $('td.any-default-radios-row').parent().hide();
+          $('input.default-checkboxes').show();
+        }
+        else {
+          $('input.default-checkboxes').hide();
+          $('td.any-default-radios-row').parent().show();
+          $('input.default-radios').show();
+        }
       }
+      // Update on widget change.
+      $('input[name="options[group_info][multiple]"]')
+        .on('change', changeDefaultWidget)
+        // Update the first time the form is rendered.
+        .trigger('change');
     }
-    // Update on widget change.
-    $('input[name="options[group_info][multiple]"]')
-      .on('change', changeDefaultWidget)
-      // Update the first time the form is rendered.
-      .trigger('change');
-  }
-};
+  };
 
-/**
- * Attaches an expose filter button to a checkbox that triggers its click event.
- *
- * @param button
- *   The DOM object representing the button to be checkboxified.
- */
-Drupal.viewsUi.Checkboxifier = function (button) {
-  this.$button = $(button);
-  this.$parent = this.$button.parent('div.views-expose, div.views-grouped');
-  this.$input = this.$parent.find('input:checkbox, input:radio');
-  // Hide the button and its description.
-  this.$button.hide();
-  this.$parent.find('.exposed-description, .grouped-description').hide();
+  /**
+   * Attaches an expose filter button to a checkbox that triggers its click event.
+   *
+   * @param button
+   *   The DOM object representing the button to be checkboxified.
+   */
+  Drupal.viewsUi.Checkboxifier = function (button) {
+    this.$button = $(button);
+    this.$parent = this.$button.parent('div.views-expose, div.views-grouped');
+    this.$input = this.$parent.find('input:checkbox, input:radio');
+    // Hide the button and its description.
+    this.$button.hide();
+    this.$parent.find('.exposed-description, .grouped-description').hide();
 
-  this.$input.on('click', $.proxy(this, 'clickHandler'));
+    this.$input.on('click', $.proxy(this, 'clickHandler'));
 
-};
+  };
 
-/**
- * When the checkbox is checked or unchecked, simulate a button press.
- */
-Drupal.viewsUi.Checkboxifier.prototype.clickHandler = function (e) {
-  this.$button
-    .trigger('mousedown')
-    .trigger('submit');
-};
-
-/**
- * Change the Apply button text based upon the override select state.
- */
-Drupal.behaviors.viewsUiOverrideSelect = {
-  attach: function (context) {
-    $(context).find('#edit-override-dropdown').once('views-ui-override-button-text', function () {
-      // Closures! :(
-      var $context = $(context);
-      var $submit = $context.find('[id^=edit-submit]');
-      var old_value = $submit.val();
+  /**
+   * When the checkbox is checked or unchecked, simulate a button press.
+   */
+  Drupal.viewsUi.Checkboxifier.prototype.clickHandler = function (e) {
+    this.$button
+      .trigger('mousedown')
+      .trigger('submit');
+  };
 
-      $submit.once('views-ui-override-button-text')
-        .on('mouseup', function () {
-          $(this).val(old_value);
-          return true;
-        });
+  /**
+   * Change the Apply button text based upon the override select state.
+   */
+  Drupal.behaviors.viewsUiOverrideSelect = {
+    attach: function (context) {
+      $(context).find('#edit-override-dropdown').once('views-ui-override-button-text', function () {
+        // Closures! :(
+        var $context = $(context);
+        var $submit = $context.find('[id^=edit-submit]');
+        var old_value = $submit.val();
+
+        $submit.once('views-ui-override-button-text')
+          .on('mouseup', function () {
+            $(this).val(old_value);
+            return true;
+          });
+
+        $(this).on('change', function () {
+          var $this = $(this);
+          if ($this.val() === 'default') {
+            $submit.val(Drupal.t('Apply (all displays)'));
+          }
+          else if ($this.val() === 'default_revert') {
+            $submit.val(Drupal.t('Revert to default'));
+          }
+          else {
+            $submit.val(Drupal.t('Apply (this display)'));
+          }
+          var $dialog = $context.closest('.ui-dialog-content');
+          $dialog.trigger('dialogButtonsChange');
+        })
+          .trigger('change');
+      });
 
-      $(this).on('change', function () {
-        var $this = $(this);
-        if ($this.val() === 'default') {
-          $submit.val(Drupal.t('Apply (all displays)'));
-        }
-        else if ($this.val() === 'default_revert') {
-          $submit.val(Drupal.t('Revert to default'));
-        }
-        else {
-          $submit.val(Drupal.t('Apply (this display)'));
-        }
-        var $dialog = $context.closest('.ui-dialog-content');
-        $dialog.trigger('dialogButtonsChange');
-      })
-        .trigger('change');
-    });
+    }
+  };
 
-  }
-};
-
-Drupal.behaviors.viewsUiHandlerRemoveLink = {
-  attach: function (context) {
-    var $context = $(context);
-    // Handle handler deletion by looking for the hidden checkbox and hiding the
-    // row.
-    $context.find('a.views-remove-link').once('views').on('click', function (event) {
-      var id = $(this).attr('id').replace('views-remove-link-', '');
-      $context.find('#views-row-' + id).hide();
-      $context.find('#views-removed-' + id).prop('checked', true);
-      event.preventDefault();
-    });
+  Drupal.behaviors.viewsUiHandlerRemoveLink = {
+    attach: function (context) {
+      var $context = $(context);
+      // Handle handler deletion by looking for the hidden checkbox and hiding the
+      // row.
+      $context.find('a.views-remove-link').once('views').on('click', function (event) {
+        var id = $(this).attr('id').replace('views-remove-link-', '');
+        $context.find('#views-row-' + id).hide();
+        $context.find('#views-removed-' + id).prop('checked', true);
+        event.preventDefault();
+      });
 
-    // Handle display deletion by looking for the hidden checkbox and hiding the
-    // row.
-    $context.find('a.display-remove-link').once('display').on('click', function (event) {
-      var id = $(this).attr('id').replace('display-remove-link-', '');
-      $context.find('#display-row-' + id).hide();
-      $context.find('#display-removed-' + id).prop('checked', true);
-      event.preventDefault();
-    });
-  }
-};
+      // Handle display deletion by looking for the hidden checkbox and hiding the
+      // row.
+      $context.find('a.display-remove-link').once('display').on('click', function (event) {
+        var id = $(this).attr('id').replace('display-remove-link-', '');
+        $context.find('#display-row-' + id).hide();
+        $context.find('#display-removed-' + id).prop('checked', true);
+        event.preventDefault();
+      });
+    }
+  };
 
 })(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/views_ui/js/views_ui.listing.js b/core/modules/views_ui/js/views_ui.listing.js
index 1b6f554..0374891 100644
--- a/core/modules/views_ui/js/views_ui.listing.js
+++ b/core/modules/views_ui/js/views_ui.listing.js
@@ -15,10 +15,10 @@
       var $table = $($input.attr('data-table'));
       var $rows;
 
-      function filterViewList (e) {
+      function filterViewList(e) {
         var query = $(e.target).val().toLowerCase();
 
-        function showViewRow (index, row) {
+        function showViewRow(index, row) {
           var $row = $(row);
           var $sources = $row.find('.views-table-filter-text-source');
           var textMatch = $sources.text().toLowerCase().indexOf(query) !== -1;
